Как преобразовать размер WPF в физические пиксели? - PullRequest
36 голосов
/ 20 июля 2010

Как лучше всего преобразовать ширину и высоту WPF (независимо от разрешения) в физические пиксели экрана?

Я показываю содержимое WPF в форме WinForms (через ElementHost) и пытаюсь выработать логику определения размеров. У меня это работает нормально, когда ОС работает по умолчанию 96 точек на дюйм. Но он не будет работать, если для ОС установлено значение 120 dpi или другое разрешение, потому что тогда элемент WPF, который сообщает о своей ширине в 96, будет иметь ширину 120 пикселей для WinForms.

Не удалось найти какие-либо настройки «пикселей на дюйм» в System.Windows.SystemParameters. Я уверен, что мог бы использовать эквивалент WinForms (System.Windows.Forms.SystemInformation), но есть ли лучший способ сделать это (читай: способ с использованием API-интерфейсов WPF, а не с помощью API-интерфейсов WinForms и выполнения математических операций вручную)? Какой «лучший способ» преобразовать «пиксели» WPF в реальные пиксели экрана?

РЕДАКТИРОВАТЬ: Я также хочу сделать это, прежде чем элемент управления WPF отображается на экране. Похоже, что Visual.PointToScreen можно было сделать, чтобы дать мне правильный ответ, но я не могу его использовать, потому что элемент управления еще не связан с родителями, и я получаю InvalidOperationException «Этот Visual не подключен к PresentationSource».

Ответы [ 4 ]

62 голосов
/ 10 августа 2010

Преобразование известного размера в пиксели устройства

Если ваш визуальный элемент уже подключен к 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.но я считаю это более надежным, потому что hWnd, созданный HwndSource, будет подключен к тому же устройству отображения, что и фактическое вновь созданное окно.Таким образом, если разные устройства отображения имеют разные 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);
}

Обратите внимание, что в этом коде я вызываю Measure только в том случае, если DesiredSize отсутствует. Это обеспечивает удобный метод для выполнения всего, но имеет несколько недостатков.iencies:

  1. Возможно, родительский элемент был бы передан в меньшем доступном размере
  2. Неэффективно, если фактический DesiredSize равен нулю (он повторно измеряется повторно)
  3. Это может маскировать ошибки таким образом, что приложение завершается сбоем из-за непредвиденного времени (например,код вызывается на уровне или выше DispatchPriority.Render)

По этим причинам я был бы склонен опустить вызов Measure в GetElementPixelSize и просто позволить клиенту сделать это.

8 голосов
/ 05 сентября 2013

Простая пропорция между 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 // ——
}

Это хорошо работает и с несколькими мониторами.

8 голосов
/ 20 июля 2010

Я нашел способ сделать это, но мне это не очень нравится:

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);
    // ...
}

Мне это не нравится, потому что (а) требуется ссылка на System.Drawing, а не на API-интерфейсы WPF; и (б) я должен сделать математику сам, что означает, что я дублирую детали реализации WPF. В .NET 3.5 мне нужно усечь результат вычисления, чтобы он соответствовал тому, что ElementHost делает с AutoSize = true, но я не знаю, будет ли это все еще точно в будущих версиях .NET.

Кажется, это работает, поэтому я публикую это на тот случай, если это поможет другим. Но если у кого-то есть лучший ответ, пожалуйста, отправляйте.

1 голос
/ 13 августа 2010

Просто сделал быстрый поиск в ObjectBrowser и нашел что-то весьма интересное, вы можете проверить это.

System.Windows.Form.AutoScaleMode , у него есть свойство DPI. Вот документы, это может быть то, что вы ищете:

публичный конст System.Windows.Forms.AutoScaleMode Dpi = 2 Участник System.Windows.Forms.AutoScaleMode

Сводка: управляет масштабом относительно разрешение дисплея. общий Разрешения 96 и 120 точек на дюйм.

Примените это к вашей форме, это должно сработать.

{наслаждаться}

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...