PresentationSource 是 WPF 中一個非常重要但通常不直接使用的基礎設施類。它代表了 WPF 可視化樹與顯示設備之間的連接點,是 WPF 渲染體系的核心組成部分。
核心概念
PresentationSource 是一個抽象類,它充當了 WPF 可視化樹與顯示技術(如 Hwnd 窗口、打印機等)之間的適配器。它的主要作用是:
- 連接可視化樹與顯示設備:將 WPF 的
Visual樹連接到實際的顯示錶面 - 座標轉換:提供在 WPF 座標和設備像素座標之間的轉換
- 消息路由:處理輸入消息(鼠標、鍵盤)的路由
- 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));
}
// 這裏可以將渲染結果導出到自定義目標
// 比如生成位圖、發送到網絡等
}
}
重要注意事項
- 生命週期管理:
PresentationSource需要正確釋放資源 - 空值檢查:
PresentationSource.FromVisual()可能返回 null - 線程安全:大多數操作必須在創建它的線程上執行
- 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 對於高級主題如自定義控件、互操作、高性能渲染等至關重要。
本文章為轉載內容,我們尊重原作者對文章享有的著作權。如有內容錯誤或侵權問題,歡迎原作者聯繫我們進行內容更正或刪除文章。