如何将WPF英寸单位转换为Winforms像素,反之亦然?
我有一个在WPF设计的窗口,我在WinForms所有者的中心使用它。 现在,我想移动所有者表单,此时我的WPF窗口也要移动到表单的中心!
但我有一个问题,只有当窗口位于屏幕中心形成的窗体的中心时。 否则以不同于Windows坐标的forms运行。 我只是将窗体的位移值添加到窗口位置。
现在我得出结论,WPF Windows上像素的坐标因WinForms而不同!
如何将WPF窗口位置转换为WinForms基本位置,反之亦然?
所有者表格代码是:
public partial class Form1 : Form { private WPF_Window.WPF win; public Form1() { InitializeComponent(); win = new WPF(); win.Show(); CenterToParent(win); } private void CenterToParent(System.Windows.Window win) { win.Left = this.Left + (this.Width - win.Width) / 2; win.Top = this.Top + (this.Height - win.Height) / 2; } protected override void OnMove(EventArgs e) { base.OnMove(e); CenterToParent(win); } }
在WPF中获取DPI值的最佳方法
方法1
这与您在Windows窗体中执行的操作相同。 System.Drawing.Graphics
对象提供了方便的属性来获取水平和垂直DPI。 让我们草拟一个辅助方法:
/// /// Transforms device independent units (1/96 of an inch) /// to pixels /// /// a device independent unit value X /// a device independent unit value Y /// returns the X value in pixels /// returns the Y value in pixels public void TransformToPixels(double unitX, double unitY, out int pixelX, out int pixelY) { using (Graphics g = Graphics.FromHwnd(IntPtr.Zero)) { pixelX = (int)((g.DpiX / 96) * unitX); pixelY = (int)((g.DpiY / 96) * unitY); } // alternative: // using (Graphics g = Graphics.FromHdc(IntPtr.Zero)) { } }
您可以使用它来转换坐标和大小值。 它非常简单和强大,完全在托管代码中(至少就消费者而言,至关重要)。 将IntPtr.Zero
作为HWND
或HDC
参数传递会IntPtr.Zero
一个Graphics
对象,该对象包装整个屏幕的设备上下文。
但是这种方法存在一个问题。 它依赖于Windows Forms / GDI +基础结构。 您将不得不添加对System.Drawing程序集的引用。 大不了? 不确定你,但对我来说这是一个需要避免的问题。
方法2
让我们更深入一步,以Win API的方式做到这一点。 GetDeviceCaps
函数检索指定设备的各种信息,并且当我们分别传递LOGPIXELSX
和LOGPIXELSY
参数时,它能够检索水平和垂直DPI。
GetDeviceCaps
函数在gdi32.dll
定义,可能是System.Drawing.Graphics
在gdi32.dll
使用的。
让我们来看看我们的助手变成了什么:
[DllImport("gdi32.dll")] public static extern int GetDeviceCaps(IntPtr hDc, int nIndex); [DllImport("user32.dll")] public static extern IntPtr GetDC(IntPtr hWnd); [DllImport("user32.dll")] public static extern int ReleaseDC(IntPtr hWnd, IntPtr hDc); public const int LOGPIXELSX = 88; public const int LOGPIXELSY = 90; /// /// Transforms device independent units (1/96 of an inch) /// to pixels /// /// a device independent unit value X /// a device independent unit value Y /// returns the X value in pixels /// returns the Y value in pixels public void TransformToPixels(double unitX, double unitY, out int pixelX, out int pixelY) { IntPtr hDc = GetDC(IntPtr.Zero); if (hDc != IntPtr.Zero) { int dpiX = GetDeviceCaps(hDc, LOGPIXELSX); int dpiY = GetDeviceCaps(hDc, LOGPIXELSY); ReleaseDC(IntPtr.Zero, hDc); pixelX = (int)(((double)dpiX / 96) * unitX); pixelY = (int)(((double)dpiY / 96) * unitY); } else throw new ArgumentNullException("Failed to get DC."); }
因此,我们已经在托管的GDI +上交换了依赖于花哨的Win API调用的依赖。 这是改善吗? 在我看来是的,只要我们在Windows上运行Win API就是最不常见的分母。 它很轻巧。 在其他平台上,我们可能不会首先遇到这种困境。
并且不要被ArgumentNullException
迷惑。 该解决方案与第一个解决方案一样强大。 System.Drawing.Graphics
也无法获取设备上下文,它将抛出同样的exception。
方法3
正如此处正式记录的那样,注册表中有一个特殊的键: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI.
它存储一个DWORD值,这正是用户在显示设置对话框中为DPI选择的值(在那里称为字体大小)。
阅读它是一个明智的选择,但我不推荐它。 您发现官方API和各种设置的存储之间存在差异。 API是一个公共合同,即使内部逻辑被完全重写,也保持不变(如果不是整个平台都很糟糕,不是吗?)。
但没有人保证内部存储将保持不变。 它可能持续了几十年,但描述其搬迁的关键设计文件可能已经等待批准。 你永远都不会知道。
始终坚持使用API(无论是原生的,Windows的forms,WPF等)。 即使底层代码从您知道的位置读取值。
方法4
这是一个非常优雅的WPF方法,我发现在这篇博客文章中有记录 。 它基于System.Windows.Media.CompositionTarget
类提供的function,该类最终表示绘制WPF应用程序的显示表面。 该类提供了两种有用的方法:
-
TransformFromDevice
-
TransformToDevice
这些名称是不言自明的,在这两种情况下,我们都得到一个System.Windows.Media.Matrix
对象,它包含设备单元(像素)和独立单元之间的映射系数。 M11将包含X轴的系数,M22将包含Y轴的系数。
由于我们一直在考虑单位 – >像素方向,所以让我们用CompositionTarget.TransformToDevice.
重写我们的帮助器CompositionTarget.TransformToDevice.
调用此方法时,M11和M22将包含我们计算的值:
- dpiX / 96
- dpiY / 96
因此,在DPI设置为120的机器上,系数将为1.25。
这是新助手:
/// /// Transforms device independent units (1/96 of an inch) /// to pixels /// /// a visual object /// a device independent unit value X /// a device independent unit value Y /// returns the X value in pixels /// returns the Y value in pixels public void TransformToPixels(Visual visual, double unitX, double unitY, out int pixelX, out int pixelY) { Matrix matrix; var source = PresentationSource.FromVisual(visual); if (source != null) { matrix = source.CompositionTarget.TransformToDevice; } else { using (var src = new HwndSource(new HwndSourceParameters())) { matrix = src.CompositionTarget.TransformToDevice; } } pixelX = (int)(matrix.M11 * unitX); pixelY = (int)(matrix.M22 * unitY); }
我不得不在方法中添加一个参数,即Visual
。 我们需要它作为计算的基础(之前的样本使用了整个屏幕的设备上下文)。 我不认为这是一个大问题,因为在运行WPF应用程序时,你很可能有一个Visual
(否则,你为什么需要转换像素坐标?)。 但是,如果您的视觉效果尚未附加到演示文稿源(即,尚未显示),则无法获取演示文稿源(因此,我们检查NULL并构建新的HwndSource
)。
参考