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

如何将WPF大小转换为物理像素?,wpf,dpi,elementhost,Wpf,Dpi,Elementhost,将WPF(分辨率无关)宽度和高度转换为物理屏幕像素的最佳方法是什么 我正在以WinForms表单(通过ElementHost)显示WPF内容,并尝试制定一些大小调整逻辑。当操作系统以默认的96 dpi运行时,我已经让它正常工作了。但当操作系统设置为120 dpi或其他分辨率时,它将不起作用,因为报告宽度为96的WPF元素实际上就WinForms而言是120像素宽 我在System.Windows.SystemParameters上找不到任何“每英寸像素数”设置。我确信我可以使用WinForms等

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

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

我在System.Windows.SystemParameters上找不到任何“每英寸像素数”设置。我确信我可以使用WinForms等价物(System.Windows.Forms.SystemInformation),但有没有更好的方法(阅读:使用WPF API的方法,而不是使用WinForms API和手动计算)?将WPF“像素”转换为真实屏幕像素的“最佳方法”是什么


编辑:我还希望在屏幕上显示WPF控件之前完成此操作。看起来Visual.PointToScreen可以为我提供正确的答案,但我无法使用它,因为该控件尚未设置为父控件,并且我得到了InvalidOperationException“此Visual未连接到PresentationSource”。

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

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


这似乎是可行的,所以我把它贴出来,以防它对其他人有帮助。但如果有人有更好的答案,请发帖。

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

如果您的视觉元素已附加到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;

注意,这比使用HNDN的ItpTr.0构造效率低,但我认为它更可靠,因为HWNDHOND创建的HWND将被附加到同一个显示设备上,就像实际新创建的窗口一样。这样,如果不同的显示设备具有不同的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大小,您需要确保它已测量。在某些情况下,已经对其进行了测量,因为:

  • 你已经量过了
  • 你测量了它的一个祖先,或者
  • 它是PresentationSource的一部分(如在可见窗口中),您将在DispatcherPriority.Render下执行,以便知道测量已经自动发生
  • 如果尚未测量视觉元素,则应在控件上调用Measure或其祖先之一(视情况而定),传入可用大小(或
    新大小(double.PositiveInfinity,double.PositiveInfinity)
    如果要根据内容调整大小:

    element.Measure(availableSize);
    
    测量完成后,所有必要的操作是使用矩阵变换所需的尺寸:

    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。这提供了一种方便的方法来执行所有操作,但有几个不足之处:

  • 可能是元素的父元素传递了更小的availableSize
  • 如果实际所需大小为零,则效率低下(重复测量)
  • 它可能会以某种方式掩盖错误,导致应用程序由于意外的时间而失败(例如,以DispatchPriority.Render或更高级别调用的代码)

  • 由于这些原因,我倾向于忽略GetElementPixelSize中的度量调用,而让客户端来执行。

    只是在ObjectBrowser中快速查找了一下,发现了一些非常有趣的内容,您可能想查看一下

    System.Windows.Form.AutoScaleMode,它有一个名为DPI的属性。以下是文档,可能就是您要查找的:

    公职人员 System.Windows.Forms.AutoScaleMode Dpi = 2 System.Windows.Forms.AutoScaleMode的成员

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

    把它应用到你的表单上,它应该会起作用

    {享受}

    和之间的简单比例:


    这也适用于多个监视器。

    没有本机WPF方法来获取屏幕DPI,并且这比我见过的一些Win32方法更干净:(但是,您可以设置顶级比例转换(从WPF“像素”返回到主机/屏幕像素)一旦计算出正确的转换率。如果WPF控件托管在您控制下的WinForms环境中,您可能会从主机传入信息——仍然很讨厌。在Win 7和8.1中为我工作。使用
    NotifyIcon
    的private“window”值作为hwnd传递给
    FromHwnd
    。private值为未来MS更改的脆弱性风险,但它很快,似乎工作正常。有关获取该值的说明,请参阅,但如果不可用,则包括获取
    NativeWindow的反射代码。处理
    。最大的问题是,如果监视器使用单独的缩放模式,则无法与多个监视器一起工作。叹气-if only.NET(或该蒙版的窗口)
    
    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 // ——
    }