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 對於高級主題如自定義控件、互操作、高性能渲染等至關重要。