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

Beh*_*zad 5 .net c# wpf pixel winforms

我有一个在WPF设计的窗口,我在WinForms所有者的中心使用它.现在,我想移动所有者表单,此时我的WPF窗口也要移动到表单的中心!

但我有一个问题,只有当窗口位于屏幕中心形成的窗体的中心时.否则以不同于Windows坐标的形式运行.我只是将窗体的位移值添加到窗口位置.

现在我得出结论,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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Beh*_*zad 10

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

方法1

这与您在Windows窗体中执行的操作相同.System.Drawing.Graphicsobject提供方便的属性来获得水平和垂直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)) { }
}
Run Code Online (Sandbox Code Playgroud)

您可以使用它来转换坐标和大小值.它非常简单和强大,完全在托管代码中(至少就消费者而言,至关重要).传递IntPtr.Zeroas HWNDHDC参数会导致Graphics包装整个屏幕的设备上下文的对象.

但是这种方法存在一个问题.它依赖于Windows Forms/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.");
}
Run Code Online (Sandbox Code Playgroud)

因此,我们已经在托管的GDI +上交换了对依赖于花哨的Win API调用的依赖.这是改善吗?在我看来是的,只要我们在Windows上运行Win 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);
}
Run Code Online (Sandbox Code Playgroud)

我不得不在方法中添加一个参数,Visual.我们需要它作为计算的基础(之前的样本使用了整个屏幕的设备上下文).我不认为这是一个大问题,因为Visual在运行WPF应用程序时你很可能有一个问题(否则,为什么你需要转换像素坐标?).但是,如果您的视觉效果尚未附加到演示文稿源(即,尚未显示),则无法获取演示文稿源(因此,我们检查NULL并构造新的HwndSource).

参考