大数跨境
0
0

WPF如何页面内嵌窗口

WPF如何页面内嵌窗口 dotNET跨平台
2026-01-03
18
导读:简介什么是XAML什么是句柄(IntPtr)HwndHost是什么什么是空域什么是XAMLXAML 是 eX
简介
  • 什么是XAML
  • 什么是句柄(IntPtr)
  • HwndHost是什么

  • 什么是空域

什么是XAML

XAML 是 eXtensible Application Markup Language 的缩写,中文常称为可扩展应用程序标记语言。它是微软为 .NET 平台(特别是 WPF、UWP、WinUI、Xamarin.Forms/Maui 等)创建的一种声明式标记语言

什么是句柄(IntPtr)

句柄 是 Handle 的中文翻译,是 Windows 操作系统中一个极其重要的核心概念。你可以把它理解为操作系统资源的"身份证号"或"引用凭证",简单的理解你可以理解为每个窗口都是一个句柄,开发者不需要知道资源在内存中的具体位置,只需要通过句柄操作即可。

HwndHost是什么

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,实现BuildWindowCoreDestroyWindowCore

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, 0000, 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);         }

效果图


注意


本文章目的是给初步了解学习WPF相关人员参考,并未涉及到非常底层的句柄以及内存相关的原理讲解,为快速集成窗体项目做准备。

【声明】内容源于网络
0
0
dotNET跨平台
专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
内容 1014
粉丝 0
dotNET跨平台 专注于.NET Core的技术传播。在这里你可以谈微软.NET,Mono的跨平台开发技术。在这里可以让你的.NET项目有新的思路,不局限于微软的技术栈,横跨Windows,
总阅读17.4k
粉丝0
内容1.0k