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());
}
}
}
}
参考
