如何将WPF英寸单位转换为Winforms像素,反之亦然

本文关键字:Winforms 像素 反之亦然 转换 单位 WPF | 更新日期: 2023-09-27 17:50:44

我有一个在WPF设计的窗口,我在WinForms所有者的中心使用了它。现在,我想移动所有者表单,现在我的WPF窗口也必须移动到表单的中心!

但我有一个问题,只有当窗口在窗体的中心时,窗体才在屏幕的中心。以及以与Windows坐标不同的形式进行操作。我只是将窗体的位移值添加到窗口位置。

现在我得出的结论是,WinForms在WPF窗口上的像素坐标是不同的!

如何将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英寸单位转换为Winforms像素,反之亦然

在WPF中获取DPI值的最佳方式

方法1

这与您在Windows窗体中执行此操作的方式相同。System.Drawing.Graphics对象提供了方便的属性来获取水平和垂直DPI。让我们草拟一个辅助方法:

/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
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参数传递会产生一个Graphics对象,该对象封装了整个屏幕的设备上下文。

不过,这种方法有一个问题。它依赖于WindowsForms/GDI+基础结构。您将不得不添加对System.Drawing部件的引用。了不起的事不确定你的情况,但对我来说,这是一个需要避免的问题。


方法2

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

GetDeviceCaps函数是在gdi32.dll中定义的,可能是System.Drawing.Graphics在后台使用的函数。

让我们看看我们的助手变成了什么:

[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;
/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
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调用的依赖性。这是一个进步吗?在我看来是的,只要我们在WindowsWinneneneba API上运行是最不常见的。它很轻。在其他平台上,我们可能一开始就不会陷入这种困境。

不要被ArgumentNullException愚弄了。此解决方案与第一个解决方案一样稳健。如果System.Drawing.Graphics也不能获得设备上下文,它将抛出相同的异常。


方法3

正如官方记录的此处,注册表中有一个特殊键:HKLM'SOFTWARE'Microsoft'Windows NT'CurrentVersion'FontDPI.它存储一个DWORD值,该值正是用户在显示设置对话框中为DPI选择的值(在此处称为字体大小(。

阅读它是一件麻烦的事,但我不推荐它。你可以看到,官方API和各种设置的存储之间有区别。API是一个公共契约,即使内部逻辑被完全重写,它也保持不变(如果不是,整个平台都很糟糕,不是吗?(。

但没有人保证内部存储会保持不变。它可能已经持续了几十年,但一份描述其搬迁的关键设计文件可能已经在等待批准。你永远不会知道。

始终坚持使用API(不管它是什么,本机,Windows窗体,WPF等(。即使底层代码从您知道的位置读取值。


方法4

这是一种非常优雅的WPF方法,我在这篇博客文章中发现了它。它基于System.Windows.Media.CompositionTarget类提供的功能,最终表示绘制WPF应用程序的显示表面。该类提供了两种有用的方法:

  • TransformFromDevice
  • TransformToDevice

这些名称不言自明,在这两种情况下,我们都得到一个System.Windows.Media.Matrix对象,该对象包含设备单元(像素(和独立单元之间的映射系数。M11将包含X轴的系数,M22–包含Y轴的系数。

到目前为止,我们一直在考虑单位->像素方向,让我们用CompositionTarget.TransformToDevice.重新编写我们的助手。当调用此方法时,M11和M22将包含我们计算为的值:

  • dpiX/96
  • dpiY/96

因此,在DPI设置为120的机器上,系数将为1.25。

这是新的助手:

/// <summary>
/// Transforms device independent units (1/96 of an inch)
/// to pixels
/// </summary>
/// <param name="visual">a visual object</param>
/// <param name="unitX">a device independent unit value X</param>
/// <param name="unitY">a device independent unit value Y</param>
/// <param name="pixelX">returns the X value in pixels</param>
/// <param name="pixelY">returns the Y value in pixels</param>
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(。

参考

我刚刚发现并测试了这个(在VB中(:

formVal = Me.LogicalToDeviceUnits(WPFval)

formVal和WPFval可以是整数或大小。