2010-07-20 20 views
35

¿Cuál es la mejor manera de convertir un ancho y alto WPF (independiente de la resolución) en píxeles de pantalla físicos?¿Cómo convierto un tamaño de WPF en píxeles físicos?

Estoy mostrando el contenido de WPF en un Formulario de WinForms (a través de ElementHost) y tratando de resolver algunos de la lógica de tamaño. Lo tengo funcionando bien cuando el sistema operativo se ejecuta con los 96 ppp predeterminados. Pero no funcionará cuando el sistema operativo esté configurado a 120 ppp o a alguna otra resolución, porque entonces un elemento de WPF que informe su ancho como 96 tendrá 120 píxeles de ancho en lo que respecta a WinForms.

No pude encontrar ninguna configuración de "píxeles por pulgada" en System.Windows.SystemParameters. Estoy seguro de que podría usar el equivalente de WinForms (System.Windows.Forms.SystemInformation), pero ¿hay una forma mejor de hacerlo (léase: una forma de utilizar las API de WPF, en lugar de utilizar las API de WinForms y hacer los cálculos manualmente)? ¿Cuál es la "mejor manera" de convertir "píxeles" de WPF en píxeles de pantalla real?

EDITAR: También estoy buscando hacer esto antes de que se muestre el control WPF en la pantalla. Parece que Visual.PointToScreen podría estar hecho para darme la respuesta correcta, pero no puedo usarlo, porque el control aún no está pareado y obtengo InvalidOperationException "Esta visual no está conectada a una PresentationSource".

+0

¿Es esto lo que estás buscando? 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% 20 píxeles & f = falso – Ragepotato

+2

@Ragepotato, eso es solo una descripción conceptual: no da el código para convertir de WPF a píxeles, y no explica cómo manejar las condiciones de contorno. –

Respuesta

57

transformación de un tamaño conocido a píxeles dispositivo

Si su elemento visual ya está unido a un PresentationSource (por ejemplo, es parte de una ventana que es visible en la pantalla), la transformada se encuentra de esta manera :

var source = PresentationSource.FromVisual(element); 
Matrix transformToDevice = source.CompositionTarget.TransformToDevice; 

Si no es así, utilice HwndSource para crear un CVent temporal:

Matrix transformToDevice; 
using(var source = new HwndSource(new HwndSourceParameters())) 
    transformToDevice = source.TransformToDevice; 
no

e que esto es menos eficiente que construir utilizando un hWnd de IntPtr.Zero, pero lo considero más confiable porque el hWnd creado por HwndSource se conectará al mismo dispositivo de visualización como lo haría una Ventana recién creada. De esta forma, si los diferentes dispositivos de visualización tienen diferentes DPI, está seguro de obtener el valor correcto de DPI.

Una vez que haya la transformación, puede convertir cualquier tamaño, desde un tamaño de WPF a un tamaño de píxel:

var pixelSize = (Size)transformToDevice.Transform((Vector)wpfSize); 

Conversión del tamaño de píxel de números enteros

Si desea convertir el píxel el tamaño de los números enteros, sólo tiene que hacer:

int pixelWidth = (int)pixelSize.Width; 
int pixelHeight = (int)pixelSize.Height; 

pero una solución más robusta sería el utilizado por 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)); 

conseguir el tamaño deseado de UIElement

Para obtener el tamaño deseado de un UIElement que necesita para asegurarse de que se mide. En algunas circunstancias que ya se medirá, ya sea porque:

  1. que se ha medido ya
  2. que se ha medido una de sus antepasados, o
  3. Es parte de un PresentationSource (por ejemplo, se encuentra en una ventana visible) y se está ejecutando debajo de DispatcherPriority.Render para que sepa que la medición ya ha ocurrido automáticamente.

