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

时间:2010-07-20 00:39:30

标签: wpf dpi elementhost

将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”。

4 个答案:

答案 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大小,您需要确保测量它。在某些情况下,它已经被测量,因为:

  1. 你已经测量过了
  2. 你测量了它的一个祖先,或
  3. 它是PresentationSource的一部分(例如,它位于可见窗口中),您在DispatcherPriority.Render下面执行,因此您知道测量已经自动发生。
  4. 如果尚未测量您的视觉元素,您应该在控件或其中一个祖先上调用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. 可能是元素的父元素已经传入较小的availableSize
    2. 如果实际的DesiredSize为零(重复重新测量),效率很低
    3. 它可能会以一种导致应用程序因意外计时而失败的方式来掩盖错误(例如,在DispatchPriority.Render或以上调用代码)
    4. 由于这些原因,我倾向于在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.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 // ——
}

这也适用于多个显示器。

答案 3 :(得分:1)

刚刚在 ObjectBrowser 中快速查找并发现了一些非常有趣的内容,您可能需要查看它。

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

  

public const   System.Windows.Forms.AutoScaleMode Dpi   = 2       System.Windows.Forms.AutoScaleMode

的成员      

摘要:控制相对于的比例   显示分辨率。共同   分辨率为96和120 DPI。

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

{享受}