-
什么是XAML -
什么是句柄(IntPtr) HwndHost是什么
什么是空域
XAML 是 eXtensible Application Markup Language 的缩写,中文常称为可扩展应用程序标记语言。它是微软为 .NET 平台(特别是 WPF、UWP、WinUI、Xamarin.Forms/Maui 等)创建的一种声明式标记语言
句柄 是 Handle 的中文翻译,是 Windows 操作系统中一个极其重要的核心概念。你可以把它理解为操作系统资源的"身份证号"或"引用凭证",简单的理解你可以理解为每个窗口都是一个句柄,开发者不需要知道资源在内存中的具体位置,只需要通过句柄操作即可。
HwndHost是 WPF 中用于托管 Win32 控件的基类。它本质上是一个 "窗口包装器",让传统的 Win32 控件能够在 WPF 应用程序中运行。
空域问题是 WPF 中嵌入 Win32 控件时的核心限制。简单说就是 "WPF 和 Win32 控件不能在同一区域共存"。用人话说就是目标控件与HwndHost在同一层级,用户控件位于HwndHost之上,理论上是控件会覆盖HwndHost,但是实际上不是,HwndHost会悬浮在控件之上,这种问题简称空域,造成这种问题的根本原因是技术实现不同。
WPF:使用 DirectX,支持 GPU 加速、矢量图形、透明度、3D 变换
Win32:使用 GDI/GDI+,基于像素、无硬件加速、不支持透明度
具体表现现象
遮挡问题
<Grid><!-- Win32 控件 --><local:HwndHostControl Width="200" Height="200"/><!-- 这个按钮会被挖掉一半! --><Button Content="我在上面"Width="100" Height="30"Canvas.Left="150" Canvas.Top="150"/></Grid>
透明无效
<Grid><!-- 设置透明背景没用! --><local:HwndHostControl Background="Transparent"><!-- Win32 控件不支持 WPF 透明度 --></local:HwndHostControl><!-- 这个控件显示不出来 --><Border Background="Red"/></Grid>
创建自定义控件,继承HwndHost,实现BuildWindowCore、DestroyWindowCore
public class HwndHostControl : HwndHost{protected override HandleRef BuildWindowCore(HandleRef hwndParent){throw new NotImplementedException();}protected override void DestroyWindowCore(HandleRef hwnd){throw new NotImplementedException();}}
创建Win32API控制句柄
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]private static extern IntPtr CreateWindowExW(uint dwExStyle,[MarshalAs(UnmanagedType.LPWStr)] string lpClassName,[MarshalAs(UnmanagedType.LPWStr)] string lpWindowName,uint dwStyle,int x,int y,int nWidth,int nHeight,IntPtr hWndParent,IntPtr hMenu,IntPtr hInstance,[MarshalAs(UnmanagedType.AsAny)] object pvParam);/// <summary>/// 设置窗口的父窗口(改变所有权与 Z 序关系)。返回旧父窗口句柄。/// </summary>[DllImport("user32.dll", SetLastError = true)]private static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);/// <summary>/// 销毁窗口,释放系统资源。销毁后句柄变为无效。/// </summary>[DllImport("user32.dll", SetLastError = true)][return: MarshalAs(UnmanagedType.Bool)]private static extern bool DestroyWindow(IntPtr hwnd);/// <summary>/// 移动并调整窗口大小,可选是否重绘(bRepaint)。/// </summary>[DllImport("user32.dll", SetLastError = true)]private static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int nWidth, int nHeight, bool bRepaint);
获取窗口句柄(本教程以记事本为例子)
Process.GetProcessesByName("notepad").First().MainWindowHandle
创建子窗口句柄
var _parentHwnd = CreateWindowExW(0x00000020u, "static", null, 0x40000000u | 0x10000000u, 0, 0, 0, 0, hwndParent.Handle, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
将获取的目标句柄填充到子窗口句柄上
SetParent(_childHwnd, _parentHwnd);
动态绘制句柄窗口大小
protected override void OnRender(DrawingContext drawingContext){base.OnRender(drawingContext);UpdateWindowPos();MoveWindow(_childHwnd, 0, (int)(0), (int)ActualWidth, (int)ActualHeight, true);}
释放句柄
protected override void DestroyWindowCore(HandleRef hwnd){DestroyWindow(hwnd.Handle);}
效果图
注意

