wpf使用設備無關單位,一開始的理解是無論DPI怎么變,顯示的窗口大小是一樣的,實際效果窗口會隨着系統DPI縮放(哪里錯了?請大佬明示);wpf設備無關單位轉換成物理單位是與DPI無關的,下面給出一個Dpi結構:
public struct Dpi { public Dpi (Double x, Double y) { DpiX = x; DpiY = y; Px2WpfX = 96 / DpiX; Px2WpfY = 96 / DpiY; } public Double DpiX { get; } public Double DpiY { get; } public Double Px2WpfX { get; } public Double Px2WpfY { get; } /// <summary> /// 英寸-厘米 /// </summary> public static readonly Double In2Cm = 2.54; /// <summary> /// 英寸-磅 /// </summary> public static readonly Double In2Pt = 72; /// <summary> /// 厘米-wpf /// </summary> public static readonly Double Cm2Wpf = 96 / 2.54; }
可以看到,wpf設備無關單位轉厘米的比例是固定的,繼而又可以轉換成英寸、磅等,只有換算成像素才需要DPI;下面說一下如何獲取DPI。
.net core提供了VisualTreeHelper.GetDpi(Visual visual)方法,返回DpiScale結構,PixelsPerInchX表示1英寸像素數(即DPI),DpiScaleX表示1個wpf設備無關單位可以轉換成多少像素;並且Window、Image等對象還提供了DpiChanged事件,非常方便根據DPI變化調整大小;
.net framework 4.8以下沒有上述便利得方法;獲取dpi的方法參考https://blog.csdn.net/Iron_Ye/article/details/83053393,里面說了多種獲取DPI的方法,並且向我們推薦使用CompositionTarget方式,經測試(本人Win10),改變系統顯示比例150%后,除Win32API外,其他方法獲取的DPI與顯示比列為100%無異,而Win32API獲取的DPI與.net core使用VisualTreeHelper.GetDpi()一樣,所以得出結論,若要獲取准確的DPI,推薦使用Win32API,每次傳入窗口句柄或Intptr.Zero,我的猜想是這樣的(沒有去驗證,我只有一塊屏幕),傳入窗口句柄則獲取窗口句柄所在屏幕的DPI,傳入Intptr.Zero則相當於傳入空指針,即獲取主屏幕DPI;另外CompositionTarget需要注意一下,PresentationSource.FromVisual(visual)傳入DrawingVisual會返回null,需要傳入DrawingVisual所在的邏輯樹結構對象(一般是Canvas);與Dpi結構配合,這里給出DpiHelper類示范:
public sealed class DpiHelper { #region Graphics public static Dpi GetDpiByGraphics (IntPtr hWnd) { using (var graphics = Graphics.FromHwnd (hWnd)) { return new Dpi (graphics.DpiX, graphics.DpiY); } } #endregion #region CompositionTarget public static Dpi GetDpiFromVisual (Visual visual) { var source = PresentationSource.FromVisual (visual); return (source == null || source.CompositionTarget == null) ? GetDpiByWin32 (IntPtr.Zero) : new Dpi (96.0 * source.CompositionTarget.TransformToDevice.M11, 96.0 * source.CompositionTarget.TransformToDevice.M22); } #endregion #region Win32 API private const Int32 LOGPIXELSX = 88; private const Int32 LOGPIXELSY = 90; [DllImport ("gdi32.dll")] private static extern Int32 GetDeviceCaps (IntPtr hdc, Int32 index); [DllImport ("user32.dll")] private static extern IntPtr GetDC (IntPtr hWnd); [DllImport ("user32.dll")] private static extern Int32 ReleaseDC (IntPtr hWnd, IntPtr hDc); public static Dpi GetDpiByWin32 (IntPtr hwnd) { var hDc = GetDC (hwnd); var dpiX = GetDeviceCaps (hDc, LOGPIXELSX); var dpiY = GetDeviceCaps (hDc, LOGPIXELSY); ReleaseDC (hwnd, hDc); return new Dpi (dpiX, dpiY); } #endregion }