5

我需要使用诸如 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>`
4

6 回答 6

1

There is also another "trick"...

Often I run into the same problems. For example I try bind a FrameworkElement to an ContentPresenter. My solution for this, I use a ItemsControl in place of the ContentPresenter and bind my single FrameworkElement over an ObservableCollection<FrameworkElement> with only the one item. After this, no problems

于 2012-10-26T15:17:00.473 回答
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));
于 2013-06-06T13:53:24.730 回答
1

发现另一个人有完全相同的问题 -在不同的 UI 线程中打印 DocumentViewer 的内容。只是走了同样的路。这里的代码是一个真正的救星。
现在我不尝试从 Dispatcher 线程访问辅助线程生成的 UI 元素,而是现在在辅助线程上执行其余的打印过程。UI元素没有跨线程的“VerifyAccess”,运行流畅。:)

于 2012-07-09T16:45:05.403 回答
1

我前段时间绑定了这个 - 我认为问题在于打印预览对话框需要在主线程上。

于 2012-07-06T11:19:18.883 回答
1

最简单的方法是。

Action a = () =>
{
    //Code from another thread.
};
Dispatcher.BeginInvoke(a);
于 2012-07-06T08:47:04.987 回答
0

在这种情况下使用调度程序类。Dispatcher 类具有 invoke 和 beginInvoke 方法。它允许使用调度过程将请求发送到当前线程。您需要做的是使用委托创建如下调用

  App.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
//you code goes here.
 }));

开始调用调用异步处理您的调用。

于 2012-07-06T09:01:37.567 回答