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.