2

Recently we've been informed that we would be implementing new components for our product using ReactJs for future plans related reasons. However, we would depend on WebView2 in order to render these components within the current WPF App.

One of the challenges that we've faced is how to achieve two-way communication between the react component and the WPF App.

My team thought about using SignalR as a Mediator between the host and the component to achieve the communication, but not sure if this is the best solution in this case.

Yes, there is a facility provided by the WebView2 to invoke scripts against the component but this would achieve a one-way communication from the WPF App to the react component.

So what is needed now is how to achieve the vice-versa, Invoking something from the react component against the WPF App. Is there are any better solutions for this case?

mabiyan
  • 667
  • 7
  • 25
  • may be something similar https://stackoverflow.com/questions/9537641/node-js-socket-io-vs-signalr-vs-c-sharp-websocket-server – mabiyan Mar 17 '22 at 10:20
  • 1
    CoreWebView2 can communicate with the Document using [PostWebMessageAsJson()](https://learn.microsoft.com/en-us/dotnet/api/microsoft.web.webview2.core.corewebview2.postwebmessageasjson) (the Document adds an EventListener for `message`) and the Document with the application using `window.chrome.webview.postMessage(json)` -- The EventListener can be part of the Document or added later calling `ExecuteScriptAsync()` – Jimi Mar 17 '22 at 10:39

1 Answers1

0

For those interested I've just released WebView2.DevTools.Dom to NuGet.org. It's free for anyone to use.

More details and examples in the Readme.

You can expose a function to your JavaScript application to call.

await webView.EnsureCoreWebView2Async();

webView.CoreWebView2.DOMContentLoaded += async (s, e) =>
{
                var devToolsContext = await webView.CoreWebView2.CreateDevToolsContextAsync();

                await devToolsContext.ExposeFunctionAsync("jsAlertButtonClick", () =>
                {
                    _ = devToolsContext.EvaluateExpressionAsync("window.alert('Hello! You invoked window.alert()');");
                });

                await devToolsContext.ExposeFunctionAsync("csAlertButtonClick", () =>
                {
                    Dispatcher.InvokeAsync(() =>
                    {
                        WindowState = WindowState switch
                        {
                            WindowState.Maximized => WindowState.Normal,
                            WindowState.Normal => WindowState.Maximized,
                            _ => WindowState.Minimized,
                        };
                    });
                });

                var meaningOfLifeAsInt = await devToolsContext.EvaluateFunctionAsync<int>("() => Promise.resolve(42)");

                var jsAlertButton = await devToolsContext.QuerySelectorAsync("#jsAlertButton");
                var csAlertButton = await devToolsContext.QuerySelectorAsync("#csAlertButton");

                _ = jsAlertButton.AddEventListenerAsync("click", "jsAlertButtonClick");
                _ = csAlertButton.AddEventListenerAsync("click", "csAlertButtonClick");

                var innerText = await jsAlertButton.GetPropertyValueAsync<string>("innerText");

                var currentTimeSpan = await devToolsContext.QuerySelectorAsync("#current-time");
                var fpsSpan = await devToolsContext.QuerySelectorAsync("#fps");

                await devToolsContext.ExposeFunctionAsync<double, bool>("requestAnimationFrameCallback", (highResTime) =>
                {
                    var duration = NodaTime.Duration.FromNanoseconds(Math.Round(highResTime * 1000) * 1000);

                    callback(duration);

                    return false;
                });

                callback(NodaTime.Duration.Zero);

                void callback(NodaTime.Duration timestamp)
                {
                    _ = currentTimeSpan.SetInnerText(GetCurrentDateTime());
                    _ = fpsSpan.SetInnerText(CalculateFps(timestamp).ToString());

                    _ = devToolsContext.EvaluateExpressionAsync(@"window.requestAnimationFrame((x) => { window.requestAnimationFrameCallback(x)});");
                }
};

Functions persist across navigations so you only need to register a function once.

There's a working example available at https://github.com/ChromiumDotNet/WebView2.DevTools.Dom/blob/main/WebView2.DevTools.Dom.Wpf.Example/MainWindow.xaml.cs#L22

amaitland
  • 4,073
  • 3
  • 25
  • 63