WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验。如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程。然而,就不能让同一个窗口内部使用多个 UI 线程吗?
答案其实是——可以的!使用 VisualTarget
即可。
阅读本文将收获一份对 VisualTarget
的解读以及一份我封装好的跨线程 UI 控件 DispatcherContainer.cs。
几个必备的组件
微软给 VisualTarget
提供的注释是:
提供跨线程边界将一个可视化树连接到另一个可视化树的功能。
注释中说 VisualTarget
就是用来连接可视化树(VisualTree
)的,而且可以跨线程边界。也就是说,这是一个专门用来使同一个窗口内部包含多个不同 UI 线程的类型。
所以,我们的目标是使用 VisualTarget
显示跨线程边界的 UI。
VisualTarget
本身继承自 CompositionTarget
,而不是 Visual
;其本身并不是可视化树的一部分。但是它的构造函数中可以传入一个 HostVisual
对象,这个对象是一个 Visual
,如果将此 HostVisual
放入原 UI 线程的可视化树上,那么 VisualTarget
就与主 UI 线程连接起来了。
另外一半,VisualTarget
需要连接另一个异步线程的可视化树。然而,VisualTarget
提供了 RootVisual
属性,直接给此属性赋一个后台 UI 控件作为其值,即连接了另一个 UI 线程的可视化树。
总结起来,其实我们只需要 new
一个 VisualTarget
的新实例,构造函数传入一个 UI 线程的可视化树中的 HostVisual
实例,RootVisual
属性设置为另一个 UI 线程中的控件,即可完成跨线程可视化树的连接。
事实上经过尝试,我们真的只需要这样做就可以让另一个线程上的 UI 呈现到当前的窗口上,同一个窗口。读者可以自行编写测试代码验证这一点,我并不打算在这里贴上试验代码,因为后面会给出完整可用的全部代码。
完善基本功能
虽说 VisualTarget
的基本使用已经可以显示一个跨线程的 UI 了,但是其实功能还是欠缺的。
一个典型的情况是,后台线程的这部分 UI 没有