Si el elemento visual no se ha medido sin embargo, debe llamar a la medida en el control o uno de sus antepasados, según corresponda, que pasa en el tamaño disponible (o new Size(double.PositivieInfinity, double.PositiveInfinity) si quiere clasificar al contenido:

element.Measure(availableSize); 

Una vez que la medición se hace, todo lo que es necesario es utilizar la matriz para transformar el DesiredSize:

var pixelSize = (Size)transformToDevice.Transform((Vector)element.DesiredSize); 

Poniendo todo junto

Aquí es un método simple que muestra cómo obtener el tamaño de píxel de un elemento:

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

Tenga en cuenta que en este código que llamo Medida sólo si no hay DesiredSize está presente. Esto proporciona un método conveniente de hacerlo todo pero tiene varias deficiencias:

  1. Puede ser que los padres del elemento habría pasado en un pequeño availableSize
  2. Es ineficiente si el DesiredSize real es cero (que se vuelve a medir repetidamente)
  3. puede enmascarar errores de una manera que hace que la aplicación a fallar debido a la sincronización inesperado (por ejemplo. el código que está siendo llamado en o por encima DispatchPriority.Render)

debido a estas razones, estaría inclinado omitir la llamada Medir en Obtener ElementPixelSize y simplemente deja que el cliente lo haga.

+1

Esta respuesta es una exageración seria, ya que en la pregunta que dije ya tengo el ancho y la altura. (sonrisa) –

+1

Y devuelve 'double's para ancho y alto. Quiero el tamaño real al que le daría ElementHost, lo que significa 'int's, redondeado de la misma manera que ElementHost. –

+0

Agregué una sección que explica cómo convertir a enteros de la misma manera que ElementHost. –

8

he encontrado una manera de hacerlo, pero no me gusta mucho:

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

no me gusta porque (a) se requiere una referencia a System.Drawing, en lugar de utilizar WPF APIs; y (b) Tengo que hacer los cálculos yo mismo, lo que significa que estoy duplicando los detalles de implementación de WPF. En .NET 3.5, tengo que truncar el resultado del cálculo para que coincida con lo que hace ElementHost con AutoSize = verdadero, pero no sé si esto todavía será preciso en las versiones futuras de .NET.

Esto parece funcionar, así que lo estoy publicando en caso de que ayude a otros. Pero si alguien tiene una mejor respuesta, por favor, envíela.

+0

No hay un método nativo WPF para obtener Screen DPI, y esto es más limpio que algunos métodos Win32 que he visto :(Sin embargo, puede configurar una transformación de escala de nivel superior (para pasar de WPF "píxeles" a host/pantalla píxeles) una vez que haya calculado la relación de conversión correcta. Si el control WPF está alojado en un entorno WinForms bajo su control, posiblemente podría pasar la información del host. Todavía es asqueroso. –

+0

Funcionó para mí en Win 7 y 8.1. Usé el valor de 'ventana' privada de 'NotifyIcon' cuando hwnd pasó a 'FromHwnd'. El valor privado es un riesgo de fragilidad para futuros cambios MS, pero fue rápido y parece funcionar bien. Para obtener más información sobre cómo obtener ese valor, consulte [ Ejemplo de CodeProject] (http://www.codeproject.com/Articles/37912/Embedding-NET-Controls-to-NotifyIcon-Balloon-Toolt), pero si no está disponible [esta respuesta SO] (http://stackoverflow.com)/a/8033317/135114) incluye el código de reflexión para obtener 'NativeWindow.Handle'. – Daryn

+0

El gran problema con esto es que no funciona con monitores múltiples si los monitores usan modos de escalado separados. Suspiro, si solo .NET (o Windows para el caso) tuviera una manera fácil de obtener el modo Escala para cada monitor. –

1

Acabo de hacer una búsqueda rápida en el ObjectBrowser y encontré algo bastante interesante, es posible que desee comprobarlo.

System.Windows.Form.AutoScaleMode, tiene una propiedad llamada DPI. Aquí está la documentación, puede ser que sea lo que buscas:

public const System.Windows.Forms.AutoScaleMode Dpi = 2 miembros de System.Windows.Forms.AutoScaleMode

Resumen: Controles escala relativa a la resolución de pantalla. Las resoluciones comunes son 96 y 120 DPI.

Aplique eso a su forma, debería hacer el truco.

{} disfrutar de

7

simple proporción entre Screen.WorkingArea y 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 // —— 
} 

Esto funciona bien con varios monitores también.

+0

Funcionó perfectamente para mí. Buena solución simple! – JoeMjr2

+0

Gracias, pero cambie el nombre de enum a algo así como "eje enum {ancho, alto}", esto será mucho más claro de entender. – Alex

Cuestiones relacionadas