Skip to content

Commit

Permalink
Improve WPF example (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
jasongin authored Oct 24, 2023
1 parent a8877c1 commit 755f3f3
Show file tree
Hide file tree
Showing 9 changed files with 325 additions and 55 deletions.
7 changes: 5 additions & 2 deletions examples/wpf/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@

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

_**.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._
_**.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._

| Command | Explanation
|----------------------------------|--------------------------------------------------
Expand Down
14 changes: 14 additions & 0 deletions examples/wpf/Window1.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<Window x:Class="Microsoft.JavaScript.NodeApi.Examples.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Microsoft.JavaScript.NodeApi.Examples"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
mc:Ignorable="d"
Title="Window1" Height="450" Width="800">
<DockPanel>
<wv2:WebView2 Name="webView"
/>
</DockPanel>
</Window>
75 changes: 75 additions & 0 deletions examples/wpf/Window1.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Threading;
using System.Windows;
using Microsoft.Web.WebView2.Core;

namespace Microsoft.JavaScript.NodeApi.Examples;

public partial class Window1 : Window
{
private readonly string markdown;

public static void CreateWebView2Window(string markdown)
{
StaThreadWrapper(() => { new Window1(markdown).ShowDialog(); });
}

private Window1(string markdown)
{
this.markdown = markdown;
InitializeComponent();
}

private static void StaThreadWrapper(Action action)
{
var t = new Thread(o =>
{
action();
////System.Windows.Threading.Dispatcher.Run();
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
t.Join();
}

protected override async void OnContentRendered(EventArgs e)
{
base.OnContentRendered(e);
await webView.EnsureCoreWebView2Async(); // This will work just fine

webView.CoreWebView2.WebMessageReceived += CoreWebView2_WebMessageReceived;

string html = $@"
<!DOCTYPE html>
<html lang=""en"">
<body onload=""drawDiagram()"">
<script type=""module"">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
window.mermaid = mermaid;
</script>
<script>
const drawDiagram = async function () {{
mermaid.initialize({{ securityLevel: ""sandbox"" }})
const graphDefinition = `{markdown}`;
const {{ svg }} = await mermaid.render('graphDiv', graphDefinition);
window.chrome.webview.postMessage(svg);
document.getElementById('diagram').innerHTML = svg;
}}
</script>
<div id=""diagram""></div>
</body>
</html>
";
webView.NavigateToString(html);
}

/// <summary>
/// Triggers when Mermaid svg is generated
/// </summary>
private void CoreWebView2_WebMessageReceived(object sender, CoreWebView2WebMessageReceivedEventArgs e)
{
string data = e.TryGetWebMessageAsString();
Console.Write(data);
////Close();
}
}
3 changes: 3 additions & 0 deletions examples/wpf/wpf.csproj → examples/wpf/WpfExample.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
<OutDir>bin</OutDir>
<NodeApiAssemblyJSModuleType>esm</NodeApiAssemblyJSModuleType>
<UseWPF>true</UseWPF>
<GenerateNodeApiTypeDefinitionsForReferences>true</GenerateNodeApiTypeDefinitionsForReferences>
</PropertyGroup>

<ItemGroup>
Expand All @@ -20,6 +21,8 @@
<NodeApiSystemReferenceAssembly Include="System.Windows.Extensions" />
<NodeApiSystemReferenceAssembly Include="System.Windows.Input.Manipulations" />
<NodeApiSystemReferenceAssembly Include="System.Xaml" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2088.41" />
<PackageReference Include="Microsoft.JavaScript.NodeApi" Version="0.4.*-*" PrivateAssets="all" />
<PackageReference Include="Microsoft.JavaScript.NodeApi.Generator" Version="0.4.*-*" />
</ItemGroup>

Expand Down
24 changes: 23 additions & 1 deletion examples/wpf/example.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,29 @@

// @ts-check

import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

import dotnet from 'node-api-dotnet';
import './bin/PresentationFramework.js';
import './bin/WpfExample.js';

// Explicitly load some assembly dependencies that are not automatically loaded
// by the NodeApi assembly resolver. (This may be improved in the future.)
dotnet.load('System.Configuration.ConfigurationManager');
dotnet.load('System.Windows.Extensions');
dotnet.load(__dirname + '/pkg/microsoft.web.webview2/1.0.2088.41/lib/netcoreapp3.0/Microsoft.Web.WebView2.Wpf.dll');
dotnet.load('PresentationFramework.Aero2');

// Explicitly load some native library dependencies.
dotnet.load('wpfgfx_cor3');
dotnet.load(__dirname + '/bin/runtimes/win-x64/native/WebView2Loader.dll');

// Show a simple message box. (This doesn't need most of the dependencies.)
////dotnet.System.Windows.MessageBox.Show('Hello from JS!', "Example");

dotnet.System.Windows.MessageBox.Show('Hello from JS!', "Example");
// Show a WPF window with a WebView2 control that renders a mermaid diagram.
const diagram = 'graph TD\n A[Hello from JS!]';
dotnet.Microsoft.JavaScript.NodeApi.Examples.Window1.CreateWebView2Window(diagram);
23 changes: 21 additions & 2 deletions src/NodeApi.DotNetHost/ManagedHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -422,20 +422,24 @@ public JSValue LoadModule(JSCallbackArgs args)
/// </summary>
/// <returns>A JS object that represents the loaded assembly; each property of the object
/// is a public type.</returns>
/// <remarks>
/// Also supports loading native libraries, to make them available for assemblies to
/// resolve using DllImport.
/// </remarks>
public JSValue LoadAssembly(JSCallbackArgs args)
{
string assemblyNameOrFilePath = (string)args[0];

if (!_loadedAssembliesByPath.ContainsKey(assemblyNameOrFilePath) &&
!_loadedAssembliesByName.ContainsKey(assemblyNameOrFilePath))
{
LoadAssembly(assemblyNameOrFilePath);
LoadAssembly(assemblyNameOrFilePath, allowNativeLibrary: true);
}

return default;
}

private Assembly LoadAssembly(string assemblyNameOrFilePath)
private Assembly LoadAssembly(string assemblyNameOrFilePath, bool allowNativeLibrary = false)
{
Trace($"> ManagedHost.LoadAssembly({assemblyNameOrFilePath})");

Expand Down Expand Up @@ -481,6 +485,21 @@ private Assembly LoadAssembly(string assemblyNameOrFilePath)

LoadAssemblyTypes(assembly);
}
catch (BadImageFormatException)
{
if (!allowNativeLibrary)
{
throw;
}

// This might be a native DLL, not a managed assembly.
// Load the native library, which enables it to be auto-resolved by
// any later DllImport operations for the same library name.
NativeLibrary.Load(assemblyFilePath);

Trace("< ManagedHost.LoadAssembly() => loaded native library");
return null!;
}
finally
{
_loadingPath = previousLoadingPath;
Expand Down
14 changes: 14 additions & 0 deletions src/NodeApi.Generator/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,20 @@ public static int Main(string[] args)

for (int i = 0; i < s_assemblyPaths.Count; i++)
{
if (Path.GetFileName(s_assemblyPaths[i]).StartsWith(
typeof(JSValue).Namespace + ".", StringComparison.OrdinalIgnoreCase))
{
// Never generate type definitions for node-api-dotnet interop assemblies.
continue;
}

if (s_assemblyPaths.Take(i).Any(
(a) => string.Equals(a, s_assemblyPaths[i], StringComparison.OrdinalIgnoreCase)))
{
// Skip duplicate references.
continue;
}

// Reference other supplied assemblies, but not the current one.
List<string> allReferencePaths = s_referenceAssemblyPaths
.Concat(s_assemblyPaths.Where((_, j) => j != i)).ToList();
Expand Down
Loading

0 comments on commit 755f3f3

Please sign in to comment.