如何将 WPF 大小转换为物理像素?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3286175/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How do I convert a WPF size to physical pixels?
提问by Joe White
What's the best way to convert a WPF (resolution-independent) width and height to physical screen pixels?
将 WPF(与分辨率无关)宽度和高度转换为物理屏幕像素的最佳方法是什么?
I'm showing WPF content in a WinForms Form (via ElementHost) and trying to work out some sizing logic. I've got it working fine when the OS is running at the default 96 dpi. But it won't work when the OS is set to 120 dpi or some other resolution, because then a WPF element that reports its Width as 96 will actually be 120 pixels wide as far as WinForms is concerned.
我在 WinForms 表单(通过 ElementHost)中显示 WPF 内容并尝试制定一些大小调整逻辑。当操作系统以默认 96 dpi 运行时,我让它工作正常。但是,当操作系统设置为 120 dpi 或其他一些分辨率时,它将不起作用,因为就 WinForms 而言,报告其宽度为 96 的 WPF 元素实际上将是 120 像素宽。
I couldn't find any "pixels per inch" settings on System.Windows.SystemParameters. I'm sure I could use the WinForms equivalent (System.Windows.Forms.SystemInformation), but is there a better way to do this (read: a way using WPF APIs, rather than using WinForms APIs and manually doing the math)? What's the "best way" to convert WPF "pixels" to real screen pixels?
我在 System.Windows.SystemParameters 上找不到任何“每英寸像素数”设置。我确定我可以使用 WinForms 等价物(System.Windows.Forms.SystemInformation),但是有没有更好的方法来做到这一点(阅读:一种使用 WPF API 的方法,而不是使用 WinForms API 并手动进行数学计算)?将 WPF“像素”转换为真实屏幕像素的“最佳方法”是什么?
EDIT: I'm also looking to do this before the WPF control is shown on the screen. It looks like Visual.PointToScreen could be made to give me the right answer, but I can't use it, because the control isn't parented yet and I get InvalidOperationException "This Visual is not connected to a PresentationSource".
编辑:我也希望在 WPF 控件显示在屏幕上之前执行此操作。看起来 Visual.PointToScreen 可以给我正确的答案,但我不能使用它,因为该控件还没有父级,并且我收到 InvalidOperationException “此 Visual 未连接到 PresentationSource”。
回答by Ray Burns
Transforming a known size to device pixels
将已知尺寸转换为设备像素
If your visual element is already attached to a PresentationSource (for example, it is part of a window that is visible on screen), the transform is found this way:
如果您的视觉元素已经附加到 PresentationSource(例如,它是屏幕上可见的窗口的一部分),则可以通过以下方式找到转换:
var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
If not, use HwndSource to create a temporary hWnd:
如果没有,使用 HwndSource 创建一个临时的 hWnd:
Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.TransformToDevice;
Note that this is less efficient than constructing using a hWnd of IntPtr.Zero but I consider it more reliable because the hWnd created by HwndSource will be attached to the same display device as an actual newly-created Window would. That way, if different display devices have different DPIs you are sure to get the right DPI value.
请注意,这比使用 IntPtr.Zero 的 hWnd 构建效率低,但我认为它更可靠,因为 HwndSource 创建的 hWnd 将连接到与实际新创建的窗口相同的显示设备。这样,如果不同的显示设备具有不同的 DPI,您一定会获得正确的 DPI 值。
Once you have the transform, you can convert any size from a WPF size to a pixel size:
完成转换后,您可以将任何大小从 WPF 大小转换为像素大小:
var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
Converting the pixel size to integers
将像素大小转换为整数
If you want to convert the pixel size to integers, you can simply do:
如果要将像素大小转换为整数,只需执行以下操作:
int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
but a more robust solution would be the one used by ElementHost:
但更强大的解决方案是 ElementHost 使用的解决方案:
int pixelWidth = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Width));
int pixelHeight = (int)Math.Max(int.MinValue, Math.Min(int.MaxValue, pixelSize.Height));
Getting the desired size of a UIElement
获取所需的 UIElement 大小
To get the desired size of a UIElement you need to make sure it is measured. In some circumstances it will already be measured, either because:
要获得所需的 UIElement 大小,您需要确保对其进行测量。在某些情况下,它已经被测量了,要么是因为:
- You measured it already
- You measured one of its ancestors, or
- It is part of a PresentationSource (eg it is in a visible Window) and you are executing below DispatcherPriority.Render so you know measurement has already happened automatically.
- 你已经测量过了
- 你测量了它的一个祖先,或者
- 它是 PresentationSource 的一部分(例如,它在一个可见的窗口中)并且您在 DispatcherPriority.Render 下执行,因此您知道测量已经自动发生。
If your visual element has not been measured yet, you should call Measure on the control or one of its ancestors as appropriate, passing in the available size (or new Size(double.PositivieInfinity, double.PositiveInfinity)
if you want to size to content:
如果你的视觉元素还没有被测量,你应该适当地在控件或其祖先之一上调用 Measure ,传入可用的大小(或者new Size(double.PositivieInfinity, double.PositiveInfinity)
如果你想调整内容的大小:
element.Measure(availableSize);
Once the measuring is done, all that is necessary is to use the matrix to transform the DesiredSize:
测量完成后,只需使用矩阵来转换 DesiredSize:
var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
Putting it all together
把这一切放在一起
Here is a simple method that shows how to get the pixel size of an element:
这是一个简单的方法,展示了如何获取元素的像素大小:
public Size GetElementPixelSize(UIElement element)
{
Matrix transformToDevice;
var source = PresentationSource.FromVisual(element);
if(source!=null)
transformToDevice = source.CompositionTarget.TransformToDevice;
else
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.CompositionTarget.TransformToDevice;
if(element.DesiredSize == new Size())
element.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
return (Size)transformToDevice.Transform((Vector)element.DesiredSize);
}
Note that in this code I call Measure only if no DesiredSize is present. This provides a convenient method to do everything but has several deficiencies:
请注意,在此代码中,仅当不存在 DesiredSize 时才调用 Measure。这提供了一种方便的方法来做任何事情,但有几个缺陷:
- It may be that the element's parent would have passed in a smaller availableSize
- It is inefficient if the actual DesiredSize is zero (it is remeasured repeatedly)
- It may mask bugs in a way that causes the application to fail due to unexpected timing (eg. the code being called at or above DispatchPriority.Render)
- 可能是元素的父元素传入了较小的 availableSize
- 如果实际 DesiredSize 为零,则效率低下(重复重新测量)
- 它可能会以某种方式掩盖错误,从而导致应用程序因意外计时而失败(例如,在 DispatchPriority.Render 或更高级别调用的代码)
Because of these reasons, I would be inclined to omit the Measure call in GetElementPixelSize and just let the client do it.
由于这些原因,我倾向于省略 GetElementPixelSize 中的 Measure 调用,而让客户端执行它。
回答by Joe White
I found a way to do it, but I don't like it much:
我找到了一种方法,但我不太喜欢它:
using (var graphics = Graphics.FromHwnd(IntPtr.Zero))
{
var pixelWidth = (int) (element.DesiredSize.Width * graphics.DpiX / 96.0);
var pixelHeight = (int) (element.DesiredSize.Height * graphics.DpiY / 96.0);
// ...
}
I don't like it because (a) it requires a reference to System.Drawing, rather than using WPF APIs; and (b) I have to do the math myself, which means I'm duplicating WPF's implementation details. In .NET 3.5, I have to truncate the result of the calculation to match what ElementHost does with AutoSize=true, but I don't know whether this will still be accurate in future versions of .NET.
我不喜欢它,因为 (a) 它需要对 System.Drawing 的引用,而不是使用 WPF API;(b) 我必须自己计算,这意味着我要复制 WPF 的实现细节。在 .NET 3.5 中,我必须截断计算结果以匹配 ElementHost 使用 AutoSize=true 所做的事情,但我不知道这在 .NET 的未来版本中是否仍然准确。
This does seem to work, so I'm posting it in case it helps others. But if anyone has a better answer, please, post away.
这似乎确实有效,因此我将其发布以防止对其他人有所帮助。但如果有人有更好的答案,请张贴。
回答by Lu55
Simple proportion between Screen.WorkingAreaand SystemParameters.WorkArea:
Screen.WorkingArea和SystemParameters.WorkArea之间的简单比例:
private double PointsToPixels (double wpfPoints, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Width / SystemParameters.WorkArea.Width;
}
else
{
return wpfPoints * Screen.PrimaryScreen.WorkingArea.Height / SystemParameters.WorkArea.Height;
}
}
private double PixelsToPoints(int pixels, LengthDirection direction)
{
if (direction == LengthDirection.Horizontal)
{
return pixels * SystemParameters.WorkArea.Width / Screen.PrimaryScreen.WorkingArea.Width;
}
else
{
return pixels * SystemParameters.WorkArea.Height / Screen.PrimaryScreen.WorkingArea.Height;
}
}
public enum LengthDirection
{
Vertical, // |
Horizontal // ——
}
This works fine with multiple monitors as well.
这也适用于多台显示器。
回答by Micael Bergeron
Just did a quick lookup in the ObjectBrowserand found something quite interesting, you might want to check it out.
刚刚在ObjectBrowser 中进行了快速查找,发现了一些非常有趣的东西,您可能想查看一下。
System.Windows.Form.AutoScaleMode, it has a property called DPI. Here's the docs, it might be what you are looking for :
System.Windows.Form.AutoScaleMode,它有一个名为 DPI 的属性。这是文档,它可能是您正在寻找的:
public const System.Windows.Forms.AutoScaleMode Dpi = 2 Member of System.Windows.Forms.AutoScaleMode
Summary: Controls scale relative to the display resolution. Common resolutions are 96 and 120 DPI.
public const System.Windows.Forms.AutoScaleMode Dpi = 2 System.Windows.Forms.AutoScaleMode 的成员
摘要:控制相对于显示分辨率的比例。常见的分辨率是 96 和 120 DPI。
Apply that to your form, it should do the trick.
将其应用于您的表单,它应该可以解决问题。
{enjoy}
{请享用}