【问题标题】:How to Convert a WPF inch unit to Winforms pixels and vice versa?如何将 WPF 英寸单位转换为 Winforms 像素,反之亦然?
【发布时间】:2015-05-24 15:07:40
【问题描述】:

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

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

现在我已经得出结论,WPF窗口上的像素坐标因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 窗口定位在 WinForms 表单的中心并保持在那里?
  • WPF 中的度量单位,以英寸为单位,而不是像素。谷歌“wpf 将英寸转换为像素”以获得明显的点击率。

标签: c# .net wpf winforms pixel


【解决方案1】:

Best way to get DPI value in WPF

方法一

这与您在 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 对象包装整个屏幕的设备上下文。

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


方法二

让我们更深入一步,用 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 调用的依赖。这是一种改进吗?在我看来是的,只要我们在 Windows 上运行,Win API 是最小的共同点。它很轻。在其他平台上,我们可能一开始就不会遇到这种困境。

不要被 ArgumentNullException 愚弄。该解决方案与第一个解决方案一样强大。如果System.Drawing.Graphics 也无法获取设备上下文,它也会抛出同样的异常。


方法3

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

阅读它很容易,但我不推荐它。您会看到官方 API 和各种设置的存储之间存在差异。 API 是一个公共合约,即使内部逻辑被完全重写(如果不是整个平台都很糟糕,不是吗?)。

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

始终坚持使用 API(无论是本机、Windows 窗体、WPF 等)。即使底层代码从您知道的位置读取值。


方法四

这是我在 this blog post 中发现的一种非常优雅的 WPF 方法。它基于System.Windows.Media.CompositionTarget 类提供的功能,该类最终表示在其上绘制 WPF 应用程序的显示表面。该类提供了 2 个有用的方法:

  • 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 )。

Reference

【讨论】:

  • 从哪里获得视觉效果?我有窗口。
  • @AlanBaljeu 如果你使用 WPF,你可以通过这个方法从 Visual 获取 DPI:VisualTreeHelper.GetDpi(visual ?? System.Windows.Application.Current.MainWindow)
【解决方案2】:

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

formVal = Me.LogicalToDeviceUnits(WPFval)

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多