如何将WPF大小转换为物理像素?

将WPF(分辨率无关)宽度和高度转换为物理屏幕像素的最佳方法是什么?

我在WinForms表单(通过ElementHost)中显示WPF内容,并试图找出一些大小的逻辑。 当操作系统以默认的96 dpi运行时,我已经可以正常工作了。 但是,当操作系统设置为120 dpi或其他分辨率时,它将不起作用,因为那么报告其宽度为96的WPF元素就WinForms而言将实际上是120像素宽。

System.Windows.SystemParameters上找不到任何“每英寸像素”设置。 我相信我可以使用相当于WinForms(System.Windows.Forms.SystemInformation),但有没有更好的方法来做到这一点(阅读:使用WPF API的方式,而不是使用WinForms API和手动做math)? 什么是将WPF“像素”转换为真实屏幕像素的“最佳方式”?

编辑:我也想在WPF控件显示在屏幕上之前这样做。 它看起来像Visual.PointToScreen可以给我正确的答案,但我不能使用它,因为该控件还没有parented,我得到InvalidOperationException“此Visual不连接到PresentationSource”。

将已知大小转换为设备像素

如果您的视觉元素已经附加到PresentationSource(例如,它是屏幕上可见的窗口的一部分),则可以通过以下方式find变换:

var source = PresentationSource.FromVisual(element); Matrix transformToDevice = source.CompositionTarget.TransformToDevice; 

如果没有,使用HwndSource创build临时hWnd:

 Matrix transformToDevice; using(var source = new HwndSource(new HwndSourceParameters())) transformToDevice = source.TransformToDevice; 

请注意,这比使用IntPtr.Zero的hWnd构造效率要低,但我认为它更可靠,因为由HwndSource创build的hWnd将被附加到与新创build的Window相同的显示设备。 这样,如果不同的显示设备有不同的DPI,你一定会得到正确的DPI值。

一旦你有了转换,你可以将WPF大小的任何大小转换为像素大小:

 var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize); 

将像素大小转换为整数

如果你想将像素大小转换为整数,你可以简单地做:

 int pixelWidth = (int)pixelSize.Width; int pixelHeight = (int)pixelSize.Height; 

但更强大的解决scheme将是ElementHost使用的解决scheme:

 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的大小,你需要确保它被测量。 在某些情况下,它会被测量,因为:

  1. 你已经测量过了
  2. 你衡量一个祖先,或者
  3. 它是PresentationSource的一部分(例如,它在一个可见的窗口中),并且正在DispatcherPriority.Render下面执行,所以您知道测量已经自动发生了。

如果您的视觉元素尚未测量,则应根据需要调用控件或其祖先之一的Measure,并传递可用的大小(或new Size(double.PositivieInfinity, double.PositiveInfinity)如果您想resize:

 element.Measure(availableSize); 

一旦完成测量,所有必要的是使用matrix来转换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。 这提供了一个方便的方法来做任何事情,但有几个缺陷:

  1. 这可能是该元素的父母会通过一个较小的可用大小
  2. 如果实际的DesiredSize为零(这是重复测量的)
  3. 它可能会掩盖错误,导致应用程序由于意外的计时而失败(例如,在DispatchPriority.Render或以上调用该代码)

由于这些原因,我倾向于省略GetElementPixelSize中的Measure调用,并让客户端执行此操作。

我find了一个办法,但我不喜欢它:

 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)我必须自己做math,这意味着我复制WPF的实现细节。 在.NET 3.5中,我必须截断计算结果以匹配ElementHost与AutoSize = true所执行的操作,但是我不知道在将来的.NET版本中这样做是否仍然准确无误。

这似乎工作,所以我张贴它的情况下,以帮助其他人。 但如果有人有更好的答案,请寄出。

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 // —— } 

这也适用于多个显示器。

只是在ObjectBrowser中进行了一次快速查找,发现了一些非常有趣的事情,您可能需要检查一下。

System.Windows.Form.AutoScaleMode ,它有一个名为DPI的属性。 这里是文档,这可能是你在找什么:

公共常量System.Windows.Forms.AutoScaleMode Dpi = 2成员的System.Windows.Forms.AutoScaleMode

摘要:控制相对于显示分辨率的缩放。 共同决议是96和120 DPI。

把它应用到你的表格,它应该做的伎俩。

{请享用}

Interesting Posts