我们有一个必须使用DocumentViewer显示FixedDocument的WPF应用程序。
FixedDocument包含几个页面需要大约10秒才能创建,所以我们决定异步创建这个。
由于FixedDocument包含FixedPages,并且这些页面包含UserControl对象的实例,我们需要在STA Apartment上创建此文档。
我们遇到了将创建的FixedDocument附加到DocumentViewer的问题,因为它们位于不同的UI线程中。
以下是UI代码段:
<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>
比你有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;
}
当代码运行时,我得到了着名的“调用线程无法访问此对象,因为不同的线程拥有它”。
我尝试使用TaskScheduler.FromCurrentSynchronizationContext()来修复它,但没有成功:
return tcs.Task.ContinueWith<FixedDocument>(r=>
{
return r.Result;
}, scheduler);
我理解错误的原因,但无法找到修复它的方法。使用调度程序并没有解决问题,因为我认为问题不在于调用代码的地方,而是我有两个需要通信的独立UI线程....
我该如何解决?
PS:DocumentViewer绑定方法是基于此great article执行的。
谢谢,
*更新* 我最终使用保存在临时文件上的XpsDocument作为中间媒体,因此DocumentViewer现在可以非常快速地打开它。将来,我希望找到一种方法来序列化FixedDocument,使其不会出现跨线程问题。
目前,我还在以编程方式创建包含每个FixedPage视觉的用户控件。
不知道为什么,但是第二次访问包含DocumentViewer的页面并生成了Document时,我无法再实例化我的自定义用户控件:
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();
}
这次我将把代码放到创建页面上。第二次调用此线程时,应用程序挂起在LabelView构造函数调用上:
//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);
如果我用TextBlock对象替换我的自定义控件,则不会出现问题。什么可能是错的?
更新
我缩小了自定义控件实例化问题,并隔离了使应用程序挂起的内容。 XAML包含对本地化机制(http://wpflocalizeextension.codeplex.com)的引用。有一些UI元素,如:
<TextBlock Text="{lex:Loc Description}" Style="{StaticResource labelTextBox}"/>
如果我在viewModel中进行本地化,问题就解决了。