在不同的UI线程中打印DocumentViewer的内容



在我的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;amp; writer, PrintTicket&amp;amp; partialTrustPrintTicket, PrintQueue&amp;amp; partialTrustPrintQueue, Double&amp;amp; width, Double&amp;amp; height, String jobDescription)
at System.Printing.PrintQueue.CreateXpsDocumentWriter(String jobDescription, PrintDocumentImageableArea&amp;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线程中访问它。

我相信你有几个选择:

  1. 你可以在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)
      });
      });
    }
    
  2. 您可以将此命令发送到创建另一个文档的线程?Hold住这个线程,执行thread.Invoke(printMethod)

  3. 你可以看看可冻结对象。看看这个页面的底部,标题是"创建你自己的可冻结类"。这将使您的文档线程安全,从不同的线程访问,而不是创建它的线程。

相关内容

  • 没有找到相关文章

最新更新