'How to communicate between JS and WPF through WebView2
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?
Solution 1:[1]
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | amaitland |
