我在 .NET 中工作,具体来说是 C#,创建一个 Win Forms UserControl,其中包含一个 WebBrowser 控件。 WebBrowser 控件托管一个页面,该页面又使用第三方 javascript 组件。我遇到的问题是调用 javascript 函数来初始化第三方 javascript 组件并阻止 Windows 窗体应用程序中的 UI,直到组件被初始化,组件通过它具有的内部 javascript 事件通知您.
部分问题在于,更改第三方 javascript 组件的任何配置参数的唯一方法是使用新配置重新初始化它。因此,例如,如果您想将其设置为只读,则必须使用只读参数重新初始化它。
在能够调用 Document.InvokeScript 然后在网页中使用 window.external 调用 UserControl 方法方面,我已经完成了所有工作,但我遇到的问题是如何阻止 UserControl 代码使初始化 javascript 组件的调用,以便它等待并且在 javascript 组件的初始化完成之前不将控制权返回给用户。
我需要它以这种方式工作的原因是因为如果我在表单上有一个“只读”复选框,它会更改 UserControl 的 ReadOnly 属性以控制 javascript 组件是否将数据显示为只读和用户非常快地单击该复选框,您将收到 javascript 错误,或者该复选框将与 javascript 组件的实际只读状态不同步。这似乎是因为控件在其配置更改后尚未重新初始化,而您已经在尝试再次更改它。
我花了很多时间尝试找到一种方法,使用从 AutoResetEvent 到 Application.DoEvents 等等的所有东西让它工作,但似乎无法让它工作。
我找到的最接近的是 Invoke a script in WebBrowser, and wait for it to finish running (synchronized)但它使用了 VS2012 中引入的功能(我正在使用 VS2010),我认为它无论如何都行不通,因为它有点不同,因为你不需要等待 javascript 事件触发。
如有任何帮助,我们将不胜感激。
最佳答案
首先的问题是需要“阻塞”UI 线程,直到某个事件被触发。通常可以重构应用程序以使用异步事件处理程序(使用或不使用 async/await
),将执行控制权交还给消息循环并避免任何阻塞。
现在让我们说,出于某种原因你不能重构你的代码。在这种情况下,您需要一个辅助模式消息循环。您还需要在等待事件时禁用主 UI,以避免讨厌的重新进入场景。等待本身应该是用户友好的(例如,使用等待光标或进度动画)并且不忙(避免在使用 DoEvents
的紧密循环中消耗 CPU 周期)。
实现此目的的一种方法是使用带有用户友好消息的模态对话框,当所需的 JavaScript 事件/回调发生时,该消息会自动消失。这是一个完整的示例:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace WbTest
{
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IScripting))]
public partial class MainForm : Form, IScripting
{
WebBrowser _webBrowser;
Action _onScriptInitialized;
public MainForm()
{
InitializeComponent();
_webBrowser = new WebBrowser();
_webBrowser.Dock = DockStyle.Fill;
_webBrowser.ObjectForScripting = this;
this.Controls.Add(_webBrowser);
this.Shown += MainForm_Shown;
}
void MainForm_Shown(object sender, EventArgs e)
{
var dialog = new Form
{
Width = 100,
Height = 50,
StartPosition = FormStartPosition.CenterParent,
ShowIcon = false,
ShowInTaskbar = false,
ControlBox = false,
FormBorderStyle = FormBorderStyle.FixedSingle
};
dialog.Controls.Add(new Label { Text = "Please wait..." });
dialog.Load += (_, __) => _webBrowser.DocumentText =
"<script>setTimeout(function() { window.external.OnScriptInitialized}, 2000)</script>";
var canClose = false;
dialog.FormClosing += (_, args) =>
args.Cancel = !canClose;
_onScriptInitialized = () => { canClose = true; dialog.Close(); };
Application.UseWaitCursor = true;
try
{
dialog.ShowDialog();
}
finally
{
Application.UseWaitCursor = false;
}
MessageBox.Show("Initialized!");
}
// IScripting
public void OnScriptInitialized()
{
_onScriptInitialized();
}
}
[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IScripting
{
void OnScriptInitialized();
}
}
看起来像这样:
另一种选择(对用户不太友好)是使用类似 WaitOneAndPump
来自 here 的东西.您仍然需要注意禁用主 UI 并向用户显示某种等待反馈。
已更新以解决评论。您的 WebBrowser 实际上是 UI 的一部分并且对用户可见吗?用户应该能够与之交互吗?如果是这样,则不能使用辅助线程来执行 JavaScript。您需要在主线程上执行此操作并继续发送消息,但 WaitOne
不会发送大部分 Windows 消息(它仅 pumps a small fraction of them ,与 COM 相关)。您也许可以使用我上面提到的 WaitOneAndPump
。您仍然需要在等待时禁用 UI,以避免重新进入。
无论如何,那仍然是一场混战。您真的不应该仅仅为了保持线性代码流而阻止执行。如果您不能使用 async/await
,您始终可以实现一个简单的状态机类并使用回调从它离开的地方继续。这就是 async/await
之前的情况。
关于c# - 在 WebBrowser 中调用一个 javascript 函数并等待 javascript 事件触发,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25418836/