深入解析:【笔记】PresentationSource详细介绍

PresentationSource 是 WPF 中一个非常重要但通常不直接使用的基础设施类。它代表了 WPF 可视化树与显示设备之间的连接点,是 WPF 渲染体系的核心组成部分。


核心概念

PresentationSource 是一个抽象类,它充当了 WPF 可视化树与显示技术(如 Hwnd 窗口、打印机等)之间的适配器。它的主要作用是:

  1. 连接可视化树与显示设备:将 WPF 的 Visual 树连接到实际的显示表面
  2. 坐标转换:提供在 WPF 坐标和设备像素坐标之间的转换
  3. 消息路由:处理输入消息(鼠标、键盘)的路由
  4. DPI 感知:处理不同 DPI 设置下的缩放问题

主要派生类

1. HwndSource

这是最常用和最重要的派生类,它将 WPF 内容托管在 Win32 窗口(HWND)中:

// 创建 HwndSource 来托管 WPF 内容
HwndSourceParameters parameters = new HwndSourceParameters("My Window");
parameters.Width = 800;
parameters.Height = 600;
HwndSource hwndSource = new HwndSource(parameters);
hwndSource.RootVisual = myWpfContent; // 设置 WPF 可视化树的根
// 现在 hwndSource 就是一个 PresentationSource

2.其他派生类

  • HwndSource:用于窗口和 Win32 互操作
  • 其他特定场景的源(如打印时)

关键属性和方法

常用属性

// 获取与 Visual 关联的 PresentationSource
PresentationSource source = PresentationSource.FromVisual(myVisual);
if (source != null)
{
// 获取 CompositionTarget(渲染目标)
CompositionTarget compositionTarget = source.CompositionTarget;
// 获取根 Visual
Visual rootVisual = source.RootVisual;
// 检查是否已释放
bool isDisposed = source.IsDisposed;
}

重要方法

// 静态方法:从 Visual 获取 PresentationSource
PresentationSource.FromVisual(visual);
// 静态方法:从依赖对象获取 PresentationSource
PresentationSource.FromDependencyObject(dependencyObject);
// 坐标转换方法
Point wpfPoint = new Point(100, 100);
Point devicePoint = source.CompositionTarget.TransformToDevice.Transform(wpfPoint);
Point backToWpf = source.CompositionTarget.TransformFromDevice.Transform(devicePoint);

实际应用场景

场景1:获取正确的 DPI 缩放信息

public static class DpiHelper
{
public static double GetDpiScale(Visual visual)
{
PresentationSource source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget != null)
{
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
return transformToDevice.M11; // 水平方向的 DPI 缩放因子
}
return 1.0; // 默认值
}
public static Size GetDeviceIndependentSize(Visual visual, Size deviceSize)
{
PresentationSource source = PresentationSource.FromVisual(visual);
if (source?.CompositionTarget != null)
{
Matrix transformFromDevice = source.CompositionTarget.TransformFromDevice;
return transformFromDevice.Transform(deviceSize);
}
return deviceSize;
}
}
// 使用示例
public partial class MainWindow : Window
{
protected override void OnRender(DrawingContext drawingContext)
{
double dpiScale = DpiHelper.GetDpiScale(this);
// 根据 DPI 缩放调整绘制
Pen scaledPen = new Pen(Brushes.Black, 1 / dpiScale);
drawingContext.DrawRectangle(Brushes.AliceBlue, scaledPen, new Rect(0, 0, 100, 100));
}
}

场景2:Win32 互操作 - 创建 WPF 内容的 HWND

public class WpfHwndHost : HwndHost
{
private HwndSource _hwndSource;
private readonly Visual _wpfContent;
public WpfHwndHost(Visual wpfContent)
{
_wpfContent = wpfContent;
}
protected override HandleRef BuildWindowCore(HandleRef hwndParent)
{
// 创建 HwndSourceParameters
HwndSourceParameters parameters = new HwndSourceParameters("WpfInHwndHost");
parameters.WindowStyle = WS.VISIBLE | WS.CHILD;
parameters.ParentWindow = hwndParent.Handle;
parameters.Width = (int)ActualWidth;
parameters.Height = (int)ActualHeight;
// 创建 HwndSource
_hwndSource = new HwndSource(parameters);
_hwndSource.RootVisual = _wpfContent;
return new HandleRef(this, _hwndSource.Handle);
}
protected override void DestroyWindowCore(HandleRef hwnd)
{
_hwndSource?.Dispose();
}
}
// 在 WPF 窗口中使用
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
// 创建要嵌入的 WPF 内容
Border wpfContent = new Border
{
Background = Brushes.LightBlue,
Child = new TextBlock { Text = "WPF in HwndHost", FontSize = 20 }
};
// 创建 HwndHost 并添加到 WPF
WpfHwndHost host = new WpfHwndHost(wpfContent);
host.Height = 200;
host.Width = 300;
myCanvas.Children.Add(host);
}
}

场景3:多显示器 DPI 感知应用

