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

Joe*_*ite 36 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".

Ray*_*rns 63

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

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

var source = PresentationSource.FromVisual(element);
Matrix transformToDevice = source.CompositionTarget.TransformToDevice;
Run Code Online (Sandbox Code Playgroud)

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

Matrix transformToDevice;
using(var source = new HwndSource(new HwndSourceParameters()))
  transformToDevice = source.TransformToDevice;
Run Code Online (Sandbox Code Playgroud)

请注意,这比使用hWnd的IntPtr.Zero构建效率低,但我认为它更可靠,因为HwndSource创建的hWnd将附加到与实际新创建的Window相同的显示设备上.这样,如果不同的显示设备具有不同的DPI,您肯定会获得正确的DPI值.

完成转换后,您可以将任何大小从WPF大小转换为像素大小:

var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize);
Run Code Online (Sandbox Code Playgroud)

将像素大小转换为整数

如果要将像素大小转换为整数,您可以简单地执行以下操作:

int pixelWidth = (int)pixelSize.Width;
int pixelHeight = (int)pixelSize.Height;
Run Code Online (Sandbox Code Playgroud)

但更强大的解决方案将是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));
Run Code Online (Sandbox Code Playgroud)

获得所需的UIElement大小

要获得所需的UIElement大小,您需要确保测量它.在某些情况下,它已经被测量,或者是因为:

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

如果尚未测量您的视觉元素,您应该在控件或其中一个祖先上调用Measure,传递可用的大小(或者new Size(double.PositivieInfinity, double.PositiveInfinity)如果您想要调整内容大小:

element.Measure(availableSize);
Run Code Online (Sandbox Code Playgroud)

测量完成后,只需使用矩阵转换DesiredSize即可:

var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize);
Run Code Online (Sandbox Code Playgroud)

把它们放在一起

这是一个简单的方法,显示如何获取元素的像素大小:

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);
}
Run Code Online (Sandbox Code Playgroud)

请注意,在此代码中,如果不存在DesiredSize,则仅调用Measure.这提供了一种方便的方法来完成所有事情,但有几个不足之处:

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

由于这些原因,我倾向于省略GetElementPixelSize中的Measure调用,只是让客户端执行它.

  • 您使用HwndSource的示例缺少CompositionTarget元素.你需要改变这个:`transformToDevice = source.TransformToDevice;`to this:`transformToDevice = source.CompositionTarget.TransformToDevice;` (4认同)
  • 这个答案是严重的矫枉过正,因为在问题中我说我已经有了宽度和高度。(咧嘴笑) (2认同)

Joe*_*ite 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);
    // ...
}
Run Code Online (Sandbox Code Playgroud)

我不喜欢它,因为(a)它需要引用System.Drawing,而不是使用WPF API; (b)我必须自己做数学,这意味着我正在重复WPF的实现细节.在.NET 3.5中,我必须截断计算结果以匹配ElementHost与AutoSize = true的作用,但我不知道在未来的.NET版本中这是否仍然准确.

这似乎有效,所以我发布它以防万一它帮助其他人.但如果有人有更好的答案,请发布.


Lu5*_*u55 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 // ——
}
Run Code Online (Sandbox Code Playgroud)

这也适用于多个显示器.