WPF 同一窗口内的多线程 UI(VisualTarget)
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 没有连接到 PresentationSource
;而 Visual.PointFromScreen
、Visual.PointFromScreen
这样的方法明确需要连接到 PresentationSource
才行。参见这里:In WPF, under what circumstances does Visual.PointFromScreen throw InvalidOperationException? - Stack Overflow。
可是,应该如何将 RootVisual
连接到 PresentationSource
呢?我从 Microsoft.DwayneNeed 项目中找到了方法。这是源码地址:Microsoft.DwayneNeed - Home。
做法是重写属性和方法:
public override Visual RootVisual
{
get => _visualTarget.RootVisual;
set
{
// 此处省略大量代码。
}
}
protected override CompositionTarget GetCompositionTargetCore()
{
return _visualTarget;
}
Microsoft.DwayneNeed
中有 VisualTargetPresentationSource
的完整代码,我自己只为这个类添加了 IDisposable
接口,用于 Dispose
掉 VisualTarget
的实例。我需要这么做是因为我即将提供可修改后台 UI 线程控件的方法。
让方法变得好用
为了让整个多线程 UI 线程的使用行云流水,我准备写一个 DispatcherContainer
类来优化多线程 UI 的使用体验。期望的使用方法是给这个控件的实例设置 Child
属性,这个 Child
是后台线程创建的 UI。然后一切线程同步相关的工作全部交给此类来完成。
在我整理后,使用此控件只需如此简单:
<Grid Background="#FFEEEEEE">
<local:DispatcherContainer x:Name="Host"/>
</Grid>
await Host.SetChildAsync<MyUserControl>();
其中,MyUserControl
是控件的类型,可以是你写的某个 XAML 用户控件,也可以是其他任何控件类型。用这个方法创建的控件,直接就是后台 UI 线程的。
当然,如果你需要自己控制初始化逻辑,可以使用委托创建控件。
await Host.SetChildAsync(() =>
{
var box = new TextBox
{
Text = "吕毅 - walterlv",
Margin = new Thickness(16),
};
return box;
});
下图即是用以上代码创建的后台线程文本框。
甚至,你已经有线程的后台 UI 控件了,或者你希望自己来创建后台的 UI 控件,则可以这样:
// 创建一个后台线程的 Dispatcher。
// 其中,UIDispatcher 是我自己封装的方法,在 GitHub 上以 MIT 协议开源。
// https://github.com/walterlv/sharing-demo/blob/master/src/Walterlv.Demo.WPF/Utils/Threading/UIDispatcher.cs
var dispatcher = await UIDispatcher.RunNewAsync("walterlv's testing thread");
// 使用这个后台线程的 Dispatcher 创建一个自己的用户控件。
var control = await dispatcher.InvokeAsync(() => new MyUserControl());
// 将这个用户控件放入封装好的 DispatcherContainer 中。
// DispatcherContainer 是我自己封装的方法,在 GitHub 上以 MIT 协议开源。
// https://github.com/walterlv/sharing-demo/blob/master/src/Walterlv.Demo.WPF/Utils/Threading/DispatcherContainer.cs
await Host.SetChildAsync(control);
注意到我们自己创建的控件已经运行在后台线程中了:
完整的代码
以下所有代码均可点击进入 GitHub 查看。
核心的代码包含两个类:
- VisualTargetPresentationSource 这是实现异步 UI 的关键核心,用于连接两个跨线程边界的可视化树,并同时提供连接到
PresentationSource
的功能。(由于我对 PresentationSource 的了解有限,此类绝大多数代码都直接来源于 Microsoft.DwayneNeed - Home。) - DispatcherContainer 当使用我封装好的多线程 UI 方案时(其实就是把这几个类自己带走啦),这个类才是大家编程开发中主要面向的 API 类啊!
其他辅助型代码:
- UIDispatcher 这并不是重点,此类型只是为了方便地创建后台
Dispatcher
。 - DispatcherAsyncOperation 此类型只是为了让
UIDispatcher
中的方法更好写一些。 - AwaiterInterfaces 这是一组可有可无的接口;给
DispatcherAsyncOperation
继承的接口,但是不继承也没事,一样能跑。
这些辅助型代码的含义可以查看我的另一篇博客:如何实现一个可以用 await 异步等待的 Awaiter - walterlv的专栏 - CSDN博客。
参考资料
- WPF Round Table Part 2: Multi UI Threaded Control - //InterKnowlogy/ Blogs
- Multithreaded UI: HostVisual – Presentation Source
- Microsoft.DwayneNeed - Home
WPF 同一窗口内的多线程 UI(VisualTarget)的更多相关文章
- WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)
原文 WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口) WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验.如果希望做不同线程 ...
- WPF 多线程 UI:设计一个异步加载 UI 的容器
对于 WPF 程序,如果你有某一个 UI 控件非常复杂,很有可能会卡住主 UI,给用户软件很卡的感受.但如果此时能有一个加载动画,那么就不会感受到那么卡顿了.UI 的卡住不同于 IO 操作或者密集的 ...
- WPF 支持的多线程 UI 并不是线程安全的
原文:WPF 支持的多线程 UI 并不是线程安全的 版权声明:本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可.欢迎转载.使用.重新发布,但务必保留文章署名吕毅(包含链 ...
- WPF多线程UI更新——两种方法
WPF多线程UI更新——两种方法 前言 在WPF中,在使用多线程在后台进行计算限制的异步操作的时候,如果在后台线程中对UI进行了修改,则会出现一个错误:(调用线程无法访问此对象,因为另一个线程拥有该对 ...
- WPF多线程UI更新
前言 在WPF中,在使用多线程在后台进行计算限制的异步操作的时候,如果在后台线程中对UI进行了修改,则会出现一个错误:(调用线程无法访问此对象,因为另一个线程拥有该对象.)这是很常见的一个错误,一不小 ...
- 【Unity】8.1 Unity内置的UI控件
分类:Unity.C#.VS2015 创建日期:2016-04-27 一.简介 Unity 5.x内置了-套完整的GUI系统,提供了从布局.控件到皮肤的-整套GUI解决方案,因此可直接利用它做出各种风 ...
- 2000条你应知的WPF小姿势 基础篇<74-77 WPF 多窗口Tips>
在正文开始之前需要介绍一个人:Sean Sexton. 来自明尼苏达双城的软件工程师.最为出色的是他维护了两个博客:2,000ThingsYou Should Know About C# 和 2,00 ...
- WPF自定义窗口基类
WPF自定义窗口基类时,窗口基类只定义.cs文件,xaml文件不定义.继承自定义窗口的类xaml文件的根节点就不再是<Window>,而是自定义窗口类名(若自定义窗口与继承者不在同一个命名 ...
- Qt 子窗口内嵌到父窗口中(无边框附体show即可)good
有时需要把一个子窗口内嵌进入父窗口当中. 我们可以这样做 1.新建一个QWidget 或者QDialog的子类 ClassA(父类为ClassB) 2.在新建类的构造函数中添加设置窗口属性 setWi ...
随机推荐
- bootstrap系统学习
1.响应式中注意的内容: 一行(row)必须在.container中. col-xs- col-sm- col-md- col-lg- 列偏移 .col-md-offset-* 列排序 .col-md ...
- 学习 nginx (持续更新)
什么是代理与反向代理,有什么应用场景? 平常经常听别人说代理与反向代理,那么这二者到底有什么区别呢? 代理 场景:我需要访问一个服务器C,但是由于某些原因我无法访问到它,(典型的就是你FQ,然后fai ...
- 二十八 Python分布式爬虫打造搜索引擎Scrapy精讲—cookie禁用、自动限速、自定义spider的settings,对抗反爬机制
cookie禁用 就是在Scrapy的配置文件settings.py里禁用掉cookie禁用,可以防止被通过cookie禁用识别到是爬虫,注意,只适用于不需要登录的网页,cookie禁用后是无法登录的 ...
- PyCharm在win10的64位系统安装实例
搭建环境 1.win10_X64,其他Win版本也可以. 2.PyCharm版本:Professional-2016.2.3. 搭建准备 1.到PyCharm官网下载PyCharm安装包. 2.选择W ...
- BOM-event事件
添加事件监听 <button id="btnShoot">shoot</button><br> <button id="btnA ...
- C# 设计模式巩固 - 简单工厂模式
前言 设计模式的文章很多.鄙人不才文笔也不咋地.写这篇只为巩固下基础知识,万一不小心帮到了您,是我莫大的荣幸!写的不好欢迎码友指正,废话结束开始进入正题. 介绍 - 简单工厂模式 官方定义:(尴尬~貌 ...
- LeetCode OJ:Swap Nodes in Pairs(成对交换节点)
Given a linked list, swap every two adjacent nodes and return its head. For example,Given 1->2-&g ...
- PHP错误Parse error: syntax error, unexpected end of file in test.php on line 12解决方法
出现这个错误的原因就是语法错误,肯定是PHP程序的书写不规范造成,PHP语句标识符错了,没有在php.ini中开启短标签!八成是这个原因,啊啊啊! 今天在写PHP程序的时候总是出现这样的错误:Pars ...
- win10 安装MongoDB
win10上面安装mongodb的时候,注意不要勾选上Install MongoDB Compass, 否则会退出报错!!!! mongodb的安装 我是在E盘建立的一个mongodb文件夹,用来安装 ...
- ThinkPHP5.0完全开发手册.【CHM】下载
ThinkPHP5已经出来很长时间了,官网也没有提供CHM格式的手册下载只有PDF格式的,我根据官网的在线手册制作了一个离线版的ThinkPHP5.0完全开发手册.CHM格式的文档.