如何将 WPF 英寸单位转换为 Winforms 像素,反之亦然?
How to Convert a WPF inch unit to Winforms pixels and vice versa?
我有一个 window 是 WPF and I used that on center of a WinForms 所有者设计的。
现在,我想移动所有者表单,目前我的 WPF window 也必须移动到表单的中心!
但是我有一个问题,只有当 window 位于屏幕中心的表单中心时。并且以与 Windows 坐标不同的形式行事。
我只是将表格的位移值添加到 window 位置。
现在我得出结论,WPF Windows 上的像素坐标与 WinForms 不同!
如何将 WPF window 位置转换为 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);
}
}
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
作为 HWND
或 HDC
参数传递会导致 Graphics
对象包装整个屏幕的设备上下文。
不过这种方法有一个问题。它依赖于 Windows Forms/GDI+ 基础设施。您将必须添加对 System.Drawing 程序集的引用。大不了?不确定你,但对我来说这是一个要避免的问题。
方法二
让我们更进一步,以 Win API 的方式进行。 GetDeviceCaps
函数检索指定设备的各种信息,当我们分别向它传递 LOGPIXELSX
和 LOGPIXELSY
参数时,能够检索水平和垂直 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 赢 API 就是最小公分母。它很轻。在其他平台上,我们可能一开始就不会遇到这种困境。
不要被那个 ArgumentNullException
愚弄了。该解决方案与第一个解决方案一样稳健。 System.Drawing.Graphics
如果它也无法获取设备上下文,也会抛出同样的异常。
方法三
正如官方记录的那样 here 注册表中有一个特殊的键:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI.
它存储一个 DWORD 值,这正是用户选择的值显示设置对话框中的 DPI(那里称为字体大小)。
阅读它很简单,但我不推荐它。你看官方的API和各种设置的存储是有区别的。 API 是一个 public 合约,即使内部逻辑被完全重写也保持不变(如果不是整个平台很糟糕,不是吗?)。
但是没有人保证内部存储会保持不变。它可能已经持续了几十年,但描述其搬迁的重要设计文件可能已经在等待批准。你永远不知道。
始终坚持 API(无论是什么,原生的,Windows 表单,WPF 等)。即使底层代码从你知道的位置读取值。
方法四
这是一种非常优雅的 WPF 方法,我在 this blog post 中找到了它。它基于 System.Windows.Media.CompositionTarget
class 提供的功能,最终表示绘制 WPF 应用程序的显示表面。 class 提供了 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
).
我刚刚发现并测试了这个(在 VB 中):
formVal = Me.LogicalToDeviceUnits(WPFval)
formVal 和 WPFval 可以是整数或大小。
我有一个 window 是 WPF and I used that on center of a WinForms 所有者设计的。 现在,我想移动所有者表单,目前我的 WPF window 也必须移动到表单的中心!
但是我有一个问题,只有当 window 位于屏幕中心的表单中心时。并且以与 Windows 坐标不同的形式行事。 我只是将表格的位移值添加到 window 位置。
现在我得出结论,WPF Windows 上的像素坐标与 WinForms 不同!
如何将 WPF window 位置转换为 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);
}
}
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
作为 HWND
或 HDC
参数传递会导致 Graphics
对象包装整个屏幕的设备上下文。
不过这种方法有一个问题。它依赖于 Windows Forms/GDI+ 基础设施。您将必须添加对 System.Drawing 程序集的引用。大不了?不确定你,但对我来说这是一个要避免的问题。
方法二
让我们更进一步,以 Win API 的方式进行。 GetDeviceCaps
函数检索指定设备的各种信息,当我们分别向它传递 LOGPIXELSX
和 LOGPIXELSY
参数时,能够检索水平和垂直 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 赢 API 就是最小公分母。它很轻。在其他平台上,我们可能一开始就不会遇到这种困境。
不要被那个 ArgumentNullException
愚弄了。该解决方案与第一个解决方案一样稳健。 System.Drawing.Graphics
如果它也无法获取设备上下文,也会抛出同样的异常。
方法三
正如官方记录的那样 here 注册表中有一个特殊的键:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI.
它存储一个 DWORD 值,这正是用户选择的值显示设置对话框中的 DPI(那里称为字体大小)。
阅读它很简单,但我不推荐它。你看官方的API和各种设置的存储是有区别的。 API 是一个 public 合约,即使内部逻辑被完全重写也保持不变(如果不是整个平台很糟糕,不是吗?)。
但是没有人保证内部存储会保持不变。它可能已经持续了几十年,但描述其搬迁的重要设计文件可能已经在等待批准。你永远不知道。
始终坚持 API(无论是什么,原生的,Windows 表单,WPF 等)。即使底层代码从你知道的位置读取值。
方法四
这是一种非常优雅的 WPF 方法,我在 this blog post 中找到了它。它基于 System.Windows.Media.CompositionTarget
class 提供的功能,最终表示绘制 WPF 应用程序的显示表面。 class 提供了 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
).
我刚刚发现并测试了这个(在 VB 中):
formVal = Me.LogicalToDeviceUnits(WPFval)
formVal 和 WPFval 可以是整数或大小。