Подтвердить что ты не робот

Как преобразовать размер WPF в физические пиксели?

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

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

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

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

4b9b3361

Ответ 1

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

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

  • Вы уже измерили это
  • Вы измерили одного из своих предков или
  • Это часть PresentationSource (например, она находится в видимом окне), и вы выполняете ниже DispatcherPriority.Render, чтобы вы знали, что измерение уже произошло автоматически.

Если ваш визуальный элемент еще не измерен, вы должны вызвать "Измерение" на элементе управления или одного из его предков соответствующим образом, передав доступный размер (или new Size(double.PositivieInfinity, 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);
}

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

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

Из-за этих причин я был бы склонен пропустить вызов Measure в GetElementPixelSize и просто позволить клиенту выполнить его.

Ответ 2

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

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, а не на использование API WPF; и (б) я должен сам выполнить математику, что означает, что я дублирую детали реализации WPF. В .NET 3.5 мне нужно усечь результат вычисления, чтобы он соответствовал тому, что ElementHost делает с помощью AutoSize = true, но я не знаю, будет ли это все еще точным в будущих версиях .NET.

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

Ответ 3

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

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

Ответ 4

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

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

public const System.Windows.Forms.AutoScaleMode Dpi = 2     Участник System.Windows.Forms.AutoScaleMode

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

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

{} пользоваться