0

We have a WPF application that must show a FixedDocument using a DocumentViewer.

The FixedDocument contains several pages that takes about 10 seconds to be created, so we decided to do this creation asynchronously.

As the FixedDocument contain FixedPages and these pages contains instances of UserControl objects, we need to create this document on a STA Apartment.

We are experiencing issues to attach the created FixedDocument to the DocumentViewer, since they are in different UI threads.

Here is the UI snippet:

<Grid Style="{StaticResource ContentRoot}">
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>

    <Label Grid.Row="0" Content="Loading..." Visibility="{Binding LabelDocument.IsNotCompleted,
        Converter={StaticResource BooleanToVisibilityConverter}}"/>

    <DocumentViewer  Document="{Binding LabelDocument.Result}" Grid.Row="1"/>

    <Label Content="{Binding UrlByteCount.ErrorMessage}" Grid.Row="2" Background="Red"
        Visibility="{Binding LabelDocument.IsFaulted,
        Converter={StaticResource BooleanToVisibilityConverter}}"/>
</Grid>

Than you have the ViewModel:

public LabelPrintViewModel()
    {
        //....
        LabelDocument = new NotifyTaskCompletion<FixedDocument>(CreateLabels());
    }

 private Task<FixedDocument> CreateLabels()
    {
        var tcs = new TaskCompletionSource<FixedDocument>();
        Thread thread = new Thread(() =>
        {
            try
            {
                //Code to create the Fixed Pages goes here.


                FixedDocument fixedDocument = new FixedDocument();

                foreach (var page in pages)
                {
                    PageContent pageContent = new PageContent();

                    ((System.Windows.Markup.IAddChild)pageContent).AddChild(page);
                    fixedDocument.Pages.Add(pageContent);
                }
                tcs.SetResult(fixedDocument);
            }
            catch (Exception ex)
            {
                tcs.SetException(ex);
            }

        });

        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        return tcs.Task;
    }

When the code runs I get the famous "The calling thread cannot access this object because a different thread owns it".

I tried to use the TaskScheduler.FromCurrentSynchronizationContext() to fix it, but no success:

return tcs.Task.ContinueWith<FixedDocument>(r=>
        {
            return r.Result;
        }, scheduler);

I understand the reason of the error, but cannot find a way to fix it. Using dispatcher is not solving the problem because I think the problem is not where the code is being called, but the fact that I have two separate UI threads that need to comunicate....

How can I fix that?

PS: The DocumentViewer binding approach was performed based on this great article.

Thank you,

*UPDATE * I end up using a XpsDocument saved on a temporary file as an intermediate media, so the DocumentViewer can now open it very quickly. In future, I hope to find a way to serialize the FixedDocument in a way that I do not get cross thread issues.

At the moment, I am still srugling to programmatically create my user control that contains each FixedPage visual.

Don't know why, but the second time I visit the page containing the DocumentViewer and the Document is generated, I can not longer instantiate my custom user control:

public async void OnNavigatedTo(FirstFloor.ModernUI.Windows.Navigation.NavigationEventArgs e)
    {
        LabelPrintViewModel vm = new LabelPrintViewModel();
        this.DataContext = vm;

        string s = await vm.LabelDocument.Task;

        XpsDocument doc = new XpsDocument(s, FileAccess.Read);
        docViewer.Document = doc.GetFixedDocumentSequence();

    } 

This time I will put the code to create the pages. The second time I call this thread the application hangs on the LabelView constructor call:

//Each item is a tinny ViewModel object to populate LabelView
                foreach (var item in _labelsToPrint)
                {
                    double pageWidth = 96 * 4.0;
                    double pageHeight = 96 * 2.12;

//The second time the calling page loads, the app hangs here.
                    LabelView lv = new LabelView(item);
                    lv.Width = pageWidth;
                    lv.Height = pageHeight;

                    FixedPage fp = new FixedPage();
                    fp.Width = pageWidth;
                    fp.Height = pageHeight;

                    fp.Children.Add(lv);

                    PageContent pageContent = new PageContent();
                    pageContent.Child = fp;

                    fixedDoc.Pages.Add(pageContent);
                }

                FileInfo tempFile = new FileInfo(System.IO.Path.GetTempPath() + DateTime.Now.Ticks.ToString() + ".xps");
                var paginator = fixedDoc.DocumentPaginator;
                var xpsDocument = new XpsDocument(tempFile.FullName, FileAccess.Write);
                var documentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
                documentWriter.Write(paginator);
                xpsDocument.Close();

                tcs.SetResult(tempFile.FullName);

If I replace my custom control with a TextBlock object, no problem happens. What might be wrong?

UPDATE

I narrowed down the custom control instantiation issue and isolated what was making the app to hang. The XAML contains references to a localization mechanism (http://wpflocalizeextension.codeplex.com). There are some UI elements like:

<TextBlock Text="{lex:Loc Description}" Style="{StaticResource labelTextBox}"/>

If I make the localization in the viewModel instead, the problem is solved.

Igor Kondrasovas
  • 1,479
  • 4
  • 19
  • 36
  • I know I am creating flow documents using BackGroundWorker. The document is returned to Complete which is on the main thread. – paparazzo Nov 25 '15 at 18:37
  • The short version is: the `FixedDocument` needs to be owned by (created in) the thread where you want to use it. You can't create it in one thread and then use it in a different thread. See the marked duplicate for additional details. – Peter Duniho Nov 25 '15 at 21:15
  • @Frisbee I am afraid I cannot use BackgroudWorker, since the thread must be STA. And the reason is because every FixedPage my FixedDocument has contain a custom UserControl object. If I try to not use STA, I get exceptions. Any Ideas? – Igor Kondrasovas Nov 25 '15 at 22:06
  • @PeterDuniho What would be the workaround strategy if the bottleneck of the FixedDocument creation is actual a user control instantiation. The control is simple, but multiply by 200 pages and you have issues. I was thinking about adding pages to the document asynchronously, but did nome come up with a solution. – Igor Kondrasovas Nov 25 '15 at 22:08
  • It gets ugly. You may be able to serialize to XML. If you search my name and FlowDocument I had a question a long time ago on it and a guy got me going. – paparazzo Nov 25 '15 at 22:21
  • _"What would be the workaround strategy if the bottleneck of the FixedDocument creation is actual a user control instantiation"_ -- in this case, I'm aware of none. Some UI objects inherit `Freezable`; they can generally be created on any thread, as long as they can be frozen before use in a different thread. But for non-freezable objects, you have to create them in the UI thread where they will be used. The usual answer is "don't do that". It's unlikely you actually need **200** different pages instantiated at the same time. How is a user supposed to interact with 200 different pages at once? – Peter Duniho Nov 25 '15 at 22:30
  • How does a question get closed on just one vote from a non-moderator? – paparazzo Nov 25 '15 at 22:39

0 Answers0