MAUI中WebView2异步脚本对话框无法等待的解决方案问询
解决MAUI WebView2自定义Prompt对话框的异步阻塞问题
针对WebView2无法等待异步弹窗的问题,有两种可行的解决方案,推荐第二种异步交互方式更符合WinUI/MAUI的架构设计:
方案一:通过TaskCompletionSource同步等待异步弹窗结果
利用TaskCompletionSource在同步的ScriptDialogOpening事件中等待异步弹窗的结果,再将结果返回给WebView2:
1. 拦截默认对话框并注册事件
webView.CoreWebView2.Settings.AreDefaultScriptDialogsEnabled = false; webView.CoreWebView2.ScriptDialogOpening += CustomScriptDialogOpening;
2. 实现事件处理函数
private void CustomScriptDialogOpening(object sender, CoreWebView2ScriptDialogOpeningEventArgs e) { e.Handled = true; switch (e.Kind) { case CoreWebView2ScriptDialogKind.Prompt: var promptTcs = new TaskCompletionSource<string>(); // 在UI线程调度自定义弹窗 Application.Current.Dispatcher.Dispatch(async () => { var dialog = new CustomPromptDialog(e.Message, e.DefaultText); var result = await dialog.ShowAsync(); promptTcs.SetResult(result == ContentDialogResult.Primary ? dialog.InputText : null); }); // 同步等待结果,WebView2会阻塞到这里 string input = promptTcs.Task.Result; if (input != null) e.Accept(input); else e.Cancel(); break; // 同理处理Alert和Confirm case CoreWebView2ScriptDialogKind.Alert: var alertTcs = new TaskCompletionSource<bool>(); Application.Current.Dispatcher.Dispatch(async () => { var dialog = new CustomAlertDialog(e.Message); await dialog.ShowAsync(); alertTcs.SetResult(true); }); alertTcs.Task.Wait(); e.Accept(); break; case CoreWebView2ScriptDialogKind.Confirm: var confirmTcs = new TaskCompletionSource<bool>(); Application.Current.Dispatcher.Dispatch(async () => { var dialog = new CustomConfirmDialog(e.Message); var result = await dialog.ShowAsync(); confirmTcs.SetResult(result == ContentDialogResult.Primary); }); bool isConfirmed = confirmTcs.Task.Result; if (isConfirmed) e.Accept(); else e.Cancel(); break; } }
3. 自定义Prompt对话框示例
public class CustomPromptDialog : ContentDialog { public string InputText { get; private set; } public CustomPromptDialog(string message, string defaultText) { Title = "输入提示"; Content = new VerticalStackLayout { Children = { new Label { Text = message, Margin = new Thickness(0,0,0,10) }, new Entry { Text = defaultText, Placeholder = "请输入内容" } } }; PrimaryButtonText = "确定"; SecondaryButtonText = "取消"; PrimaryButtonClick += (s, args) => { InputText = ((Entry)((VerticalStackLayout)Content).Children[1]).Text; }; SecondaryButtonClick += (s, args) => { InputText = null; }; } }
方案二:JS Promise + WebMessage异步交互(推荐)
这种方式完全避免线程阻塞,通过替换JS的prompt函数为返回Promise的自定义函数,再通过WebView2的消息机制和C#端交互:
1. 注入自定义JS代码替换prompt
在WebView2初始化完成后执行:
await webView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(@" window.prompt = function(msg, defaultVal) { return new Promise(resolve => { // 发送消息到C#端 window.chrome.webview.postMessage({ type: 'prompt', message: msg, defaultValue: defaultVal || '' }); // 保存resolve回调供C#调用 window.__promptResolve = resolve; }); }; ");
2. 注册WebMessageReceived事件处理请求
webView.CoreWebView2.WebMessageReceived += async (sender, e) => { var msg = System.Text.Json.JsonSerializer.Deserialize<PromptRequest>(e.WebMessageAsJson); if (msg.Type != "prompt") return; // 显示自定义弹窗 var dialog = new CustomPromptDialog(msg.Message, msg.DefaultValue); var result = await dialog.ShowAsync(); string input = result == ContentDialogResult.Primary ? dialog.InputText : null; // 调用JS的resolve返回结果 await webView.CoreWebView2.ExecuteScriptAsync($"window.__promptResolve('{input}');"); }; // 定义消息模型 public class PromptRequest { public string Type { get; set; } public string Message { get; set; } public string DefaultValue { get; set; } }
方案对比
- 方案一:实现简单,直接拦截原生事件,但会短暂阻塞WebView2的事件线程,复杂场景下可能导致UI卡顿
- 方案二:完全异步,符合MAUI/WinUI的异步设计,无线程阻塞问题,扩展性更强,推荐使用
内容的提问来源于stack exchange,提问作者OMendoza
相关产品推荐
相关产品推荐

