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

时间:2011-09-07 14:22:56

标签: wpf multithreading printing documentviewer printdialog

在我的WPF应用中,我特别Window包含DocumentViewer等其他控件。

当打开并加载此窗口时,它会动态构建带有进度指示器的FixedDocument,然后将其显示在DocumentViewer中。它工作,为了改善用户体验,我在自己的线程中运行此窗口,以便在构建文档时主应用程序窗口仍然响应。

根据this web page的提示,我在一个新的主题中打开我的窗口:

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的内置Print命令:

<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线程中打开,并尝试访问由我自己的线程创建和拥有的文档,因此崩溃。

我有什么想法可以解决这个问题?我想把窗口保持在自己的线程中。

2 个答案:

答案 0 :(得分:7)

经过一些谷歌搜索,我偶然发现了以下主题,这似乎是我遇到的确切问题。

PrintDialog and a secondary UI thread severe problem

在那个帖子中,这个人最终使用了一个自定义的PrintDialog类(其源代码被找到here),这与内置的PrintDialog非常相似,但是通过一些调整来修复这些交叉-thread bug(它还会覆盖XPS Document Writer,它显然会将自己更深入地绑定到应用程序的主UI线程中)

我复制并粘贴了该自定义PrintDialog的代码(并将该类重命名为ThreadSafePrintDialog),删除了我的Print按钮的CommandTarget,而是使用我自己的Print方法:

private void Print_Executed(object sender, ExecutedRoutedEventArgs args) {
    var printDialog = new ThreadSafePrintDialog();
    if (!printDialog.ShowDialog(this)) return;

    printDialog.PrintDocument(DocumentViewer.Document.DocumentPaginator, "My Document");
}

完美无缺。

答案 1 :(得分:1)

你的预感是正确的。当UI线程由另一个线程创建时,您无法在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. 您可以将此命令发送到创建此其他文档的线程吗?坚持这个主题并进行thread.Invoke(printMethod)

  3. 您可以查看Freezable Objects。请查看本页底部,标题为“创建自己的Freezable类”。这将使您的文档可以从与创建它的线程不同的线程进行线程安全访问。