40

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

我在 WinForms 表单(通过 ElementHost)中显示 WPF 内容,并尝试制定一些大小调整逻辑。当操作系统以默认的 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”。

4

4 回答 4

71

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

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

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

如果没有,请使用 HwndSource 创建一个临时 hWnd:

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

请注意,这比使用 IntPtr.Zero 的 hWnd 构建效率低,但我认为它更可靠,因为由 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 大小,您需要确保对其进行测量。在某些情况下,它已经被测量过了,或者是因为:

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

如果您的视觉元素尚未被测量,您应该在控件或其祖先之一上调用 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。这提供了一种方便的方法来做所有事情,但有几个缺陷:

  1. 元素的父元素可能会传入较小的可用大小
  2. 如果实际 DesiredSize 为零(重复重新测量),则效率低下
  3. 它可能会以某种方式掩盖错误,从而导致应用程序由于意外时间而失败(例如,在 DispatchPriority.Render 或以上调用的代码)

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

于 2010-08-10T15:08:34.870 回答
10

Screen.WorkingAreaSystemParameters.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 // ——
}

这也适用于多台显示器。

于 2013-09-05T12:50:51.063 回答
9

我找到了一种方法,但我不太喜欢它:

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 版本中是否仍然准确。

这似乎确实有效,所以我发布它以防它帮助其他人。但是,如果有人有更好的答案,请发帖。

于 2010-07-20T01:45:37.877 回答
1

刚刚在ObjectBrowser中进行了快速查找,发现了一些非常有趣的东西,您可能想检查一下。

System.Windows.Form.AutoScaleMode,它有一个名为 DPI 的属性。这是文档,它可能是您正在寻找的:

public const System.Windows.Forms.AutoScaleMode Dpi = 2 System.Windows.Forms.AutoScaleMode 的成员

摘要:控制相对于显示分辨率的比例。常见的分辨率是 96 和 120 DPI。

将其应用于您的表单,它应该可以解决问题。

{请享用}

于 2010-08-13T15:21:18.350 回答