WebViewコントロール(MAUI)とWebページ(JavaScript)の連携
WebViewコントロール(MAUI)とWebページ(JavaScript)の連携
アプリケーションとWebViewコントロール(MAUI)のWebコンテンツとの間で、データのやり取りを行うことができます。
アプリからJavaScriptの処理を実行するEvaluateJavaScriptAsync
メソッドや、
JavaScriptコードにてアプリに文字を送ることが可能です。
C#からWebページのJavaScriptを実行する方法
WebViewコントロール(MAUI)には、表示しているページに対してJavaScriptを実行するEvaluateJavaScriptAsync
メソッドがあります。
このEvaluateJavaScriptAsync
メソッドの引数にJavaScriptのコードを渡すことで実行されます。
実行結果を戻り値で取得することも可能です。
下記コードでは、アプリでボタン押下するとWeb画面でalertを表示し、JavaScriptからの値をアプリで取得してデバッグに出力ます。
//Xamlファイルにボタンを追加し、xaml.csファイルに以下のコードを追加します。 private async void OnJsClicked(object sender, EventArgs e) { var result = await MyWebView.EvaluateJavaScriptAsync("function test(){alert('test');return 'ok'}test();"); Debug.WriteLine($"return val = {result}"); }
注意
JavaScriptの記載が誤っている場合は、Web側で何も処理されないか、例外が発生します。
JavaScriptのコードを複数行の文字列(StringBuilderで連結したり、@を使った場合)で構成すると、なぜかは分かりませんがJavaScriptが動作しませんでした。
これを回避する為に、各行の末尾にバックスラッシュを入れるか、改行の削除(「js.Replace("\r\n", "")」)を行っています。
参考
JavaScriptからメッセージを送り、C#で受け取る方法
WebViewコントロール(MAUI)にはJavaScriptからのメッセージを受け取るイベントがありませんので、
受け取れるようにWebViewコントロールを拡張します。
MauiProgram.csに拡張WebViewの定義を行います。CreateMauiAppメソッドを以下のように変更します。
public static MauiApp CreateMauiApp() { var builder = MauiApp.CreateBuilder(); builder .UseMauiApp<App>() .ConfigureFonts(fonts => { fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold"); }) .ConfigureMauiHandlers(handlers => { handlers.AddHandler(typeof(WebViewEx), typeof(WebViewExHandler)); }); return builder.Build(); }
拡張WebViewのクラスとしてWebViewEx.csを作成して、以下のコードにします。
InvokeActionメソッド
namespace MauiWebView { public class JavaScriptActionEventArgs : EventArgs { public string Payload { get; private set; } public JavaScriptActionEventArgs(string payload) { Payload = payload; } } public class WebViewEx : WebView { public event EventHandler<JavaScriptActionEventArgs> JavaScriptAction; public WebViewEx() { } public void InvokeAction(string data) { JavaScriptAction?.Invoke(this, new JavaScriptActionEventArgs(data)); } } }
※変更する部分を抜粋して記載してます。
・・・ <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:controls="clr-namespace:MauiWebView" x:Class="MauiWebView.MainPage"> ・・・ <controls:WebViewEx x:Name="MyWebView" HorizontalOptions="Center" HeightRequest="300" WidthRequest="500" /> <Button x:Name="NavigateBtn" Text="ローカルhtmlを表示" Clicked="OnNavigateClicked" HorizontalOptions="Center" />
namespace MauiWebView; public partial class MainPage : ContentPage { public MainPage() { InitializeComponent(); MyWebView.JavaScriptAction += WebViewEx_JavaScriptAction; } private void OnNavigateClicked(object sender, EventArgs e) { string htmlSource = @" <html> <head></head> <body> <script> var counter = 1; function buttonClicked(e) { invokeCSharpAction(String(counter++)); } function invokeCSharpAction(data){window.chrome.webview.postMessage(data);} </script> <h1>サンプル</h1> <button id='firebtn' onclick='javascript:buttonClicked(event)'>クリック</button> </html> "; MyWebView.Source = new HtmlWebViewSource() { Html = htmlSource }; } private void WebViewEx_JavaScriptAction(object sender, JavaScriptActionEventArgs e) { Dispatcher.Dispatch(() => { ChangeLabel.Text = "html内のクリック数: " + e.Payload; }); } }
using Microsoft.Maui.Handlers; using Microsoft.UI.Xaml.Controls; namespace MauiWebView.Platforms.Windows { public class WebViewExHandler : WebViewHandler { const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}"; public static IPropertyMapper<WebViewEx, WebViewExHandler> PropertyMapper = new PropertyMapper<WebViewEx, WebViewExHandler>(WebViewHandler.Mapper) { [nameof(WebViewEx.Source)] = MapSource, }; public WebViewExHandler() : base(PropertyMapper, CommandMapper) { } protected override WebView2 CreatePlatformView() { var webView = new WebView2(); webView.WebMessageReceived += MessageReceived; return webView; } protected async override void ConnectHandler(WebView2 platformView) { base.ConnectHandler(platformView); await platformView.EnsureCoreWebView2Async(); } protected override void DisconnectHandler(WebView2 platformView) { base.DisconnectHandler(platformView); } //JavaScriptからメッセージを受信したときに実行 private void MessageReceived(object sender, Microsoft.Web.WebView2.Core.CoreWebView2WebMessageReceivedEventArgs args) { //JavaScriptから通知された値を取得 //String text = args.TryGetWebMessageAsString(); //例外になる var text = args.WebMessageAsJson; //WebViewクラスに通知 ((WebViewEx)VirtualView)?.InvokeAction(text); } public static void MapSource(WebViewExHandler handler, WebViewEx wv) { if (wv.Source != null) { if(wv.Source is UrlWebViewSource url) { (handler?.PlatformView).Source = new Uri(url.Url); } else if(wv.Source is HtmlWebViewSource html) { (handler?.PlatformView).NavigateToString(html.Html); } } } } }
using CoreGraphics; using Foundation; using Microsoft.Maui.Controls; using Microsoft.Maui.Handlers; using Microsoft.Maui.Platform; using WebKit; using MauiWebView; using System; namespace MauiWebView.Platforms.iOS { public class WebViewExHandler : WebViewHandler { const string JavaScriptFunction = "function invokeCSharpAction(data){window.webkit.messageHandlers.invokeAction.postMessage(data);}"; private WKUserContentController userController; private JSBridge jsBridgeHandler; public static IPropertyMapper<WebViewEx, WebViewExHandler> PropertyMapper = new PropertyMapper<WebViewEx, WebViewExHandler>(WebViewHandler.Mapper) { [nameof(WebViewEx.Source)] = MapSource, }; public WebViewExHandler() : base(PropertyMapper, CommandMapper) { } public static void MapSource(WebViewExHandler handler, WebViewEx wv) { LoadSource(wv.Source, handler?.PlatformView); } protected override WKWebView CreatePlatformView() { jsBridgeHandler = new JSBridge(this); userController = new WKUserContentController(); var script = new WKUserScript(new NSString(JavaScriptFunction), WKUserScriptInjectionTime.AtDocumentEnd, false); userController.AddUserScript(script); userController.AddScriptMessageHandler(jsBridgeHandler, "invokeAction"); var config = new WKWebViewConfiguration { UserContentController = userController }; var webView = new WKWebView(CGRect.Empty, config); return webView; } protected override void ConnectHandler(WKWebView platformView) { base.ConnectHandler(platformView); } protected override void DisconnectHandler(WKWebView platformView) { base.DisconnectHandler(platformView); userController.RemoveAllUserScripts(); userController.RemoveScriptMessageHandler("invokeAction"); jsBridgeHandler?.Dispose(); jsBridgeHandler = null; } private static void LoadSource(WebViewSource source, WKWebView control) { if (source is HtmlWebViewSource html) { control.LoadHtmlString(html.Html, new NSUrl(html.BaseUrl ?? "http://localhost", true)); } else if (source is UrlWebViewSource url) { control.LoadRequest(new NSUrlRequest(new NSUrl(url.Url))); } } } public class JSBridge : NSObject, IWKScriptMessageHandler { readonly WeakReference<WebViewExHandler> hybridWebViewRenderer; internal JSBridge(WebViewExHandler hybridRenderer) { hybridWebViewRenderer = new WeakReference<WebViewExHandler>(hybridRenderer); } public void DidReceiveScriptMessage(WKUserContentController userContentController, WKScriptMessage message) { WebViewExHandler hybridRenderer; if (hybridWebViewRenderer.TryGetTarget(out hybridRenderer)) { ((WebViewEx)hybridRenderer.VirtualView)?.InvokeAction(message.Body.ToString()); } } } }
参考