Passing Wpf Objects Between Threads (With Source Code)

本文探讨了在WPF应用程序中解决跨线程传递UI组件的问题。通过使用自定义线程并结合Dispatcher进行数据交换,最终采用XAML读写方式成功实现FixedDocument对象的线程间传递。
Passing Wpf Objects Between Threads (With Source Code)

When working on yaTImer's new report engine I got myself into a bit of a problem, I'm blogging about it because I couldn't find an answer on the web and I can't believe I'm the only one with this problem, so I hope someone will find my solution helpful (or maybe suggest a better one).

My reports engine generates a FixedDocument that I can print or show to the user, because the report can contain a lot of information generating the document can potentially take some time, so I went for the easy solution and dropped a BackgroundWorker into the code.

So this:

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _documentViewer.Document = GenerateDocument();
}

Becomes this:

// WARNING: THIS CODE DOESN'T WORK
private BackgroundWorker _backgroundWorker;
private FixedDocument _result;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundWorker = new BackgroundWorker();
   _backgroundWorker.DoWork += DoGenerateDocument;
   _backgroundWorker.RunWorkerCompleted += FinishedGenerating;
   _backgroundWorker.RunWorkerAsync();
}

void DoGenerateDocument(object sender, DoWorkEventArgs e)
{
   _result = GenerateDocument(); // this line throws an exception
}

void FinishedGenerating(object sender, RunWorkerCompletedEventArgs e)
{
   _documentViewer.Document = _result;
}

While the code was very elegant it doesn't work, I got an exception with a very nice error message: " The calling thread must be STA, because many UI components require this."

Fortunately the error message is very clear and it's easy to find the solution on the web, you can't use Wpf objects from a thread-pool thread (or from a BackgroundWorker) you have to create your own thread, the code to create the thread is very simple:

Thread _backgroundThread;
_backgroundThread = new Thread(DoGenerateDocument);
_backgroundThread.SetApartmentState(ApartmentState.STA);
_backgroundThread.Start();

Now because we no longer have BackgroundWorker events we have to write our own code to pass data between threads, this is easy to do with the Dispacher class

// WARNING: THIS CODE DOESN'T WORK
private Thread _backgroundThread;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundThread = new Thread(DoGenerateDocument);
   _backgroundThread.SetApartmentState(ApartmentState.STA);
   _backgroundThread.Start();
}

void DoGenerateDocument()
{
   FixedDocument result = GenerateDocument();
   Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action<FixedDocument> )FinishedGenerating,
      result);
}

void FinishedGenerating(FixedDocument result)
{
   _documentViewer.Document = result; // now this line throws an exception
}
This code is written inside a Wpf window, and the Window class has a Dispacher property that returns the dispatcher for the thread that created the window, if you're code doesn't use a window then you need to get the dispatcher associated with the thread you want to communicate with, this is done by reading Dispacher.CurrentDispacher from that thread.

But this code also doesn't work, when I pass the FixedDocument into the document viewer I get this lovely exception: "The calling thread cannot access this object because a different thread owns it."

This is where things get complicated, I couldn't find any way to marshal the document into the UI thread.

Since this is a fixed document the natural thing to do is to save it into an XPS file (XPS is the Wpf version of PDF) and then read it from the other thread, I've tried doing this with a MemoryStream (I don't want to create an actual file) and I discovered a problem with this approach – apparently you can't read an XPS from a memory stream, you need an actual file.

At that point I got the clever idea of using XAML, you can read XAML from a memory stream and the code to read and write XAML is actually simpler then the XPS code, here is the final code using XamlReader and XamlWriter:

private Thread _backgroundThread;

void Window_Loaded(object sender, RoutedEventArgs e)
{
   _backgroundThread = new Thread(DoGenerateDocument);
   _backgroundThread.SetApartmentState(ApartmentState.STA);
   _backgroundThread.Start();
}

void DoGenerateDocument()
{
   FixedDocument result = GenerateDocument();
   MemoryStream stream = new MemoryStream();
   XamlWriter.Save(result, stream);
   Dispatcher.BeginInvoke(
      DispatcherPriority.Normal,
      (Action<MemoryStream> )FinishedGenerating,
      stream);
}

void FinishedGenerating(MemoryStream stream)
{
   stream.Seek(0, SeekOrigin.Begin);
   FixedDocument result = (FixedDocument)XamlReader.Load(stream);
   _documentViewer.Document = result;
}

This trick should use with any Wpf object, not just FixedDocument.

### 如何编译源代码 在 Linux 系统中,通常通过 `make` 工具来完成从源码到可执行程序的构建过程。以下是关于如何编译源代码的关键点: #### 编译环境准备 确保开发环境中已安装必要的工具链,例如 GCC 或 Clang 编译器以及 `make` 工具。可以通过以下命令检查是否存在这些工具: ```bash gcc --version make --version ``` 如果未安装,则可以使用包管理器进行安装。例如,在基于 Debian 的系统上运行: ```bash sudo apt-get update && sudo apt-get install build-essential ``` #### 配置阶段 许多项目提供了一个名为 `configure` 的脚本用于初始化构建环境并检测系统的配置需求。此脚本会生成适合当前平台的 `Makefile` 文件。运行方式如下: ```bash ./configure ``` 该命令可能接受多种参数来自定义安装路径或其他特性设置。 #### 构建阶段 一旦有了合适的 `Makefile` 文件,就可以调用 `make` 来启动实际的编译流程。默认情况下,它会读取当前目录中的 `Makefile` 并按照指定规则依次处理各个目标文件[^1]。 为了加速大型项目的编译速度,还可以利用多核处理器的优势,比如指定 `-j8` 参数让其同时开启八个线程工作: ```bash make -j8 ``` #### 安装阶段 当所有二进制文件被成功创建之后(一般位于特定子目录如 `bin`, `lib` 中),最后一步就是把这些产物部署至最终位置以便全局访问。这通常是通过执行下面这条指令实现的: ```bash sudo make install ``` 注意这里往往需要用到超级用户的权限因为标准库路径属于受保护区域[^1]。 #### 清理旧版本 假如之前已经存在相同名称的应用实例或者想彻底移除先前的手动装配成果再重新开始新的尝试时,如果没有现成的 `uninstall` 规则可用的话,一种通用做法是从头记录下每次安放的具体地点然后手动删除它们;当然也有更简便的办法即借助专门设计为此目的服务的小型脚本来自动化这一繁琐操作[^2]。 ```python import os def clean_installed_files(file_list_path): with open(file_list_path, 'r') as f: files = f.readlines() for file in files: try: os.remove(file.strip()) print(f"Removed {file.strip()}") except Exception as e: print(e) # Example usage would involve passing the path to a log of installed items. clean_installed_files('/path/to/install.log') ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值