Desarrollo de aplicaciones de escritorio de alto PPP en Windows
Este contenido está dirigido a desarrolladores que buscan actualizar las aplicaciones de escritorio para controlar los cambios de factor de escala de pantalla (puntos por pulgada o PPP) dinámicamente, lo que permite que sus aplicaciones sean nítidas en cualquier pantalla en la que se representen.
Para empezar, si va a crear una nueva aplicación de Windows desde cero, es muy recomendable crear una aplicación de Plataforma universal de Windows (UWP). Las aplicaciones para UWP se escalan de forma automática ,y dinámica, para cada pantalla en la que se ejecutan.
Aplicaciones de escritorio que usan tecnologías de programación anteriores de Windows (programación Win32 sin procesar, Windows Forms, Windows Presentation Framework (WPF), etc.) no pueden controlar automáticamente el escalado de PPP sin trabajo adicional para desarrolladores. Sin dichas acciones, las aplicaciones aparecerán borrosas o de tamaño incorrecto en muchos escenarios de uso comunes. En este documento se proporciona contexto e información sobre lo que implica actualizar una aplicación de escritorio para que se represente correctamente.
Factor de escala de visualización & PPP
A medida que la tecnología de visualización ha progresado, los fabricantes de paneles de visualización han empaquetado un número creciente de píxeles en cada unidad de espacio físico en sus paneles. Esto ha dado lugar a que los puntos por pulgada (PPP) de los paneles de visualización modernos sean mucho más altos de lo que históricamente han sido. En el pasado, la mayoría de las pantallas tenían 96 píxeles por pulgada lineal de espacio físico (96 PPP); en 2017, las pantallas con casi 300 PPP o superior están disponibles fácilmente.
La mayoría de los marcos de interfaz de usuario de escritorio heredados tienen suposiciones integradas de que el PPP de visualización no cambiará durante la vigencia del proceso. Esta suposición ya no es cierta, con los PPP de visualización que suelen cambiar varias veces durante la duración de un proceso de aplicación. Algunos escenarios comunes en los que los cambios en el factor de escala o PPP de visualización son:
- Configuraciones de varios monitores en las que cada pantalla tiene un factor de escala diferente y la aplicación se mueve de una pantalla a otra (por ejemplo, una pantalla de 4K y una pantalla de 1080p).
- Acoplamiento y desacoplamiento de un portátil de PPP alto con una pantalla externa de PPP bajo (o viceversa)
- Conectar a través de Escritorio remoto desde un portátil o tableta de PPP alto a un dispositivo de PPP bajo (o viceversa)
- Realizar un cambio en la configuración del factor de escala de visualización mientras se ejecutan las aplicaciones
En estos escenarios, las aplicaciones para UWP se vuelven a dibujar automáticamente para el nuevo PPP. De forma predeterminada, y sin trabajo adicional para desarrolladores, las aplicaciones de escritorio no lo hacen. Las aplicaciones de escritorio que no realizan este trabajo adicional para responder a los cambios de PPP pueden aparecer borrosos o de tamaño incorrecto para el usuario.
Modo de reconocimiento de PPP
Las aplicaciones de escritorio deben indicar a Windows si admiten el escalado de PPP. De forma predeterminada, el sistema considera que las aplicaciones de escritorio no son conscientes y el mapa de bits amplía sus ventanas. Al establecer uno de los siguientes modos de reconocimiento de PPP disponibles, las aplicaciones pueden indicar explícitamente a Windows cómo desean controlar el escalado de PPP:
PPP no consciente
Las aplicaciones no conscientes de PPP se representan en un valor de PPP fijo de 96 (100 %). Cada vez que estas aplicaciones se ejecutan en una pantalla con una escala de visualización superior a 96 PPP, Windows ajustará el mapa de bits de la aplicación al tamaño físico esperado. Esto da como resultado que la aplicación aparezca borrosa.
Reconocimiento de PPP del sistema
Las aplicaciones de escritorio que son compatibles con el PPP del sistema suelen recibir el PPP del monitor conectado principal a partir del momento en que el usuario inicia sesión. Durante la inicialización, diseñaron su interfaz de usuario correctamente (controles de ajuste de tamaño, elegir tamaños de fuente, cargar recursos, etc.) con ese valor de PPP del sistema. Por lo tanto, Windows no escala las aplicaciones con reconocimiento de PPP del sistema (mapa de bits extendido) en pantallas que se representan en ese único PPP. Cuando la aplicación se mueve a una pantalla con un factor de escala diferente, o si cambia el factor de escala de visualización, Windows escalará las ventanas de la aplicación, lo que hará que aparezcan borrosas. De hecho, las aplicaciones de escritorio con reconocimiento de PPP del sistema solo se representan de forma nítida en un solo factor de escala de visualización, volviéndose borrosas cada vez que cambia el PPP.
Reconocimiento de PPP por monitor y por monitor (V2)
Se recomienda que las aplicaciones de escritorio se actualicen para usar el modo de reconocimiento de PPP por monitor, lo que les permite representarse inmediatamente de forma correcta cada vez que cambia el PPP. Cuando una aplicación informa a Windows de que quiere ejecutarse en este modo, Windows no ajustará el mapa de bits a la aplicación cuando cambie el PPP, en lugar de enviar WM_DPICHANGED a la ventana de la aplicación. A continuación, es responsabilidad completa de la aplicación controlar el cambio de tamaño para el nuevo PPP. La mayoría de los marcos de interfaz de usuario usados por las aplicaciones de escritorio (controles comunes de Windows (comctl32), Windows Forms, Windows Presentation Framework, etc.) no admite el escalado automático de PPP, lo que requiere que los desarrolladores cambien el tamaño y cambien la posición del contenido de sus ventanas.
Hay dos versiones de reconocimiento por monitor que una aplicación puede registrarse como: versión 1 y versión 2 (PMv2). El registro de un proceso como en ejecución en el modo de reconocimiento de PMv2 da como resultado:
- La aplicación que se notifica cuando cambia el VALOR de PPP (los HWND de nivel superior y secundarios)
- La aplicación que ve los píxeles sin procesar de cada pantalla
- La aplicación nunca se está escalando mediante Windows
- Área no cliente automática (título de ventana, barras de desplazamiento, etc.) Escalado de PPP por Windows
- Cuadros de diálogo Win32 (de CreateDialog) escalados automáticamente por Windows
- Recursos de mapa de bits dibujados por temas en controles comunes (casillas, fondos de botón, etc.) que se representan automáticamente en el factor de escala de PPP adecuado.
Cuando se ejecuta en modo de reconocimiento por monitor v2, las aplicaciones se notifican cuando su PPP ha cambiado. Si una aplicación no cambia su tamaño para el nuevo PPP, la interfaz de usuario de la aplicación aparecerá demasiado pequeña o demasiado grande (dependiendo de la diferencia en los valores de PPP anteriores y nuevos).
Nota:
El reconocimiento por monitor V1 (PMv1) es muy limitado. Se recomienda que las aplicaciones usen PMv2.
En la tabla siguiente se muestra cómo se representarán las aplicaciones en diferentes escenarios:
Modo de reconocimiento de PPP | Versión de Windows introducida | Vista de PPP de la aplicación | Comportamiento en el cambio de PPP |
---|---|---|---|
No consciente | N/D | Todas las pantallas son de 96 PPP | Extensión de mapa de bits (borrosa) |
Sistema | Vista | Todas las pantallas tienen el mismo PPP (el PPP de la pantalla principal en el momento en que se inició la sesión del usuario actual) | Extensión de mapa de bits (borrosa) |
Por monitor | 8.1 | El PPP de la pantalla en la que se encuentra principalmente la ventana de la aplicación. |
|
V2 por monitor | Windows 10 Creators Update (1703) | El PPP de la pantalla en la que se encuentra principalmente la ventana de la aplicación. |
Escalado automático de PPP de:
|
Reconocimiento de PPP por monitor (V1)
El modo de reconocimiento de PPP por monitor V1 (PMv1) se introdujo con Windows 8.1. Este modo de reconocimiento de PPP es muy limitado y solo ofrece la funcionalidad que se muestra a continuación. Se recomienda que las aplicaciones de escritorio usen el modo de reconocimiento por monitor v2, compatible con Windows 10 1703 o superior.
La compatibilidad inicial para el reconocimiento por monitor solo ofrecía las aplicaciones siguientes:
- Los HWND de nivel superior reciben una notificación de un cambio de PPP y se les proporciona un nuevo tamaño sugerido.
- Windows no ajustará el mapa de bits a la interfaz de usuario de la aplicación
- La aplicación ve todas las pantallas en píxeles físicos (consulte virtualización)
En Windows 10 1607 o superior, las aplicaciones PMv1 también pueden llamar a EnableNonClientDpiScaling durante WM_NCCREATE para solicitar que Windows escale correctamente el área que no es de cliente de la ventana.
Compatibilidad con el escalado de PPP por monitor por marco de interfaz de usuario o tecnología
En la tabla siguiente se muestra el nivel de compatibilidad de reconocimiento de PPP por monitor que ofrecen varios marcos de interfaz de usuario de Windows a partir de Windows 10 1703:
Marco o tecnología | Soporte técnico | Versión del SO | Escalado de PPP controlado por | Lecturas adicionales |
---|---|---|---|---|
Plataforma universal de Windows (UWP) | Completo | 1607 | Marco de interfaz de usuario | Plataforma universal de Windows (UWP) |
Win32/Controles habituales V6 sin procesar (comctl32.dll) |
|
1703 | Application | Muestra de GitHub |
Windows Forms | Escalado de PPP automático por monitor limitado para algunos controles | 1703 | Marco de interfaz de usuario | Compatibilidad con valores altos de PPP en Windows Forms |
Windows Presentation Framework (WPF) | Las aplicaciones nativas de WPF escalarán WPF hospedado en otros marcos y otros marcos hospedados en WPF no escalarán automáticamente | 1607 | Marco de interfaz de usuario | Muestra de GitHub |
GDI | Ninguno | N/D | Application | Consulte Escalado de valores altos de PPP de GDI |
GDI+ | Ninguno | N/D | Application | Consulte Escalado de valores altos de PPP de GDI |
MFC | Ninguno | N/D | Application | N/D |
Actualizar aplicaciones existentes
Para actualizar una aplicación de escritorio existente para controlar el escalado de PPP correctamente, debe actualizarse de forma que, como mínimo, las partes importantes de su interfaz de usuario se actualicen para responder a los cambios de PPP.
La mayoría de las aplicaciones de escritorio se ejecutan en modo de reconocimiento de PPP del sistema. Las aplicaciones con reconocimiento de PPP del sistema normalmente se escalan a la PPP de la pantalla principal (la pantalla en la que se encontraba la bandeja del sistema en el momento en que se inició la sesión de Windows). Cuando cambia el PPP, Windows ajustará la interfaz de usuario de estas aplicaciones, lo que a menudo hace que se desenfoquen. Al actualizar una aplicación con reconocimiento de PPP del sistema para que sea compatible con PPP por monitor, el código que controla el diseño de la interfaz de usuario debe actualizarse de modo que se realice no solo durante la inicialización de la aplicación, sino también siempre que se reciba una notificación de cambio de PPP (WM_DPICHANGED en el caso de Win32). Normalmente, esto implica volver a consultar las suposiciones del código que la interfaz de usuario solo debe escalarse una vez.
Además, en el caso de la programación de Win32, muchas API de Win32 no tienen ningún contexto de PPP o visualización, por lo que solo devolverán valores relativos al PPP del sistema. Puede ser útil grep a través del código para buscar algunas de estas API y reemplazarlas por variantes con reconocimiento de PPP. Algunas de las API comunes que tienen variantes con reconocimiento de PPP son:
Versión única de PPP | Versión por monitor |
---|---|
GetSystemMetrics | GetSystemMetricsForDpi |
AdjustWindowRectEx | AdjustWindowRectExForDpi |
SystemParametersInfo | SystemParametersInfoForDpi |
GetDpiForMonitor | GetDpiForWindow |
También es una buena idea buscar tamaños codificados de forma rígida en el código base que suponen un PPP constante y reemplazarlos por código que tenga en cuenta correctamente el escalado de PPP. A continuación se muestra un ejemplo que incorpora todas estas sugerencias:
Ejemplo:
En el ejemplo siguiente se muestra un caso de Win32 simplificado de creación de un HWND secundario. La llamada a CreateWindow supone que la aplicación se ejecuta en 96 PPP (USER_DEFAULT_SCREEN_DPI
constante) y que el tamaño ni la posición del botón serán correctos en los PPP superiores:
case WM_CREATE:
{
// Add a button
HWND hWndChild = CreateWindow(L"BUTTON", L"Click Me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
50,
50,
100,
50,
hWnd, (HMENU)NULL, NULL, NULL);
}
El código actualizado siguiente muestra:
- El código de creación de ventanas PPP escala la posición y el tamaño del HWND secundario para el PPP de su ventana primaria
- Responder al cambio de PPP al cambiar la posición y cambiar el tamaño del HWND secundario
- Tamaños codificados de forma rígida quitados y reemplazados por código que responde a los cambios de PPP
#define INITIALX_96DPI 50
#define INITIALY_96DPI 50
#define INITIALWIDTH_96DPI 100
#define INITIALHEIGHT_96DPI 50
// DPI scale the position and size of the button control
void UpdateButtonLayoutForDpi(HWND hWnd)
{
int iDpi = GetDpiForWindow(hWnd);
int dpiScaledX = MulDiv(INITIALX_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
int dpiScaledY = MulDiv(INITIALY_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
int dpiScaledWidth = MulDiv(INITIALWIDTH_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
int dpiScaledHeight = MulDiv(INITIALHEIGHT_96DPI, iDpi, USER_DEFAULT_SCREEN_DPI);
SetWindowPos(hWnd, hWnd, dpiScaledX, dpiScaledY, dpiScaledWidth, dpiScaledHeight, SWP_NOZORDER | SWP_NOACTIVATE);
}
...
case WM_CREATE:
{
// Add a button
HWND hWndChild = CreateWindow(L"BUTTON", L"Click Me",
WS_CHILD|WS_VISIBLE|BS_PUSHBUTTON,
0,
0,
0,
0,
hWnd, (HMENU)NULL, NULL, NULL);
if (hWndChild != NULL)
{
UpdateButtonLayoutForDpi(hWndChild);
}
}
break;
case WM_DPICHANGED:
{
// Find the button and resize it
HWND hWndButton = FindWindowEx(hWnd, NULL, NULL, NULL);
if (hWndButton != NULL)
{
UpdateButtonLayoutForDpi(hWndButton);
}
}
break;
Al actualizar una aplicación con reconocimiento de PPP del sistema, se indican algunos pasos comunes que se deben seguir:
- Marque el proceso como compatible con PPP por monitor (V2) mediante un manifiesto de aplicación (u otro método, según los marcos de interfaz de usuario usados).
- Haga que la lógica de diseño de la interfaz de usuario sea reutilizable y muévala del código de inicialización de la aplicación de forma que se pueda reutilizar cuando se produce un cambio de PPP (WM_DPICHANGED en el caso de la programación de Windows (Win32)).
- Invalide cualquier código que suponga que los datos confidenciales de PPP (PPP/fonts/sizes/etc.) nunca deben actualizarse. Es una práctica muy común almacenar en caché los tamaños de fuente y los valores de PPP en la inicialización del proceso. Al actualizar una aplicación para que sea compatible con PPP por monitor, se deben volver a evaluar los datos confidenciales de PPP cada vez que se encuentre un nuevo PPP.
- Cuando se produce un cambio de PPP, vuelva a cargar (o volver a rasterizar) los recursos de mapa de bits para el nuevo PPP u, opcionalmente, el mapa de bits ajustará los recursos cargados actualmente al tamaño correcto.
- Grep para API que no son compatibles con PPP por monitor y los reemplazan por API con reconocimiento de PPP por monitor (si procede). Ejemplo: reemplace GetSystemMetrics por GetSystemMetricsForDpi.
- Pruebe la aplicación en un sistema de varias pantallas o varios PPP.
- En el caso de las ventanas de nivel superior de la aplicación que no pueda actualizar a una escala de PPP correcta, use el escalado de PPP en modo mixto (que se describe a continuación) para permitir el ajuste de mapa de bits de estas ventanas de nivel superior por el sistema.
Escalado de PPP en modo mixto (escala de PPP de subproceso)
Al actualizar una aplicación para admitir el reconocimiento de PPP por monitor, a veces puede resultar poco práctico o imposible actualizar todas las ventanas de la aplicación en un solo uso. Esto puede deberse simplemente al tiempo y al esfuerzo necesario para actualizar y probar toda la interfaz de usuario, o porque no posee todo el código de interfaz de usuario que necesita ejecutar (si la aplicación quizá carga la interfaz de usuario de terceros). En estas situaciones, Windows ofrece una manera de facilitar el mundo del reconocimiento por monitor al permitirle ejecutar algunas de las ventanas de aplicación (solo de nivel superior) en su modo original de reconocimiento de PPP mientras centra su tiempo y la energía actualizando las partes más importantes de su interfaz de usuario.
A continuación se muestra una ilustración de lo que podría tener este aspecto: actualice la interfaz de usuario de la aplicación principal ("Ventana principal" en la ilustración) para ejecutarse con reconocimiento de PPP por monitor mientras ejecuta otras ventanas en su modo existente ("Ventana secundaria").
Antes de la actualización de aniversario de Windows 10 (1607), el modo de reconocimiento de PPP de un proceso era una propiedad de todo el proceso. A partir de la actualización de aniversario de Windows 10, esta propiedad ahora se puede establecer por ventana de nivel superior. (Las ventanas secundarias deben seguir coincidiendo con el tamaño de escala de su elemento primario). Una ventana de nivel superior se define como una ventana sin elemento primario. Normalmente, se trata de una ventana "normal" con botones minimizar, maximizar y cerrar. El escenario para el que el reconocimiento de PPP de subproceso está pensado es para tener una interfaz de usuario secundaria escalada por Windows (mapa de bits extendido) mientras se centra el tiempo y los recursos en la actualización de la interfaz de usuario principal.
Para habilitar el reconocimiento de PPP de subproceso, llame a SetThreadDpiAwarenessContext antes y después de las llamadas de creación de ventanas. La ventana que se crea se asociará con el reconocimiento de PPP que establezca a través de SetThreadDpiAwarenessContext. Use la segunda llamada para restaurar el reconocimiento de PPP del subproceso actual.
Aunque el uso del escalado de PPP de subproceso permite confiar en Windows para realizar algunas de las escalas de PPP de la aplicación, puede aumentar la complejidad de la aplicación. Es importante que comprenda los inconvenientes de este enfoque y la naturaleza de las complejidades que presenta. Para obtener más información sobre el reconocimiento de PPP de subproceso, consulte Escalado de PPP en modo mixto y API compatibles con PPP.
Prueba de los cambios
Después de actualizar la aplicación para que sea consciente de PPP por monitor, es importante validar que la aplicación responde correctamente a los cambios de PPP en un entorno de PPP mixto. Algunos detalles específicos para probar incluyen:
- Mover las ventanas de la aplicación entre las pantallas de valores de PPP diferentes
- Inicio de la aplicación en pantallas de valores de PPP diferentes
- Cambio del factor de escala del monitor mientras se ejecuta la aplicación
- Cambiar la pantalla que usa como pantalla principal, cerrar sesión en Windows y volver a probar la aplicación después de volver a iniciar sesión. Esto resulta especialmente útil para buscar código que usa tamaños o dimensiones codificados de forma rígida.
Problemas comunes (Win32)
No usar el rectángulo sugerido que se proporciona en WM_DPICHANGED
Cuando Windows envía a la ventana de su aplicación un mensaje WM_DPICHANGED, este mensaje incluye un rectángulo sugerido que debe usar para cambiar el tamaño de su ventana. Es fundamental que la aplicación use este rectángulo para cambiar su tamaño, ya que esto hará lo siguiente:
- Asegúrese de que el cursor del mouse permanece en la misma posición relativa en la ventana al arrastrar entre pantallas.
- Evite que la ventana de la aplicación entre en un ciclo de cambio de PPP recursivo, donde un cambio de PPP desencadena un cambio de PPP posterior, que desencadena otro cambio de PPP.
Si tiene requisitos específicos de la aplicación que le impiden usar el rectángulo sugerido que Windows proporciona en el mensaje de WM_DPICHANGED, consulte WM_GETDPISCALEDSIZE. Este mensaje se puede usar para dar a Windows un tamaño deseado que le gustaría usar una vez que se ha producido el cambio de PPP, a la vez que evita los problemas descritos anteriormente.
Falta de documentación sobre la virtualización
Cuando un HWND o un proceso se ejecutan como no conscientes de PPP o de PPP del sistema, Windows puede ampliar el mapa de bits. Cuando esto sucede, Windows escala y convierte información confidencial de PPP de algunas API en el espacio de coordenadas del subproceso de llamada. Por ejemplo, si un subproceso no consciente de PPP consulta el tamaño de pantalla mientras se ejecuta en una pantalla con valores altos de PPP, Windows virtualizará la respuesta dada a la aplicación como si la pantalla estuviera en 96 unidades de PPP. Como alternativa, cuando un subproceso con reconocimiento de PPP del sistema interactúa con una presentación en un PPP diferente al que estaba en uso cuando se inició la sesión del usuario actual, Windows escalará algunas llamadas API al espacio de coordenadas que el HWND usaría si se estuviera ejecutando en su factor de escala de PPP original.
Al actualizar la aplicación de escritorio a ppp correctamente, puede ser difícil saber qué llamadas API pueden devolver valores virtualizados en función del contexto del subproceso; Microsoft no documenta actualmente esta información. Tenga en cuenta que si llama a cualquier API del sistema desde un contexto de subproceso sin reconocimiento de PPP o compatible con el sistema, es posible que el valor devuelto se virtualice. Por lo tanto, asegúrese de que el subproceso se ejecuta en el contexto de PPP que espera al interactuar con la pantalla o las ventanas individuales. Al cambiar temporalmente el contexto de PPP de un subproceso mediante SetThreadDpiAwarenessContext, asegúrese de restaurar el contexto anterior cuando haya terminado para evitar provocar un comportamiento incorrecto en otra parte de la aplicación.
Muchas API de Windows no tienen un contexto de PPP
Muchas API heredadas de Windows no incluyen un contexto de PPP o HWND como parte de su interfaz. Como resultado, los desarrolladores suelen tener que realizar un trabajo adicional para controlar el escalado de cualquier información confidencial de PPP, como tamaños, puntos o iconos. Por ejemplo, los desarrolladores que usan LoadIcon deben cargar iconos cargados de mapa de bits o usar API alternativas para cargar iconos de tamaño correcto para el PPP adecuado, como LoadImage.
Restablecimiento forzado del reconocimiento de PPP en todo el proceso
En general, el modo de reconocimiento de PPP del proceso no se puede cambiar después de la inicialización del proceso. Sin embargo, Windows puede cambiar forzadamente el modo de reconocimiento de PPP del proceso si intenta interrumpir el requisito de que todos los HWND de un árbol de ventana tengan el mismo modo de reconocimiento de PPP. En todas las versiones de Windows, a partir de Windows 10 1703, no es posible tener diferentes HWND en un árbol HWND que se ejecute en diferentes modos de reconocimiento de PPP. Si intenta crear una relación de elementos primarios-secundarios que interrumpa esta regla, se puede restablecer el reconocimiento de PPP de todo el proceso. Esto se puede desencadenar mediante:
- Una llamada a CreateWindow en la que la ventana primaria pasada es de un modo de reconocimiento de PPP diferente al subproceso que realiza la llamada.
- Una llamada a SetParent donde las dos ventanas están asociadas a diferentes modos de reconocimiento de PPP.
En la tabla siguiente se muestra lo que sucede si intenta infringir esta regla:
Operación | Windows 8.1 | Windows 10 (1607 y versiones anteriores) | Windows 10 (1703 y versiones posteriores) |
---|---|---|---|
CreateWindow (en proceso) | N/D | Hereda secundaria (modo mixto) | Hereda secundaria (modo mixto) |
CreateWindow (proceso cruzado) | Restablecimiento forzado (del proceso del autor de la llamada) | Hereda secundaria (modo mixto) | Restablecimiento forzado (del proceso del autor de la llamada) |
SetParent (en proceso) | N/D | Restablecimiento forzado (del proceso actual) | Error (ERROR_INVALID_STATE) |
SetParent (proceso cruzado) | Restablecimiento forzado (del proceso de la ventana secundaria) | Restablecimiento forzado (del proceso de la ventana secundaria) | Restablecimiento forzado (del proceso de la ventana secundaria) |