Как я преобразовываю размер WPF в физические пиксели?

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

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

Я не мог найти настройки "пикселей на дюйм" в Системе. Windows. SystemParameters. Я уверен, что мог использовать эквивалентный WinForms (Система. Windows. Формы. SystemInformation), но там лучший способ сделать это (чтение: способ использовать API WPF, вместо того, чтобы использовать API WinForms и вручную сделать математику)? Что "лучший способ" состоит в том, чтобы преобразовать "пиксели" WPF в реальные экранные пиксели?

Править: Я также надеюсь делать это, прежде чем управление WPF покажут на экране. Это похоже Визуальный. PointToScreen мог быть заставлен дать мне правильный ответ, но я не могу использовать его, потому что управление еще не порождается, и я получаю InvalidOperationException, "Это Визуальное не подключено к PresentationSource".

36
задан Joe White 20 July 2010 в 01:06
поделиться

3 ответа

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

Если ваш визуальный элемент уже прикреплен к 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 отсутствует.Это обеспечивает удобный способ делать все, но имеет несколько недостатков:

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

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

63
ответ дан 27 November 2019 в 05:37
поделиться

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

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.

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

8
ответ дан 27 November 2019 в 05:37
поделиться

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

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

public const System.Windows.Forms.AutoScaleMode Dpi = 2 Член System.Windows.Forms.AutoScaleMode

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

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

{наслаждайтесь}

1
ответ дан 27 November 2019 в 05:37
поделиться
Другие вопросы по тегам:

Похожие вопросы: