'Which WebView2 event/function can be use to replace ScriptNotify Webview event?

Today, I have decided to migrate WebView control to WebView2 control in a VB.net application.

I have installed WebView2 from NuGet and I have changed declarations and initialization lines (only 4 lines in my VB program because I'm using 2 WebView2).

When I build my application, Visual Studio 2019 indicates that ScriptNotify event is not an event of WebView2!

Private Sub wvSelect_ScriptNotify(
    sender As Object, 
    e As WebViewControlScriptNotifyEventArgs) Handles wvSelect.ScriptNotify

Using old WebView control, this event is generated using windows.external.notify Javascript function.

function clickPlus(e) { window.external.notify("+PLUS:"); }

Which event is replacing ScriptNotify event in WebView2 control?

I'm interested to know the new event and also how this event is called from Javascript.



Solution 1:[1]

A few possible scenarios to handle event notifications with WebView2:
See also: Use JavaScript in WebView for extended scenarios.

Generic initialization:

  • The WebView2 instance is named myWebView2.
  • Using basic methods: for example, the JSON deserialization is performed using JavaScriptSerializer. You probably have Json.Net or System.Text.Json instead.
  • Also assuming WinForms, you haven't specified the GUI framework

Private Async Sub Load(sender As Object, e as EventArgs) Handles MyBase.Load
    await myWebView2.EnsureCoreWebView2Async(Nothing)
End Sub

Also, I'm using this simple class model, used to deserialize the JSON message received, since WebView2.WebMessageReceived assumes a JSON format; you can pass whatever string you want, though.

Private Class JsonEventMessage
    Public Property Element As String
    Public Property Value As String
End Class

1 - In this scenario, javascript Functions are already defined in the HTML

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        function SomeFunction(e, v) {
           var json = JSON.stringify({ Element:e.target.id, Value:v });
           window.chrome.webview.postMessage(json);
        }
    </script>
</head>
<body>
    <script>
        document.getElementById("div1").addEventListener("click", function(e) {SomeFunction(e, '+PLUS:')});
    </script>
    <div id="div1">Some Text to Click</div>
</body>
</html>

In this case, you just need to subscribe to the WebMessageReceived event and deserialize the JSON:

AddHandler myWebView2.WebMessageReceived, AddressOf myWebView2_WebMessageReceived

' [...]

Private Sub myWebView2_WebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs)
    Dim message = New JavaScriptSerializer().Deserialize(Of JsonEventMessage)(e.TryGetWebMessageAsString())
    Console.WriteLine(message.Element)
    Console.WriteLine(message.Value)
End Sub

Note: I'm using e.TryGetWebMessageAsString() and not e.WebMessageAsJson since the latter is enclosed in quotes (double-stringified). It's better used when you pass a simple string.


2 - Here, a JavaScript Function that can notify a message is defined in HEAD, but no event handlers are added anywhere else.
Assume you will add HTML Elements at run-time or you don't want to add the event handlers in the HTML or anything else.

<!DOCTYPE html>
<html>
<head>
    <script type="text/javascript">
        function SomeFunction(e, v) {
           var json = JSON.stringify({ Element:e.target.id, Value:v });
           window.chrome.webview.postMessage(json);
        }
    </script>
</head>
<body>
    <div id="div1">Some Text to Click</div>
</body>
</html>

Subscribe to the WebMessageReceived event as before and to the NavigationCompleted event. This event is raised when the Document is loaded. We need this event, since, in the example, the event handlers are added to Elements in the Body, which is not available before this point.

In NavigationCompleted, we can add the JavaScript Function to a string and call the WebView2.ExecuteScriptAsync() method, which executes the script after the DOM is ready, otherwise GetElementById() would not find the target.

AddHandler myWebView2.WebMessageReceived, AddressOf myWebView2_WebMessageReceived
AddHandler myWebView2.NavigationCompleted, AddressOf myWebView2_NavigationCompleted

' [...]

Private Sub myWebView2_WebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs)
    Dim message = New JavaScriptSerializer().Deserialize(Of JsonEventMessage)(e.TryGetWebMessageAsString())
    ' [...]
End Sub

Private Async Sub myWebView2_NavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs)
    Dim func As String =
        "document.getElementById('div1').
            addEventListener('click', function(e) { 
                SomeFunction(e, '+PLUS:')
            });"
    Await myWebView2.ExecuteScriptAsync(func)
End Sub

3 - This time, no JavaScript Functions are defined anywhere in the HTML.

<!DOCTYPE html>
<html>
<head>
</head>
<body>
    <div id="div1">Some Text to Click</div>
</body>
</html>

Case 1:
Subscribe to the WebMessageReceived and NavigationCompleted events as before.

In NavigationCompleted, the JavaScript Function that posts the message is added to the event handler directly. Call ExecuteScriptAsync() to execute the script and add the Event Listener to a HTML Element.

AddHandler myWebView2.WebMessageReceived, AddressOf myWebView2_WebMessageReceived
AddHandler myWebView2.NavigationCompleted, AddressOf myWebView2_NavigationCompleted

' [...]

Private Sub myWebView2_WebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs)
    Dim message = New JavaScriptSerializer().Deserialize(Of JsonEventMessage)(e.TryGetWebMessageAsString())
    ' [...]
End Sub

Private Async Sub myWebView2_NavigationCompleted(sender As Object, e As CoreWebView2NavigationCompletedEventArgs)
    Dim func As String =
    "document.getElementById('div1').
        addEventListener('click', function(e) { 
            var json = JSON.stringify({Element:e.target.id, Value:'+PLUS:'});
            window.chrome.webview.postMessage(json); 
    });"
    Await myWebView2.ExecuteScriptAsync(func)
End Sub

Case 2:
Subscribe to the WebMessageReceived and WebView2.CoreWebView2InitializationCompleted.
In this case, we're adding an event listener to the Document, then determine which Element has been clicked using the Event.Target member and any other property or value that can be useful to handle the notification.

Here, I'm simply passing the [Event].target.id and [Event].target.innerText as JSON properties. You can of course get any other attribute necessary in your use-case.

AddHandler myWebView2.WebMessageReceived, AddressOf myWebView2_WebMessageReceived
AddHandler myWebView2.CoreWebView2InitializationCompleted, AddressOf myWebView2_CoreWebView2InitializationCompleted
' [...]

Private Sub myWebView2_WebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs)
    Dim message = New JavaScriptSerializer().
        Deserialize(Of JsonEventMessage)(e.TryGetWebMessageAsString())
    ' [...]
End Sub

Private Async Sub myWebView2_CoreWebView2InitializationCompleted(
    sender As Object, 
    e As CoreWebView2InitializationCompletedEventArgs)

    Dim func as String = 
    "document.addEventListener
        ('click', function(e) {
            window.chrome.webview.postMessage(
                JSON.stringify({ Element:e.target.id, Value:e.target.innerText })
            );
       });"
    Await myWebView2.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(func)
End Sub

Solution 2:[2]

My problem is now solved and my solution is following ...

I want to display following HTML page

<div id="table">
    <div id='Keyboard' class='panel'>
        <div class='krow'>
            <span class='k digit'>7</span>
            <span class='k digit'>8</span>
            <span class='k digit'>9</span>
            <span class='k back'>&#x2B60;x</span>
        </div>
        <div class='krow'>
            <span class='k digit'>4</span>
            <span class='k digit'>5</span>
            <span class='k digit'>6</span>
            <span class='k delete'>x&#x2B60;</span>
        </div>
        <div class='krow'>
            <span class='k digit'>1</span>
            <span class='k digit'>2</span>
            <span class='k digit'>3</span>
            <span class='k clear'>&#x21E4;&#x22C6;</span>
        </div>
        <div class='krow'>
            <span class='k empty'></span>
            <span class='k digit'>0</span>
            <span class='k point'>.</span>
            <span class='k plus'><b>+</b></span>
        </div>
    </div>
</div>

To intercept click on digit <div>, I have written following Javascript lines

$(document).ready(function ()
    {
    $('.digit').click(function (ev) { clickDigit(ev); });
    ...
    }

function clickDigit(e)
    {
    eDigit = e.target;
    var sValue = eDigit.innerText;
    window.chrome.webview.postMessage("+DIGIT:" + sValue);
    }

As you can see, I call postMessage() function to send a notification to VB.Net code.

To initiaze and display my HTML code, I have written following lines

Private Sub Form_Load(sender As Object, e As EventArgs) Handles MyBase.Load
    InitializeAsync()
End Sub

Async Sub InitializeAsync()
    Await wvSelect.EnsureCoreWebView2Async()
    wvSelect.NavigateToString(sHtmlText)
End Sub

where WebView2 control is defined in Designer part of my Form

Friend WithEvents wvSelect As Microsoft.Web.WebView2.WinForms.WebView2

Finally, to intercept JavaScript notification, I have written following VB.Net Code

Private Sub wvSelect_WebMessageReceived(sender As Object, e As CoreWebView2WebMessageReceivedEventArgs) _
    Handles wvSelect.WebMessageReceived

    Dim sValue As String = e.TryGetWebMessageAsString
    Dim iPos = sValue.IndexOf(":")
    Dim sAction = sValue.Substring(0, iPos)
    sValue = sValue.Substring(iPos + 1)

    Select Case sAction
        Case "+DIGIT"
            SendMessage(txtInput.Handle, WM_CHAR, AscW(sValue.Chars(0)), 0)

I hope than what I have reported in this answer can help others users and complete Jimi answer.

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
Solution 2 schlebe