将WPF(与分辨率无关的)宽度和高度转换为物理屏幕像素的最佳方法是什么?
我在WinForms表单中显示WPF内容(通过ElementHost)并试图找出一些大小调整逻辑。当操作系统以默认的96 dpi运行时,我的工作正常。但是当操作系统设置为120 dpi或其他分辨率时,它将无法工作,因为就WinForms而言,报告其宽度为96的WPF元素实际上将是120像素宽。
我在System.Windows.SystemParameters上找不到任何“每英寸像素数”设置。我确信我可以使用WinForms等效(System.Windows.Forms.SystemInformation),但是有更好的方法吗(阅读:使用WPF API的方式,而不是使用WinForms API并手动进行数学运算)?将WPF“像素”转换为实际屏幕像素的“最佳方法”是什么?
编辑:我也希望在屏幕上显示WPF控件之前执行此操作。看起来像Visual.PointToScreen可以给我正确的答案,但我不能使用它,因为控件还没有父级,我得到InvalidOperationException“这个Visual没有连接到PresentationSource”。
答案 0 :(得分:61)
将已知尺寸转换为设备像素
如果您的可视元素已经附加到PresentationSource(例如,它是屏幕上可见的窗口的一部分),则可以通过以下方式找到转换:
var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
如果没有,请使用HwndSource创建临时hWnd:
Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
transformToDevice = source.TransformToDevice;
请注意,这比使用hWnd的IntPtr.Zero构建效率低,但我认为它更可靠,因为HwndSource创建的hWnd将附加到与新创建的实际Window相同的显示设备上。这样,如果不同的显示设备具有不同的DPI,您肯定会获得正确的DPI值。
完成转换后,您可以将任何大小从WPF大小转换为像素大小:
var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
将像素大小转换为整数
如果要将像素大小转换为整数,只需执行以下操作:
int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
但是更强大的解决方案将是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));
获得所需的UIElement大小
要获得所需的UIElement大小,您需要确保测量它。在某些情况下,它已经被测量,因为:
如果尚未测量您的视觉元素,您应该在控件或其中一个祖先上调用Measure,传递可用的大小(或new Size(double.PositivieInfinity, double.PositiveInfinity)
如果您想要调整内容大小:
element.Measure(availableSize);
测量完成后,只需使用矩阵转换DesiredSize:
var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
全部放在一起
这是一个简单的方法,展示了如何获取元素的像素大小:
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);
}
请注意,在此代码中,如果不存在DesiredSize,则仅调用Measure。这提供了一种方便的方法来完成所有事情,但有几个不足之处:
由于这些原因,我倾向于在GetElementPixelSize中省略Measure调用,只是让客户端执行它。
答案 1 :(得分:8)
我找到了一种方法,但我不喜欢它:
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);
// ...
}
我不喜欢它,因为(a)它需要引用System.Drawing,而不是使用WPF API; (b)我必须自己做数学,这意味着我正在重复WPF的实现细节。在.NET 3.5中,我必须截断计算结果以匹配ElementHost对AutoSize = true的作用,但我不知道这在将来的.NET版本中是否仍然准确。
这似乎有效,所以我发布它以防万一它帮助别人。但如果有人有更好的答案,请发帖。
答案 2 :(得分:8)
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 // ——
}
这也适用于多个显示器。
答案 3 :(得分:1)
刚刚在 ObjectBrowser 中快速查找并发现了一些非常有趣的内容,您可能需要查看它。
System.Windows.Form.AutoScaleMode ,它有一个名为DPI的属性。这是文档,它可能是您正在寻找的:
public const System.Windows.Forms.AutoScaleMode Dpi = 2 System.Windows.Forms.AutoScaleMode
的成员摘要:控制相对于的比例 显示分辨率。共同 分辨率为96和120 DPI。
将它应用到您的表单,它应该可以解决问题。
{享受}