public class MultiMonitorDpiAwareWindow : Window
{
public MultiMonitorDpiAwareWindow()
{
// 监听 PresentationSource 变化
this.SourceInitialized += OnSourceInitialized;
}
private void OnSourceInitialized(object sender, EventArgs e)
{
PresentationSource source = PresentationSource.FromVisual(this);
if (source != null)
{
// 监听 DPI 变化
source.ContentRendered += OnContentRendered;
}
}
private void OnContentRendered(object sender, EventArgs e)
{
UpdateDpiAwareContent();
}
private void UpdateDpiAwareContent()
{
PresentationSource source = PresentationSource.FromVisual(this);
if (source?.CompositionTarget == null) return;
Matrix dpiTransform = source.CompositionTarget.TransformToDevice;
double dpiScaleX = dpiTransform.M11;
double dpiScaleY = dpiTransform.M22;
// 根据 DPI 缩放更新 UI
this.Title = $"DPI Scale: {dpiScaleX:F2}x{dpiScaleY:F2}";
// 调整字体大小等
myTextBlock.FontSize = 12 * dpiScaleX;
}
protected override void OnLocationChanged(EventArgs e)
{
base.OnLocationChanged(e);
// 窗口移动到不同 DPI 的显示器时更新
UpdateDpiAwareContent();
}
}

场景4:输入消息处理

public class CustomInputHandler
{
private readonly HwndSource _hwndSource;
public CustomInputHandler(Window window)
{
_hwndSource = PresentationSource.FromVisual(window) as HwndSource;
if (_hwndSource != null)
{
// 添加钩子处理原始 Win32 消息
_hwndSource.AddHook(WndProc);
}
}
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
const int WM_MOUSEMOVE = 0x0200;
const int WM_KEYDOWN = 0x0100;
switch (msg)
{
case WM_MOUSEMOVE:
// 自定义鼠标移动处理
handled = HandleMouseMove(lParam);
break;
case WM_KEYDOWN:
// 自定义键盘处理
handled = HandleKeyDown(wParam);
break;
}
return IntPtr.Zero;
}
private bool HandleMouseMove(IntPtr lParam)
{
// 解析鼠标坐标
int x = (short)(lParam.ToInt32() & 0xFFFF);
int y = (short)((lParam.ToInt32() >> 16) & 0xFFFF);
// 自定义处理逻辑
return false; // 返回 true 表示已处理,阻止进一步传递
}
private bool HandleKeyDown(IntPtr wParam)
{
// 处理按键
return false;
}
}

高级应用:自定义渲染目标

public class CustomPresentationSource : PresentationSource
{
private readonly Visual _rootVisual;
public CustomPresentationSource(Visual rootVisual)
{
_rootVisual = rootVisual;
base.RootVisual = rootVisual;
}
protected override CompositionTarget GetCompositionTargetCore()
{
return new CustomCompositionTarget();
}
public override bool IsDisposed => false;
private class CustomCompositionTarget : CompositionTarget
{
public override Matrix TransformToDevice => Matrix.Identity;
public override Matrix TransformFromDevice => Matrix.Identity;
}
}
// 使用自定义 PresentationSource
public class CustomRenderer
{
private readonly CustomPresentationSource _source;
private readonly DrawingVisual _rootVisual;
public CustomRenderer()
{
_rootVisual = new DrawingVisual();
_source = new CustomPresentationSource(_rootVisual);
}
public void RenderToCustomTarget()
{
using (DrawingContext dc = _rootVisual.RenderOpen())
{
// 执行绘制操作
dc.DrawRectangle(Brushes.White, null, new Rect(0, 0, 100, 100));
}
// 这里可以将渲染结果导出到自定义目标
// 比如生成位图、发送到网络等
}
}

重要注意事项

  1. 生命周期管理PresentationSource 需要正确释放资源
  2. 空值检查PresentationSource.FromVisual() 可能返回 null
  3. 线程安全:大多数操作必须在创建它的线程上执行
  4. DPI 感知:正确处理不同 DPI 场景
// 安全的用法示例
public static void SafePresentationSourceUsage(Visual visual)
{
// 总是在 UI 线程上调用
Dispatcher.VerifyAccess();
PresentationSource source = PresentationSource.FromVisual(visual);
if (source == null || source.IsDisposed)
return;
try
{
// 使用 source...
var transform = source.CompositionTarget.TransformToDevice;
}
catch (InvalidOperationException)
{
// 处理已释放的情况
}
}

总结

PresentationSource 是 WPF 架构中的关键基础设施,主要用于:

  • Win32 互操作:通过 HwndSource 将 WPF 内容嵌入到原生窗口中
  • DPI 处理:正确获取和响应不同显示器的 DPI 设置
  • 坐标转换:在 WPF 坐标和设备像素坐标之间转换
  • 低级输入处理:通过消息钩子处理原始输入消息
  • 自定义渲染:创建非标准显示目标

虽然日常 WPF 开发中很少直接使用,但理解 PresentationSource 对于高级主题如自定义控件、互操作、高性能渲染等至关重要。

了解更多

PresentationSource

System.Windows.Controls 命名空间 | Microsoft Learn

控件库 - WPF .NET Framework | Microsoft Learn

WPF 介绍 | Microsoft Learn

使用 Visual Studio 创建新应用教程 - WPF .NET | Microsoft Learn

https://github.com/HeBianGu

HeBianGu的个人空间-HeBianGu个人主页-哔哩哔哩视频

GitHub - HeBianGu/WPF-Control: WPF轻量控件和皮肤库

GitHub - HeBianGu/WPF-ControlBase: Wpf封装的自定义控件资源库

posted on 2025-12-27 08:15  ljbguanli  阅读(6)  评论(0)    收藏  举报