我需要使用诸如FixedDocument,FlowDocument,PageContent,BlockUIContainer等所有wpf UI元素生成打印预览(长篇)。为了保持我的UI响应,我在一个单独的Thread类线程上做这个部分(BackgroundWorker将无法工作,因为我需要一个STA线程)。到目前为止,一切都很好 但是现在显示打印预览之后我需要打印,并且在生成的预览上单击“打印”图标会引发臭名昭着的“调用线程无法访问此对象,因为其他线程拥有它”。例外。那么,有什么办法吗?
编辑(代码):
Dispatcher.CurrentDispatcher.Invoke(new Action(() =>
{
Thread thread = new Thread(() =>
{
FixedDocument document = renderFlowDocumentTemplate(report);
PrintPreview preview = new PrintPreview();
preview.WindowState = WindowState.Normal;
preview.documentViewer.Document = document;
preview.ShowDialog();
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}));`
好的,RenderFlowDocumentTemplate()生成打印预览(包含UI元素)并用报表数据填充它们。 PrintPreview是一个自定义窗口,其中包含一个实际保存并显示预览的DocumentViewer元素,并包含打印图标,点击后我想要获取PrintDialog窗口。
编辑(XAML):
<cw:CustomWindow x:Class="MyApp.Reports.PrintPreview"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cw="clr-namespace:MyApp.UI.CustomWindows;assembly=MyApp.UI.CustomWindows">
<DocumentViewer Margin="0,30,0,0" Name="documentViewer"></DocumentViewer>
</cw:CustomWindow>`
答案 0 :(得分:1)
最简单的方法是。
Action a = () =>
{
//Code from another thread.
};
Dispatcher.BeginInvoke(a);
答案 1 :(得分:1)
前一段时间我把它绑起来了 - 我认为发现问题是printpreview对话框需要在主线程上。
答案 2 :(得分:1)
找到另一个有完全相同问题的人 - Printing the content of a DocumentViewer in a different UI thread。沿着同样的道路走。代码here是一个真正的救世主 现在我不是试图从Dispatcher线程访问辅助线程生成的UI元素,而是现在剩余的打印过程在辅助线程上执行。没有UI元素的跨线程“VerifyAccess”,并且它运行顺畅。 :)
答案 3 :(得分:1)
还有另一个“诡计”......
我经常遇到同样的问题。例如,我尝试将FrameworkElement
绑定到ContentPresenter
。我的解决方案是,我使用ItemsControl
代替ContentPresenter
,并将FrameworkElement
与ObservableCollection<FrameworkElement>
绑定在一个只有一个项目的{{1}}上。在此之后,没有问题
答案 4 :(得分:1)
我写过这个简单的片段,我对它没有任何经验,但我测试了一些东西,它似乎工作得很好。
/// <summary>
/// Creates UI element on a seperate thread and transfers it to
/// main UI thread.
///
/// Usage; if you have complex UI operation that takes a lot of time, such as XPS object creation.
/// </summary>
/// <param name="constructObject">Function that creates the necessary UIElement - will be executed on new thread</param>
/// <param name="constructionCompleted">Callback to the function that receives the constructed object.</param>
public void CreateElementOnSeperateThread(Func<UIElement> constructObject, Action<UIElement> constructionCompleted)
{
VerifyAccess();
// save dispatchers for future usage.
// we create new element on a seperate STA thread
// and then basically swap UIELEMENT's Dispatcher.
Dispatcher threadDispatcher = null;
var currentDispatcher = Dispatcher.CurrentDispatcher;
var ev = new AutoResetEvent(false);
var thread = new Thread(() =>
{
threadDispatcher = Dispatcher.CurrentDispatcher;
ev.Set();
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
ev.WaitOne();
threadDispatcher.BeginInvoke(new Action(() =>
{
var constructedObject = constructObject();
currentDispatcher.BeginInvoke(new Action(() =>
{
var fieldinfo = typeof (DispatcherObject).GetField("_dispatcher",
BindingFlags.NonPublic |
BindingFlags.Instance);
if (fieldinfo != null)
fieldinfo.SetValue(constructedObject, currentDispatcher);
constructionCompleted(constructedObject);
threadDispatcher.BeginInvokeShutdown(DispatcherPriority.Normal);
}), DispatcherPriority.Normal);
}), DispatcherPriority.Normal);
}
以下是用法:
CreateElementOnSeperateThread(() =>
{
// running on new temp dispatcher.
var loadsOfItems = new List<int>();
for(var i = 0; i < 100000; i++)
loadsOfItems.Add(i+12);
var dataGrid = new DataGrid {ItemsSource = loadsOfItems, Width = 500, Height = 500};
dataGrid.Measure(new Size(500, 500));
dataGrid.Arrange(new Rect(0, 0, 500, 500));
return dataGrid;
}, result => SampleGrid.Children.Add(result));
答案 5 :(得分:0)
在这种情况下使用调度程序类。 Dispatcher类具有invoke和beginInvoke方法。允许使用调度过程将请求发送到当前线程。 你需要做的是使用委托
创建一个如下所示的调用 App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
//you code goes here.
}));
开始调用调用以异步方式处理您的调用。