WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)

简介: 原文 WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口) WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验。如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程。

原文 WPF 同一窗口内的多线程/多进程 UI(使用 SetParent 嵌入另一个窗口)

WPF 的 UI 逻辑只在同一个线程中,这是学习 WPF 开发中大家几乎都会学习到的经验。如果希望做不同线程的 UI,大家也会想到使用另一个窗口来实现,让每个窗口拥有自己的 UI 线程。然而,就不能让同一个窗口内部使用多个 UI 线程吗?

阅读本文将收获一份 Win32 函数 SetParent 及相关函数的使用方法。


WPF 同一个窗口中跨线程访问 UI 有多种方法:

前者使用的是 WPF 原生方式,做出来的跨线程 UI 可以和原来的 UI 相互重叠遮挡。后者使用的是 Win32 的方式,实际效果非常类似 WindowsFormsHost,新线程中的 UI 在原来的所有 WPF 控件上面遮挡。另外,后者不止可以是跨线程,还可以跨进程。

准备必要的 Win32 函数

完成基本功能所需的 Win32 函数是非常少的,只有 SetParent 和 MoveWindow

[DllImport("user32.dll")]
public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent); [DllImport("user32.dll", SetLastError = true)] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); 

SetParent 用于指定传统的窗口父子关系。有多传统呢?呃……就是 Windows 自诞生以来的那种传统。在传统的 Win32 应用程序中,每一个控件都有自己的窗口句柄,它们之间通过 SetParent 进行连接;可以说一个 Button 就是一个窗口。而我们现在使用 SetParent 其实就是在使用传统 Win32 程序中的控件的机制。

MoveWindow 用于指定窗口相对于其父级的位置,我们使用这个函数来决定新嵌入的窗口在原来界面中的位置。

启动后台 UI 线程

启动一个后台的 WPF UI 线程网上有不少线程的方法,但大体思路是一样的。我之前在 如何实现一个可以用 await 异步等待的 Awaiter 一文中写了一个利用 async/await 做的更高级的版本。

为了继续本文,我将上文中的核心文件抽出来做成了 GitHubGist,访问 Custom awaiter with background UI thread 下载那三个文件并放入到自己的项目中。

  • AwaiterInterfaces.cs 为实现 async/await 机制准备的一些接口,虽然事实上可以不需要,不过加上可以防逗比。
  • DispatcherAsyncOperation.cs 这是我自己实现的自定义 awaiter,可以利用 awaiter 的回调函数机制规避线程同步锁的使用。
  • UIDispatcher.cs 用于创建后台 UI 线程的类型,这个文件包含本文需要使用的核心类,使用到了上面两个文件。

在使用了上面的三个文件的情况下,创建一个后台 UI 线程并获得用于执行代码的 Dispatcher 只需要一句话:

// 传入的参数是线程的名称,也可以不用传。
var dispatcher = await UIDispatcher.RunNewAsync("Background UI"); 

在得到了后台 UI 线程 Dispatcher 的情况下,无论做什么后台线程的 UI 操作,只需要调用 dispatcher.InvokeAsync 即可。

我们使用下面的句子创建一个后台线程的窗口并显示出来:

var backgroundWindow = await dispatcher.InvokeAsync(() => { var window = new Window(); window.SourceInitialized += OnSourceInitialized; window.Show(); return window; }); 

在代码中,我们监听了 SourceInitialized 事件。这是 WPF 窗口刚刚获得 Windows 窗口句柄的时机,在此事件中,我们可以最早地拿到窗口句柄以便进行 Win32 函数调用。

