在后台线程中创建 WPF 组件

发布于 2024-09-05 22:05:36 字数 667 浏览 4 评论 0原文

我正在开发一个报告系统,将通过 DocumentPaginator 创建一系列 DocumentPage。这些文档包括许多要实例化的 WPF 组件,以便分页器在稍后发送到 XpsDocumentWriter 时包含正确的内容(进而发送到实际打印机) )。

我现在的问题是 DocumentPage 实例需要相当长的时间才能创建(足以让 Windows 将应用程序标记为冻结),因此我尝试在后台线程中创建它们,这是有问题的,因为 WPF 希望从 GUI 线程设置它们的属性。我还希望显示一个进度条,指示到目前为止已创建的页面数。因此,看起来我试图在 GUI 上并行发生两件事。

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

  • 创建一系列 DocumentPage。
    • 其中包括WPF 组件
    • 这些将在后台线程上创建,或者使用其他一些技巧,这样应用程序就不会被冻结。
  • 创建每个页面后,应更新 WPF ProgressBar

如果没有合适的方法来做到这一点,则非常欢迎替代解决方案和方法。

Im working on a reporting system, a series of DocumentPage are to be created through a DocumentPaginator. These documents include a number of WPF components that are to be instantiated so the paginator includes the correct things when later sent to the XpsDocumentWriter (which in turn is sent to the actual printer).

My problem now is that the DocumentPage instances take quite a while to create (enough for Windows to mark the application as frozen) so I tried to create them in a background thread, which is problematic since WPF expects the attributes on them to be set from the GUI thread. I would also like to have a progress bar showing up, indicating how many pages have been created so far. Thus, it looks like Im trying to get two things to happen in parallell on the GUI.

The problem is hard to explain and Im really not sure how to tackle it. In short:

  • Create a series of DocumentPage's.
    • These include WPF components
    • These are to be created on a background thread, or use some other trick so the application isnt frozen.
  • After each page is created, a WPF ProgressBar should be updated.

If there is no decent way to do this, alternate solutions and approaches are more than welcome.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

兮子 2024-09-12 22:05:36

只要线程是 STA,您就应该能够在后台线程中运行分页器。

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

thread.SetApartmentState(ApartmentState.STA);

如果您确实必须使用 GUI 线程,请查看 Freezable 类,因为您可能必须将对象从后台线程移动到 GUI 线程。

You should be able to run the paginator in a background thread as long as the thread is STA.

After you've set up your thread, try this prior to running it.

thread.SetApartmentState(ApartmentState.STA);

If you really must be on the GUI thread, then check out the Freezable class, as you might have to move the objects from your background thread to the GUI thread.

森林散布 2024-09-12 22:05:36

If the portions that require the UI thread are relatively small, you can use the Dispatcher to perform those operations without blocking the UI. There's overhead associated with this, but it may allow the bulk of the calculations to occur in the background and will interleave the work on the UI thread with other UI tasks. You can update the progress bar with the Dispatcher as well.

几度春秋 2024-09-12 22:05:36

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

只要使用文档的代码一次仅请求一页或两页,就不会有性能瓶颈。

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

最坏的情况是应用程序以缩略图视图显示页面。在这种情况下,我会:

  1. 缩略图视图将其 ItemsSource 绑定到最初填充有虚拟页面的“RealizedPages”集合
  2. 每当测量虚拟页面时,它都会在 DispatcherPriority.Background 处对调度程序操作进行排队以调用 DocumentPaginator.GetPage()然后用真实页面替换 RealizedPages 集合中的虚拟页面。

如果由于单独项目的数量而导致即使实现单个页面也存在性能问题,则可以在页面上具有大量项目的任何 ItemsControl 中使用相同的通用方法。

另请注意:XPS 打印系统一次不会处理多个 DocumentPage,因此,如果您知道那是您的客户,则实际上可以通过适当的修改一遍又一遍地返回相同的 DocumentPage。

My guess is that everything that is time-consuming to create is within your Visual. If so, there is an easy solution: Don't create actual DocumentPage objects and their associated Visuals until DocumentPaginator.GetPage() is called.

As long as the code that consumes your document only requests one or two pages at a time there will be no performance bottleneck.

If you're printing to the printer or to a file, everything can be done on a background thread, but if you're displaying onscreen you only need to display a few DocumentPages at a time anyway. In either case you won't get any UI lockups.

The worst case scenario would be an app that displays pages in a thumbnail view. In this case, I would:

  1. The thumbnail view would bind its ItemsSource to a "RealizedPages" collection which initially is filled with dummy pages
  2. Whenever a dummy page is measured, it queues a dispatcher operation at DispatcherPriority.Background to call DocumentPaginator.GetPage() and then replace the dummy page in the RealizedPages collection with the real page.

If there are performance concerns even with realizing a single page because of the number of separate items, this same general approach can be used within whatever ItemsControl on the page has the large number of items.

One more note: The XPS printing system doesn't ever process more than one DocumentPage at a time, so if you know that's your client you can actually just keep returning the same DocumentPage over and over again with appropriate modifications.

小女人ら 2024-09-12 22:05:36

进一步阐述 Ray Burns 的答案:难道您不能在后台线程的类中完成数据处理,然后在处理完成后将 DocumentPage 的属性数据绑定到此类吗?

Elaborating further on Ray Burns' answer: Couldn't you have your dataprocessing done in a class on a background thread and then databind the DocumentPage's properties to this class when the processing is done?

别闹i 2024-09-12 22:05:36

在这个问题上有点晚了,但我刚刚找到了一个解决方案,所以我想我会分享。为了显示 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 窗口,用于显示进度信息。

“上下文”保存我的进度对话框的进度数据。它包含一个已取消的属性,以便我可以中止在主线程上运行的操作。它还包括一个完整的属性,以便在操作完成时可以关闭进度对话框。

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

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

A little late to the game on this one, but I just worked out a solution to this so I thought I would share. In order to display the UI elements they have to be created on the UI thread on which they will be displayed. Since the long running task is on the UI thread, it will prevent a progress bar from updating. To get around this, I created the progress bar on a new UI thread and created the pages on the main UI thread.

        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' was my own WPF window for displaying progress information.

'context' holds the progress data for my progress dialog. It includes a cancelled property so that I can abort the action running on the main thread. It also includes a complete property so the progress dialog can close when the Action has finished.

'Action' is the method used to create all the UI elements. It monitors the context for the cancel flag and stops generating the UI elements if the flag is set. It sets the complete flag when it is done.

I don't remember the exact reason I had to set Thread 't' to an STA thread and IsBackground to true, but I am pretty sure it won't work without them.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文