Ответ 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 и просто позволить клиенту выполнить его.