如何将WPF英寸单位转换为Winforms像素,反之亦然? [英] How to Convert a WPF inch unit to Winforms pixels and vice versa?
问题描述
我有一个设计为 WPF ,然后在 WinForms 所有者. 现在,我要移动所有者表单,此刻,我的
I have a window that designed at WPF and I used that on center of a WinForms owner. Now, I want to move the owner form and at the moment my WPF window has also to be moved in the center of the form!
但是我有一个问题,仅当窗口位于屏幕中央的窗体的中央时.否则,其行为方式将与Windows坐标不同. 我只是将表单的位移值添加到窗口位置.
But I have a problem, Only when window is in the center of the form that form in the center of the screen. And otherwise act in a different form than the Windows coordinates. I just add the displacement values of the form to window location.
现在我得出的结论是WinForms在WPF Windows上的像素坐标是不同的!
Now I have come to the conclusion that the coordinates of the pixels on WPF Windows are different by WinForms!
如何转换 WPF 窗口
How to convert WPF window location to WinForms base location and vice versa ?
所有者表格代码为:
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.让我们来勾画一个辅助方法:
Best way to get DPI value in WPF
Method 1
It’s the same way you did that in Windows Forms. System.Drawing.Graphics
object provides convenient properties to get horizontal and vertical DPI. Let’s sketch up a helper method:
/// <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
对象,该对象包装整个屏幕的设备上下文.
You can use it transforms both coordinates as well as size values. It’s pretty simple and robust and completely in managed code (at least as far as you, the consumer, is concerned). Passing IntPtr.Zero
as HWND
or HDC
parameter results in a Graphics
object that wraps a device context of the entire screen.
这种方法有一个问题.它依赖于Windows Forms/GDI +基础结构.您将必须添加对System.Drawing程序集的引用.大不了不确定您的身份,但是对我来说,这是一个需要避免的问题.
There is one problem with this approach though. It has a dependency on Windows Forms/GDI+ infrastructure. You are going to have to add a reference to System.Drawing assembly. Big deal? Not sure about you, but for me this is an issue to avoid.
让我们更进一步,以Win API的方式来做. GetDeviceCaps
函数可检索指定设备的各种信息,并在分别传递LOGPIXELSX
和LOGPIXELSY
参数时能够检索水平和垂直DPI.
Let’s take it one step deeper and do it the Win API way. GetDeviceCaps
function retrieves various information for the specified device and is able to retrieve horizontal and vertical DPI’s when we pass it LOGPIXELSX
and LOGPIXELSY
parameters respectively.
GetDeviceCaps
函数是在gdi32.dll
中定义的,可能是System.Drawing.Graphics
在后台使用的功能.
GetDeviceCaps
function is defined in gdi32.dll
and is probably what System.Drawing.Graphics
uses under the hood.
让我们看看我们的助手已经变成了:
Let’s have a look at what our helper has become:
[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上运行是最不常见的.它是轻量级的.在其他平台上,我们首先可能不会遇到这个难题.
So we have exchanged a dependency on managed GDI+ for the dependency on fancy Win API calls. Is that an improvement? In my opinion yes, as long as we run on Windows Win API is a least common denominator. It is lightweight. On other platforms we wouldn’t probably have this dilemma in the first place.
也不要被那个ArgumentNullException
所迷惑.该解决方案与第一个解决方案一样强大.如果System.Drawing.Graphics
也无法获取设备上下文,则会抛出相同的异常.
And don’t get fooled by that ArgumentNullException
. This solution is as robust as the first one. System.Drawing.Graphics
will throw this same exception if it can’t obtain a device context too.
如正式记录在 此处 是注册表中的特殊键:HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI.
它存储一个DWORD值,该值正是用户在显示设置对话框中为DPI选择的值(在此处称为字体大小).
As officially documented here there is a special key in the registry: HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontDPI.
It stores a DWORD value which is exactly what the user chooses for DPI in the display settings dialog (it’s called a font size there).
阅读它很容易,但我不建议这样做.您会看到官方API和用于各种设置的存储之间存在差异.该API是一项公共合同,即使内部逻辑已被完全重写(如果整个平台不烂,不是吗?),它也保持不变.
Reading it is a no-brainer but I wouldn’t recommend it. You see there is a difference between an official API and a storage for various settings. The API is a public contract that stays the same even if the internal logic is totally rewritten (If it doesn’t the whole platform sucks, doesn’t it?).
但是没有人保证内部存储将保持不变.它可能已经使用了几十年,但是描述其重新布置的重要设计文档可能已经在等待批准.你永远不会知道.
But nobody guarantees that the internal storage will remain the same. It may have lasted for a couple of decades but a crucial design document that describes its relocation may already be pending an approval. You never know.
始终坚持使用API(无论它是什么,本机,Windows窗体,WPF等).即使基础代码从您知道的位置读取了值.
Always stick to API (whatever it is, native, Windows Forms, WPF, etc). Even if the underlying code reads the value from the location you know.
This is a pretty elegant WPF approach that I’ve found documented in this blog post. It is based on the functionality provided by System.Windows.Media.CompositionTarget
class that ultimately represents the display surface on which the WPF application is drawn. The class provides 2 useful methods:
-
TransformFromDevice
-
TransformToDevice
TransformFromDevice
TransformToDevice
名称是不言而喻的,在两种情况下,我们都得到一个System.Windows.Media.Matrix
对象,该对象包含设备单位(像素)和独立单位之间的映射系数. M11将包含X轴系数和M22 – Y轴系数.
The names are self-explanatory and in both cases we get a System.Windows.Media.Matrix
object that contains the mapping coefficients between device units (pixels) and independent units. M11 will contain a coefficient for the X axis and M22 – for the Y axis.
到目前为止,由于我们一直在考虑units-> pixels方向,因此请使用CompositionTarget.TransformToDevice.
重新编写我们的助手.调用此方法时,M11和M22将包含我们计算为的值:
As we have been considering a units->pixels direction so far let’s re-write our helper with CompositionTarget.TransformToDevice.
When calling this method M11 and M22 will contain values that we calculated as:
- dpiX/96
- dpiY/96
因此,在DPI设置为120的机器上,系数为1.25.
So on a machine with DPI set to 120 the coefficients will be 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
).
I had to add one more parameter to the method, the Visual
. We need it as a base for calculations (previous samples used the device context of the entire screen for that). I don’t think it’s a big issue as you are more than likely to have a Visual
at hand when running your WPF application (otherwise, why would you need to translate pixel coordinates?). However, if your visual hasn't been attached to a presentation source (that is, it hasn't been shown yet) you can't get the presentation source (thus, we have a check for NULL and construct a new HwndSource
).
这篇关于如何将WPF英寸单位转换为Winforms像素,反之亦然?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!