在后台线程创建WPF组件

问题描述:

进出口报告系统的工作,一系列DocumentPage是通过DocumentPaginator创建。这些文件包括许多WPF组件将被实例化,所以分页程序包括正确的事情,当后发送到XpsDocumentWriter(而这又被发送到实际打印机)的。在后台线程创建WPF组件

我现在的问题是,DocumentPage情况下,需要一段时间来创造(足够的Windows标记应用为冷冻),所以我想在一个后台线程,这是有问题来创建它们,因为WPF期望的属性从GUI线程设置它们。我还想显示一个进度条,表明迄今为止创建了多少页面。因此,它看起来像我试图让两件事情在GUI上平行发生。

这个问题很难解释,我真的不知道如何解决它。总之:

  • 创建一系列DocumentPag e's。
    • 包括WPF组件
    • 这些都是要在后台线程创建,或者使用一些其他的技巧,以便应用程序的心不是冻结。
  • 在创建每个页面后,应更新WPF ProgressBar

如果没有体面的方式来做到这一点,替代解决方案和方法是值得欢迎的。

你应该能够只要线程是STA运行在后台线程分页程序。

设置完线程后,请在运行之前尝试此操作。

thread.SetApartmentState(ApartmentState.STA); 

如果你真的必须在GUI线程上,然后检查出Freezable类,因为你可能需要的对象从后台线程移动到GUI线程。

+0

设置apartmentstate可以让我在后台线程中创建WPF组件。但我需要稍后将它们移动到GUI线程。本文展示了一种方式(http://www.nbdtech.com/Blog/archive/2007/08/01/Passing-Wpf-Objects-Between-Threads-With-Source-Code.aspx),但是这是与FixedDocument,该方法不支持DocumentPage,因为它缺少默认构造函数。有没有办法可以使用Freezable类将DocumentPage移动到GUI线程? – Mizipzor 2010-06-10 21:10:54

+0

划伤Freezable建议 - DocumentPage不从Freezable继承,并且它包含一个Visual,因此创建DocumentPage的Freezable子类很困难。您提到的示例使用XamlReader和XamlWriter跨线程移动对象 - 也许您可以使用BinaryFormatter(或某种其他序列化方法)将DocumentPages序列化为流,然后在另一侧读取它们? – 2010-06-10 21:27:54

+0

看看BinaryFormatter,它看起来像使用XamlWriter的方法类似。也许它运作得更好,我看着它,谢谢。 =) – Mizipzor 2010-06-10 23:24:14

如果需要在UI线程的部分都比较小,你可以使用Dispatcher而不阻塞UI执行这些操作。这有相关的开销,但它可能允许大部分计算在后台进行,并将UI线程上的工作与其他UI任务交错。您也可以使用分派器更新进度条。

我的猜测是,所有耗时的工作都在Visual中。如果是这样,有一个简单的解决方案:在调用DocumentPaginator.GetPage()之前,不要创建实际的DocumentPage对象及其关联的视觉对象。

只要消耗你的文档中的代码只要求在同一时间不会有性能瓶颈的一个或两页。

如果你要打印到打印机或文件,一切都可以在后台线程来完成,但如果你在屏幕上显示你只需要反正一次显示几个DocumentPages。无论哪种情况,您都不会遇到任何UI锁定。

最糟糕的情况是应用程序在缩略图视图中显示页面。在这种情况下,我想:

  1. 缩略图视图将它的ItemsSource绑定到最初充满虚拟页面的“RealizedPages”收集
  2. 每当一个虚拟页面被测量后,它会将在的DispatcherPriority调度员操作。背景调用DocumentPaginator.GetPage(),然后将RealizedPages集合中的虚拟页面替换为实际页面。

如果有性能问题,即使由于单独项目的数量实现一个单一的页面,在此相同的一般方法可以内页面上的任何ItemsControl中有大量的项目中使用

还有一点需要注意:XPS打印系统一次不会处理一个以上的DocumentPage,因此如果您知道这是您的客户端,您可以通过适当的修改反复重复使用同一个DocumentPage。

详细阐述Ray Burns的回答:难道你没有在后台线程的类中完成数据处理,然后在处理完成后将DocumentPage的属性绑定到这个类?

这个游戏有点晚,但我只是想出了一个解决方案,所以我想我会分享。为了显示UI元素,必须在它们将显示的UI线程上创建UI元素。由于长时间运行的任务位于UI线程上,因此将阻止更新进度栏。为了解决这个问题,我在新的UI线程上创建了进度条,并在主UI线程上创建了页面。

 Thread t = new Thread(() => 
      { 
       ProgressDialog pd = new ProgressDialog(context); 
       pd.WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen; 
       pd.Show(); 
       System.Windows.Threading.Dispatcher.Run(); 
      }); 
     t.SetApartmentState(ApartmentState.STA); 
     t.IsBackground = true; 
     t.Start(); 

     Action(); //we need to execute the action on the main thread so that UI elements created by the action can later be displayed in the main UI 

'ProgressDialog'是我自己的WPF窗口,用于显示进度信息。

'context'保存我的进度对话框的进度数据。它包含一个取消的属性,以便我可以放弃在主线程上运行的动作。它还包含一个完整的属性,以便在操作完成时进度对话框可以关闭。

'Action'是用于创建所有UI元素的方法。它监视取消标志的上下文,并在标志被设置时停止生成UI元素。它完成后设置完整标志。

我不记得我不得不将线程't'设置为STA线程和IsBackground为true的确切原因,但我确信没有它们就无法工作。