private void OnSourceInitialized(object sender, EventArgs e) { // 在这里可以获取到窗口句柄。 } 

嵌入窗口

为了比较容易写出嵌入窗口的代码,我将核心部分代码贴出来:

class ParentWindow : Window
{ public ParentWindow() { InitializeComponent(); Loaded += OnLoaded; } private async void OnLoaded(object sender, RoutedEventArgs e) { // 获取父窗口的窗口句柄。 var hwnd = (HwndSource) PresentationSource.FromVisual(this); _parentHwnd = hwnd; // 在后台线程创建子窗口。 var dispatcher = await UIDispatcher.RunNewAsync("Background UI"); await dispatcher.InvokeAsync(() => { var window = new Window(); window.SourceInitialized += OnSourceInitialized; window.Show(); }); } private void OnSourceInitialized(object sender, EventArgs e) { var childHandle = new WindowInteropHelper((Window) sender).Handle; SetParent(childHandle, _parentHwnd.Handle); MoveWindow(childHandle, 0, 0, 300, 300, true); } private HwndSource _parentHwnd; [DllImport("user32.dll")] public static extern bool SetParent(IntPtr hWnd, IntPtr hWndNewParent); [DllImport("user32.dll", SetLastError = true)] public static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint); } 

具体执行嵌入窗口的是这一段:

private void OnSourceInitialized(object sender, EventArgs e) { var childHandle = new WindowInteropHelper((Window) sender).Handle; SetParent(childHandle, _parentHwnd.Handle); MoveWindow(childHandle, 0, 0, 300, 300, true); } 

最终显示时会将后台线程的子窗口显示到父窗口的 (0, 0, 300, 300) 的位置和大小。可以试试在主线程写一个 Thread.Sleep(5000),在卡顿的事件内,你依然可以拖动子窗口的标题栏进行拖拽。

嵌入了后台线程的窗口

当然,如果你认为外面那一圈窗口的非客户区太丑了,使用普通设置窗口属性的方法去掉即可:

await dispatcher.InvokeAsync(() => { var window = new Window { BorderBrush = Brushes.DodgerBlue, BorderThickness = new Thickness(8), Background = Brushes.Teal, WindowStyle = WindowStyle.None, ResizeMode = ResizeMode.NoResize, Content = new TextBlock { Text = "walterlv.github.io", HorizontalAlignment = HorizontalAlignment.Center, VerticalAlignment = VerticalAlignment.Center, Foreground = Brushes.White, FontSize = 24, } }; window.SourceInitialized += OnSourceInitialized; window.Show(); }); 

本文会经常更新,请阅读原文: https://walterlv.com/post/embed-win32-window-using-csharp.html,以避免陈旧错误知识的误导,同时有更好的阅读体验。

目录
相关文章
|
24天前
|
调度 开发者 Python
深入浅出操作系统:进程与线程的奥秘
在数字世界的底层,操作系统扮演着不可或缺的角色。它如同一位高效的管家,协调和控制着计算机硬件与软件资源。本文将拨开迷雾,深入探索操作系统中两个核心概念——进程与线程。我们将从它们的诞生谈起,逐步剖析它们的本质、区别以及如何影响我们日常使用的应用程序性能。通过简单的比喻,我们将理解这些看似抽象的概念,并学会如何在编程实践中高效利用进程与线程。准备好跟随我一起,揭开操作系统的神秘面纱,让我们的代码运行得更加流畅吧!
|
2月前
|
消息中间件 并行计算 安全
进程、线程、协程
【10月更文挑战第16天】进程、线程和协程是计算机程序执行的三种基本形式。进程是操作系统资源分配和调度的基本单位,具有独立的内存空间,稳定性高但资源消耗大。线程是进程内的执行单元,共享内存,轻量级且并发性好,但同步复杂。协程是用户态的轻量级调度单位,适用于高并发和IO密集型任务,资源消耗最小,但不支持多核并行。
58 1
|
24天前
|
消息中间件 Unix Linux
【C语言】进程和线程详解
在现代操作系统中,进程和线程是实现并发执行的两种主要方式。理解它们的区别和各自的应用场景对于编写高效的并发程序至关重要。
49 6
|
25天前
|
调度 开发者
深入理解:进程与线程的本质差异
在操作系统和计算机编程领域,进程和线程是两个核心概念。它们在程序执行和资源管理中扮演着至关重要的角色。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
52 5
|
22天前
|
算法 调度 开发者
深入理解操作系统:进程与线程的管理
在数字世界的复杂编织中,操作系统如同一位精明的指挥家,协调着每一个音符的奏响。本篇文章将带领读者穿越操作系统的幕后,探索进程与线程管理的奥秘。从进程的诞生到线程的舞蹈,我们将一起见证这场微观世界的华丽变奏。通过深入浅出的解释和生动的比喻,本文旨在揭示操作系统如何高效地处理多任务,确保系统的稳定性和效率。让我们一起跟随代码的步伐,走进操作系统的内心世界。
|
24天前
|
调度 开发者
核心概念解析:进程与线程的对比分析
在操作系统和计算机编程领域,进程和线程是两个基本而核心的概念。它们是程序执行和资源管理的基础,但它们之间存在显著的差异。本文将深入探讨进程与线程的区别,并分析它们在现代软件开发中的应用和重要性。
45 4
|
1月前
|
并行计算 数据处理 调度
Python中的并发编程:探索多线程与多进程的奥秘####
本文深入探讨了Python中并发编程的两种主要方式——多线程与多进程,通过对比分析它们的工作原理、适用场景及性能差异,揭示了在不同应用需求下如何合理选择并发模型。文章首先简述了并发编程的基本概念,随后详细阐述了Python中多线程与多进程的实现机制,包括GIL(全局解释器锁)对多线程的影响以及多进程的独立内存空间特性。最后,通过实例演示了如何在Python项目中有效利用多线程和多进程提升程序性能。 ####
|
1月前
|
Linux 调度 C语言
深入理解操作系统:进程和线程的管理
【10月更文挑战第32天】本文旨在通过浅显易懂的语言和实际代码示例,带领读者探索操作系统中进程与线程的奥秘。我们将从基础知识出发,逐步深入到它们在操作系统中的实现和管理机制,最终通过实践加深对这一核心概念的理解。无论你是编程新手还是希望复习相关知识的资深开发者,这篇文章都将为你提供有价值的见解。
|
1月前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
29 1
|
1月前
深入理解操作系统:进程与线程的管理
【10月更文挑战第30天】操作系统是计算机系统的核心,它负责管理计算机硬件资源,为应用程序提供基础服务。本文将深入探讨操作系统中进程和线程的概念、区别以及它们在资源管理中的作用。通过本文的学习,读者将能够更好地理解操作系统的工作原理,并掌握进程和线程的管理技巧。
45 2

相关实验场景

更多