如何将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作为HWNDHDC参数传递会IntPtr.Zero一个Graphics对象,该对象包装整个屏幕的设备上下文。

但是这种方法存在一个问题。 它依赖于Windows Forms / GDI +基础结构。 您将不得不添加对System.Drawing程序集的引用。 大不了? 不确定你,但对我来说这是一个需要避免的问题。


方法2

让我们更深入一步,以Win API的方式做到这一点。 GetDeviceCaps函数检索指定设备的各种信息,并且当我们分别传递LOGPIXELSXLOGPIXELSY参数时,它能够检索水平和垂直DPI。

GetDeviceCaps函数在gdi32.dll定义,可能是System.Drawing.Graphicsgdi32.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 )。

参考