【问题标题】:How to access separate thread generated WPF UI elements from the Dispatcher thread?如何从 Dispatcher 线程访问单独的线程生成的 WPF UI 元素?
【发布时间】:2012-07-06 16:51:48
【问题描述】:

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

【问题讨论】:

  • 请分享您的代码以了解问题。

标签: wpf multithreading dispatcher print-preview


【解决方案1】:

最简单的方法是。

Action a = () =>
{
    //Code from another thread.
};
Dispatcher.BeginInvoke(a);

【讨论】:

  • 您能否详细说明一下。它仍然是“调用线程无法访问此对象,因为不同的线程拥有它。”
  • 找到引发异常的确切行并将其放入Action 方法中。在需要的地方使用 UI 中的Dispatcher。否则,您能否发布一些代码,以便我们更好地理解问题?
【解决方案2】:

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

【讨论】:

  • 对我来说似乎如此。我可以将一些非 UI 代码从其他线程编组到主线程。但是如何用 UI 元素做同样的事情呢?
  • 我认为您必须在该线程上创建它们 - 除非在渲染时设置了线程引用,但我认为它已经在构建中。如果您的 renderFlowDocumentTemplate 正在创建一些没有引用其他线程的 uielements,您可以调用它,那么它位于正确的线程上 - 如果您没有得到任何输出,您可以调用 measure 并在 uielement 上安排以强制重绘。
【解决方案3】:

找到另一个有完全相同问题的人 - Printing the content of a DocumentViewer in a different UI thread。只是走了同样的路。代码here 是真正的救星。
现在我不尝试从 Dispatcher 线程访问辅助线程生成的 UI 元素,而是现在在辅助线程上执行其余的打印过程。 UI元素没有跨线程的“VerifyAccess”,运行流畅。 :)

【讨论】:

    【解决方案4】:

    还有一个“绝招”……

    我经常遇到同样的问题。例如,我尝试将FrameworkElement 绑定到ContentPresenter。我的解决方案是,我使用ItemsControl 代替ContentPresenter,并将我的单个FrameworkElement 绑定到ObservableCollection&lt;FrameworkElement&gt; 上,仅使用一项。之后就没有问题了

    【讨论】:

      【解决方案5】:

      我写过这个简单的sn-p,我没有任何经验,但我测试了一些东西,它似乎工作正常。

      /// <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));
      

      【讨论】:

      • 我不完全确定,但你为什么要问?据我所知,冻结对象本质上已经是跨线程的。
      • 我打算在一个线程(渲染目标)中进行一些渲染,然后在主线程中使用它们。所以我想知道图像源的上下文更改是否存在问题。
      【解决方案6】:

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

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

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

      【讨论】:

      • 替代方法可能是使用您的打印特定功能创建委托和函数,并从您的代码中调用该委托。这会奏效。如果你可以分享你的代码,如果不是更好。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-09-17
      • 1970-01-01
      • 1970-01-01
      • 2021-11-21
      • 2015-06-02
      相关资源
      最近更新 更多