Skip to content

Commit 755f3f3

Browse files
authored
Improve WPF example (#163)
1 parent a8877c1 commit 755f3f3

9 files changed

Lines changed: 325 additions & 55 deletions

File tree

examples/wpf/README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11

22
## Example: Calling WFP APIs from JS
3-
The `example.js` script dynamically loads the WPF .NET assemblies and shows a simple message box.
3+
The `example.js` script loads WPF .NET assemblies and shows a WPF window with a WebView2
4+
control with a JS script that renders a mermaid diagram.
45

5-
_**.NET events** are not yet projected to JS ([#59](https://github.com/microsoft/node-api-dotnet/issues/59)). WPF capabilities will be limited until that issue is resolved._
6+
_**.NET events** are not yet projected to JS
7+
([#59](https://github.com/microsoft/node-api-dotnet/issues/59)).
8+
WPF capabilities will be limited until that issue is resolved._
69

710
| Command | Explanation
811
|----------------------------------|--------------------------------------------------

examples/wpf/Window1.xaml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Window x:Class="Microsoft.JavaScript.NodeApi.Examples.Window1"
2+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
3+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
4+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
5+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
6+
xmlns:local="clr-namespace:Microsoft.JavaScript.NodeApi.Examples"
7+
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
8+
mc:Ignorable="d"
9+
Title="Window1" Height="450" Width="800">
10+
<DockPanel>
11+
<wv2:WebView2 Name="webView"
12+
/>
13+
</DockPanel>
14+
</Window>

examples/wpf/Window1.xaml.cs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System;
2+
using System.Threading;
3+
using System.Windows;
4+
using Microsoft.Web.WebView2.Core;
5+
6+
namespace Microsoft.JavaScript.NodeApi.Examples;
7+
8+
public partial class Window1 : Window
9+
{
10+
private readonly string markdown;
11+
12+
public static void CreateWebView2Window(string markdown)
13+
{
14+
StaThreadWrapper(() => { new Window1(markdown).ShowDialog(); });
15+
}
16+
17+
private Window1(string markdown)
18+
{
19+
this.markdown = markdown;
20+
InitializeComponent();
21+
}
22+
23+
private static void StaThreadWrapper(Action action)
24+
{
25+
var t = new Thread(o =>
26+
{
27+
action();
28+
////System.Windows.Threading.Dispatcher.Run();
29+
});
30+
t.SetApartmentState(ApartmentState.STA);
31+
t.Start();
32+
t.Join();
33+
}
34+
35+
protected override async void OnContentRendered(EventArgs e)
36+
{
37+
base.OnContentRendered(e);
38+
await webView.EnsureCoreWebView2Async(); // This will work just fine
39+
40+
webView.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;
41+
42+
string html = $@"
43+
<!DOCTYPE html>
44+
<html lang=""en"">
45+
<body onload=""drawDiagram()"">
46+
<script type=""module"">
47+
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
48+
window.mermaid = mermaid;
49+
</script>
50+
<script>
51+
const drawDiagram = async function () {{
52+
mermaid.initialize({{ securityLevel: ""sandbox"" }})
53+
const graphDefinition = `{markdown}`;
54+
const {{ svg }} = await mermaid.render('graphDiv', graphDefinition);
55+
window.chrome.webview.postMessage(svg);
56+
document.getElementById('diagram').innerHTML = svg;
57+
}}
58+
</script>
59+
<div id=""diagram""></div>
60+
</body>
61+
</html>
62+
";
63+
webView.NavigateToString(html);
64+
}
65+
66+
/// <summary>
67+
/// Triggers when Mermaid svg is generated
68+
/// </summary>
69+
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
70+
{
71+
string data = e.TryGetWebMessageAsString();
72+
Console.Write(data);
73+
////Close();
74+
}
75+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
<OutDir>bin</OutDir>
88
<NodeApiAssemblyJSModuleType>esm</NodeApiAssemblyJSModuleType>
99
<UseWPF>true</UseWPF>
10+
<GenerateNodeApiTypeDefinitionsForReferences>true</GenerateNodeApiTypeDefinitionsForReferences>
1011
</PropertyGroup>
1112

1213
<ItemGroup>
@@ -20,6 +21,8 @@
2021
<NodeApiSystemReferenceAssembly Include="System.Windows.Extensions" />
2122
<NodeApiSystemReferenceAssembly Include="System.Windows.Input.Manipulations" />
2223
<NodeApiSystemReferenceAssembly Include="System.Xaml" />
24+
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2088.41" />
25+
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.4.*-*" PrivateAssets="all" />
2326
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.4.*-*" />
2427
</ItemGroup>
2528

examples/wpf/example.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,29 @@
33

44
// @ts-check
55

6+
import * as path from 'node:path';
7+
import { fileURLToPath } from 'node:url';
8+
const __filename = fileURLToPath(import.meta.url);
9+
const __dirname = path.dirname(__filename);
10+
611
import dotnet from 'node-api-dotnet';
712
import './bin/PresentationFramework.js';
13+
import './bin/WpfExample.js';
14+
15+
// Explicitly load some assembly dependencies that are not automatically loaded
16+
// by the NodeApi assembly resolver. (This may be improved in the future.)
17+
dotnet.load('System.Configuration.ConfigurationManager');
18+
dotnet.load('System.Windows.Extensions');
19+
dotnet.load(__dirname + '/pkg/microsoft.web.webview2/1.0.2088.41/lib/netcoreapp3.0/Microsoft.Web.WebView2.Wpf.dll');
20+
dotnet.load('PresentationFramework.Aero2');
21+
22+
// Explicitly load some native library dependencies.
23+
dotnet.load('wpfgfx_cor3');
24+
dotnet.load(__dirname + '/bin/runtimes/win-x64/native/WebView2Loader.dll');
25+
26+
// Show a simple message box. (This doesn't need most of the dependencies.)
27+
////dotnet.System.Windows.MessageBox.Show('Hello from JS!', "Example");
828

9-
dotnet.System.Windows.MessageBox.Show('Hello from JS!', "Example");
29+
// Show a WPF window with a WebView2 control that renders a mermaid diagram.
30+
const diagram = 'graph TD\n A[Hello from JS!]';
31+
dotnet.Microsoft.JavaScript.NodeApi.Examples.Window1.CreateWebView2Window(diagram);

src/NodeApi.DotNetHost/ManagedHost.cs

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -422,20 +422,24 @@ public JSValue LoadModule(JSCallbackArgs args)
422422
/// </summary>
423423
/// <returns>A JS object that represents the loaded assembly; each property of the object
424424
/// is a public type.</returns>
425+
/// <remarks>
426+
/// Also supports loading native libraries, to make them available for assemblies to
427+
/// resolve using DllImport.
428+
/// </remarks>
425429
public JSValue LoadAssembly(JSCallbackArgs args)
426430
{
427431
string assemblyNameOrFilePath = (string)args[0];
428432

429433
if (!_loadedAssembliesByPath.ContainsKey(assemblyNameOrFilePath) &&
430434
!_loadedAssembliesByName.ContainsKey(assemblyNameOrFilePath))
431435
{
432-
LoadAssembly(assemblyNameOrFilePath);
436+
LoadAssembly(assemblyNameOrFilePath, allowNativeLibrary: true);
433437
}
434438

435439
return default;
436440
}
437441

438-
private Assembly LoadAssembly(string assemblyNameOrFilePath)
442+
private Assembly LoadAssembly(string assemblyNameOrFilePath, bool allowNativeLibrary = false)
439443
{
440444
Trace($"> ManagedHost.LoadAssembly({assemblyNameOrFilePath})");
441445

@@ -481,6 +485,21 @@ private Assembly LoadAssembly(string assemblyNameOrFilePath)
481485

482486
LoadAssemblyTypes(assembly);
483487
}
488+
catch (BadImageFormatException)
489+
{
490+
if (!allowNativeLibrary)
491+
{
492+
throw;
493+
}
494+
495+
// This might be a native DLL, not a managed assembly.
496+
// Load the native library, which enables it to be auto-resolved by
497+
// any later DllImport operations for the same library name.
498+
NativeLibrary.Load(assemblyFilePath);
499+
500+
Trace("< ManagedHost.LoadAssembly() => loaded native library");
501+
return null!;
502+
}
484503
finally
485504
{
486505
_loadingPath = previousLoadingPath;

src/NodeApi.Generator/Program.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ public static int Main(string[] args)
6262

6363
for (int i = 0; i < s_assemblyPaths.Count; i++)
6464
{
65+
if (Path.GetFileName(s_assemblyPaths[i]).StartsWith(
66+
typeof(JSValue).Namespace + ".", StringComparison.OrdinalIgnoreCase))
67+
{
68+
// Never generate type definitions for node-api-dotnet interop assemblies.
69+
continue;
70+
}
71+
72+
if (s_assemblyPaths.Take(i).Any(
73+
(a) => string.Equals(a, s_assemblyPaths[i], StringComparison.OrdinalIgnoreCase)))
74+
{
75+
// Skip duplicate references.
76+
continue;
77+
}
78+
6579
// Reference other supplied assemblies, but not the current one.
6680
List<string> allReferencePaths = s_referenceAssemblyPaths
6781
.Concat(s_assemblyPaths.Where((_, j) => j != i)).ToList();

0 commit comments

Comments
 (0)