views:

86

answers:

4

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.

A: 

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.

Matt Bridges
Setting the apartmentstate lets me create the WPF components in the background thread. But I need to move them to the GUI thread later. This article showed a way (http://www.nbdtech.com/Blog/archive/2007/08/01/Passing-Wpf-Objects-Between-Threads-With-Source-Code.aspx) but that was with FixedDocument, the approach doesnt work with DocumentPage since it lacks a default constructor. Is there a way I could use the Freezable class to move the DocumentPage to the GUI thread?
mizipzor
Scratch the Freezable suggestion -- DocumentPage doesn't inherit from Freezable, and it contains a Visual so it would be difficult to create a Freezable subclass of DocumentPage. The example you mention uses XamlReader and XamlWriter to move the object across threads -- perhaps you could use a BinaryFormatter (or some other serialization method) to serialize the DocumentPages to a stream and then read them back out on the other side?
Matt Bridges
Looking at the BinaryFormatter it seems like the approach there is similar to the one using the XamlWriter. Maybe it works better, Ill look into it, thanks. =)
mizipzor
A: 

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.

Dan Bryant
A: 

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.

Ray Burns
A: 

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?

Dabblernl