在我的WPF应用程序中,我有特定的Window
,其中包含其他控件中的DocumentViewer
。
打开并加载此窗口时,动态构建带有进度指示器的FixedDocument
,并显示在DocumentViewer
中。它可以工作,并且为了改善用户体验,我在它自己的线程中运行这个窗口,以便在构建文档时主应用程序窗口仍然是响应的。
根据这个网页上的提示,我在一个新的线程中打开我的窗口,像这样:
public void ShowDocumentViewerWindow(params object[] data) {
var thread = new Thread(() => {
var window = new MyDocumentViewerWindow(new MyObject(data));
window.Closed += (s, a) => window.Dispatcher.InvokeShutdown();
window.Show();
System.Windows.Threading.Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
到目前为止,我对这个设置很满意,但是我刚刚遇到了一个问题。
MyDocumentViewerWindow
包含一个打印按钮,它引用了针对DocumentViewer的内置打印命令:
<Button Command="Print" CommandTarget="{Binding ElementName=MyDocumentViewer}">Print</Button>
在我把窗口放在它自己的线程中之前,这工作得很好。但现在,当我点击它时,应用程序崩溃了。Visual Studio 2010突出显示了上述代码中的以下行作为崩溃位置,并显示消息'调用线程无法访问此对象,因为另一个线程拥有它。':
System.Windows.Threading.Dispatcher.Run();
堆栈跟踪像这样开始:
at System.Windows.Threading.Dispatcher.VerifyAccess()
at MS.Internal.Printing.Win32PrintDialog.ShowDialog()
at System.Windows.Controls.PrintDialog.ShowDialog()
at System.Printing.PrintQueue.GatherDataFromPrintDialog(PrintDialog printDialog, XpsDocumentWriter&amp; writer, PrintTicket&amp; partialTrustPrintTicket, PrintQueue&amp; partialTrustPrintQueue, Double&amp; width, Double&amp; height, String jobDescription)
at System.Printing.PrintQueue.CreateXpsDocumentWriter(String jobDescription, PrintDocumentImageableArea&amp; documentImageableArea)
at System.Windows.Controls.Primitives.DocumentViewerBase.OnPrintCommand()
at System.Windows.Controls.Primitives.DocumentViewerBase.ExecutedRoutedEventHandler(Object target, ExecutedRoutedEventArgs args)
...
我的直觉是打印对话框在主UI线程中打开,并试图访问由我自己的线程创建和拥有的文档,因此崩溃。
我该怎么解决这个问题?我想让窗口保持在它自己的线程中
经过更多的谷歌搜索,我偶然发现了以下线程,这似乎是我所遇到的确切问题。
PrintDialog和一个辅助UI线程严重问题
在这个线程中,这个家伙最终使用了一个自定义的PrintDialog类(其源代码在这里找到),它与内置的PrintDialog非常相似,但是有一些调整来修复这些跨线程错误(并且它还覆盖了XPS Document Writer,它显然将自己进一步绑定到应用程序的主UI线程中)
我复制并粘贴了自定义PrintDialog的代码(并将类重命名为ThreadSafePrintDialog
),删除了我的打印按钮的CommandTarget,而是使用我自己的打印方法:
private void Print_Executed(object sender, ExecutedRoutedEventArgs args) {
var printDialog = new ThreadSafePrintDialog();
if (!printDialog.ShowDialog(this)) return;
printDialog.PrintDocument(DocumentViewer.Document.DocumentPaginator, "My Document");
}
完美工作。
你的预感是对的。当这个对象已经被其他线程创建时,你不能在UI线程中访问它。
我相信你有几个选择:
-
你可以在UI线程上创建这个文档,也许在后台线程中收集你需要的信息,然后在UI线程上实际构造对象。这取决于您的文档创建需要什么。你可以这样做:
public void CreateDocument(T inputDataForDocumentCreation) { var uiDispatcher = Dispatcher.CurrentDispatcher; ThreadPool.QueueUserWorkItem(_ => { // Load and create document components with yourDataForDocumentCreation dispatcher.BeginInvoke(DispatcherPriority.Normal, () => { //Actually create the document (this will happen on the UI thread, so it may be accessed from the UI thread) }); }); }
-
您可以将此命令发送到创建另一个文档的线程?Hold住这个线程,执行
thread.Invoke(printMethod)
-
你可以看看可冻结对象。看看这个页面的底部,标题是"创建你自己的可冻结类"。这将使您的文档线程安全,从不同的线程访问,而不是创建它的线程。