2010-07-20 3 views
35

Каков наилучший способ преобразования 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».

+0

Вы хотите что-то ищите? http://books.google.com/books?id=xEDKq_QlZ18C&lpg=PA7&ots=lxNG6ADXPa&dq=WPF%20pixels%20to%20real%20screen%20pixels&pg=PA7#v=onepage&q=WPF%20pixels%20to%20real%20screen% 20pixels & f = false – Ragepotato

+2

@Ragepotato, это всего лишь концептуальный обзор - он не дает код для преобразования из WPF в пиксели, и он не объясняет, как обрабатывать граничные условия. –

ответ

57

преобразовательные известного размера для устройства пикселей

Если ваш визуальный элемент уже подключен к 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; 

не e, что это менее эффективно, чем создание с использованием 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); 
} 

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

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

из-за эти причины, я был бы склонен опустить вызов Measure в Get ElementPixelSize и просто позвольте клиенту сделать это.

+1

Этот ответ серьезный overkill, так как в вопросе, который я сказал, у меня уже есть ширина и высота. (grin) –

+1

И он возвращает 'double' для ширины и высоты. Я хочу фактический размер, который будет иметь размер ElementHost, что означает 'int', округленное так же, как это делает ElementHost. –

+0

Я добавил раздел, объясняющий, как преобразовать в целые числа так же, как и ElementHost. –

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

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

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

+0

Нет никакого встроенного метода WPF для получения DPI экрана, и это чище, чем некоторые методы Win32, которые я видел: (Однако вы можете настроить преобразование масштаба на верхнем уровне (перейти от WPF «пикселей» обратно к хосту/экрану пиксели), как только вы выработаете правильный коэффициент конверсии. Если элемент управления WPF размещен в среде WinForms под вашим контролем, вы можете передать информацию с хоста - все еще неактуально. – 2010-07-20 06:40:55

+0

Работала для меня в Win 7 и 8.1. Использовал «закрытое» окно «NotifyIcon» как «hwnd», переданное в «FromHwnd». Частное значение - это риск хрупкости будущих изменений MS, но он был быстрым и, похоже, работает нормально. Описание получения этого значения см. В [ Пример CodeProject] (http://www.codeproject.com/Articles/37912/Embedding-NET-Controls-to-NotifyIcon-Balloon-Toolt), но если недоступно [этот ответ SO]] (http://stackoverflow.com/a/8033317/135114) включает код отражения, чтобы получить «NativeWindow.Handle». – Daryn

+0

Большая проблема заключается в том, что он не работает с несколькими мониторами, если мониторы используют отдельные режимы масштабирования. Sigh - если только .NET (или Windows, если на то пошло), был простой способ получить режим масштабирования для каждого монитора. –

1

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

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

общественного сопзЬ System.Windows.Forms.AutoScaleMode Dpi = 2 Член System.Windows.Forms.AutoScaleMode

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

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

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

7

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

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

+0

Отлично работает для меня. Хорошее простое решение! – JoeMjr2

+0

Спасибо, но, пожалуйста, переименуйте перечисление на что-то вроде «enum Axis {Width, Height}», это будет намного понятнее. – Alex

Смежные вопросы