我正在尝试将一些html内容呈现给Windows服务中的位图。
我正在使用System.Windows.Controls.WebBrowser来执行渲染。基本的渲染设置作为一个独立的进程工作,WPF窗口托管控件,但作为一项服务,至少我没有得到LoadCompleted事件。
我知道我至少需要一个Dispatcher或其他消息泵循环来实现这个WPF控件。也许我做得对,WebBrowser控件只需要额外的技巧/不兼容性。这就是我所拥有的:
我相信只有一个Dispatcher需要运行,并且它可以在服务的整个生命周期内运行。我相信Dispatcher.Run()是实际的循环本身,因此需要它自己的线程,否则它可以阻止。在这种情况下,该线程需要[STAThread]
。因此,在相关的静态构造函数中,我有以下内容:
var thread = new Thread(() =>
{
dispatcher = Dispatcher.CurrentDispatcher;
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
其中dispatcher
是静态字段。同样,我认为只能有一个,但我不确定我是否应该从任何地方使用Dispatcher.CurrentDispatcher()
并获得正确的参考。
渲染操作如下。我在WebBrowser
的线程上创建,导航和处理dispatcher
,但事件处理程序分配和mres.Wait
我认为可能都发生在渲染请求处理操作上。我得到了The calling thread cannot access this object because a different thread owns it
,但现在我没有这个设置。
WebBrowser wb = null;
var mres = new ManualResetEventSlim();
try
{
dispatcher.Invoke(() => { wb = new WebBrowser(); });
wb.LoadCompleted += (s, e) =>
{
// Not firing
};
try
{
using (var ms = new MemoryStream())
using (var sw = new StreamWriter(ms, Encoding.Unicode))
{
sw.Write(html);
sw.Flush();
ms.Seek(0, SeekOrigin.Begin);
// GO!
dispatcher.Invoke(() =>
{
try
{
wb.NavigateToStream(ms);
Debug.Assert(Dispatcher.FromThread(Thread.CurrentThread) != null);
}
catch (Exception ex)
{
// log
}
});
if (!mres.Wait(15 * 1000)) throw new TimeoutException();
}
}
catch (Exception ex)
{
// log
}
}
finally
{
dispatcher.Invoke(() => { if (wb != null) wb.Dispose(); });
}
当我运行它时,我每次都会得到超时异常,因为LoadCompleted永远不会触发。我试图验证调度员正在运行并正确抽水。不知道该怎么做,但我从静态构造函数中勾选了一些调度程序的事件,并从中获得了一些打印输出,所以我认为它正在工作。
代码确实到达wb.NavigateToStream(ms);
断点。
Dispatcher的这个错误的应用程序?由于其他原因,wb.LoadCompleted是否未触发?
谢谢!
答案 0 :(得分:1)
以下是代码的修改版本,可用作控制台应用。几点:
您需要WPF WebBrowser的父窗口。它可能是如下所示的隐藏窗口,但必须在物理上创建(即具有实时HWND句柄)。否则,WB永远不会完成加载文档(wb.Document.readyState == "interactive"
),并且LoadCompleted
永远不会被解雇。我不知道这种行为,它与WebBrowser控件的WinForms版本不同。请问为什么你选择这个项目的WPF?
您需要在创建WB控件的同一个线程上添加wb.LoadCompleted
事件处理程序(调度程序的线程在此处)。在内部,WPF WebBrowser只是公寓线程WebBrowser ActiveX控件的包装器,它通过IConnectionPointContainer
接口公开其事件。规则是,对公寓线程COM对象的所有调用必须在最初创建对象的线程上(或代理),因为这就是这种对象所期望的。从这个意义上说,IConnectionPointContainer
方法与WB的其他方法没什么不同。
一个次要的StreamWriter
会自动关闭它初始化的流(除非明确告知不在构造函数中这样做),因此不需要用{{1}包装流}。
代码已准备好编译和运行(它需要一些额外的程序集引用:PresentationFramework,WindowsBase,System.Windows,System.Windows.Forms,Microsoft.mshtml)。
using
答案 1 :(得分:0)
Webbrowser Control可能需要Desktop Interaction才能呈现内容:
我的感觉是说使用WPF控件,特别是Webbrowser-Control(=围绕IE ActiveX控件的Wrapper)并不是最好的想法。还有其他渲染引擎可能更适合这项任务:{ {3}}