5

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

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

4

2 回答 2

7

在谷歌搜索之后,我偶然发现了以下线程,这似乎是我遇到的确切问题。

PrintDialog 和辅助 UI 线程严重问题

在那个线程中,这个家伙最终使用了一个自定义的 PrintDialog 类(其源代码可在此处找到),它与内置的 PrintDialog 非常相似,但有一些调整来修复这些跨线程错误(它也覆盖 XPS 文档编写器,它显然将自身进一步绑定到应用程序的主 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");
}

完美运行。

于 2011-09-07T15:07:34.853 回答
1

你的预感是正确的。当它由另一个线程创建时,您无法在 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 类”。这将使您的文档线程安全,可以从与创建它的线程不同的线程访问。

于 2011-09-07T14:34:06.970 回答