Compatibilidad de WinForms con pantallas de alto DPI ── Por qué se ve borroso o se rompe el diseño en monitores 4K, y cómo abordarlo de forma realista

· Actualizado el: · · WinForms, Alto DPI, Windows, .NET, C#, .NET Framework, Mantenimiento de sistemas heredados, UI, Asesoría técnica

“Desde que cambiamos a un portátil nuevo, el texto de la aplicación de negocio se ve borroso y cuesta leerlo.” “Al conectarla a un monitor 4K, los botones y las etiquetas quedaron superpuestos.” “Solo en el entorno al 125 % de un cliente, la vista previa del informe se rompe.” En los últimos años este tipo de consultas se ha disparado en las épocas de renovación de equipos. Las pantallas de alta resolución que superan Full HD y el escalado de pantalla del 125 al 200 % se han vuelto estándar incluso en los PC de oficina, y las aplicaciones WinForms creadas en la época de 96 DPI y 100 % ya no pueden mostrarse cómodamente tal cual. Puede decirse que este es, hoy en día, el tema más frecuente entre las consultas de mantenimiento de aplicaciones WinForms antiguas.

Lo complicado es que los síntomas parecen dispersos: “se ve borroso”, “se rompe el diseño”, “solo una parte se ve pequeña”. En realidad, las causas se pueden organizar en unas pocas categorías, y si se sabe qué síntoma corresponde a qué causa, es posible tanto corregirlo como decidir “hasta dónde llegar”. Este artículo organiza, desde una perspectiva práctica, la compatibilidad de las aplicaciones WinForms (tanto en .NET como en .NET Framework) con el alto DPI, tomando como base el mecanismo de escalado de DPI de Windows y los modos de reconocimiento de DPI, y abarcando desde los métodos de configuración hasta las trampas habituales y una estrategia de adopción por etapas.

Terminología usada en este artículo

Antes de llegar a las conclusiones, conviene fijar los términos que aparecerán repetidamente en el texto. Junto a cada uno se indica en qué capítulo del artículo se trata, así que, si en algún momento pierde el hilo, puede volver aquí.

Término Significado Detalle
DPI / escalado de pantalla Puntos por pulgada. Con 96 DPI como 100 %, 125 % equivale a 120 DPI y 150 % a 144 DPI Capítulo 2
Virtualización de DPI Medida de rescate del sistema operativo: para una aplicación que no declara compatibilidad con DPI, el SO le hace creer que “la pantalla es de 96 DPI” y amplía el resultado como si fuera un mapa de bits. El diseño no se rompe, pero toda la pantalla se ve borrosa1 Capítulo 2
Modo de reconocimiento de DPI Categoría con la que la aplicación declara al SO “hasta qué punto sabe manejar el DPI”. Son cuatro: Unaware / System Aware / Per-Monitor / Per-Monitor V22 Capítulo 3
System Aware Modo que “se ajusta al DPI del monitor principal en el momento de iniciar sesión”. Se ve nítido en el monitor principal, pero al moverla a otro monitor el SO la amplía y se ve borrosa Capítulo 3
PMv2 Abreviatura de Per-Monitor V2. Modo que sigue el DPI de cada monitor. El SO escala automáticamente el área no cliente, como la barra de título2 Capítulo 3
Escalado GDI Versión mejorada de la ampliación por mapa de bits: la aplicación permanece como Unaware, pero el SO amplía a nivel vectorial solo el texto y las figuras dibujados con GDI. Permite reducir el desenfoque del texto sin cambiar el código2 Capítulo 3 y sección 4.3
Manifiesto XML incrustado en el ejecutable. Es uno de los lugares donde se declara el modo de reconocimiento de DPI Capítulo 4
AutoScaleMode Mecanismo de escalado automático propio del formulario de WinForms, distinto del modo de reconocimiento de DPI Capítulo 5

1. Primero, la conclusión

  • En un entorno de alto DPI, que una aplicación antigua se vea borrosa no es un error, sino una medida de rescate del sistema operativo (la virtualización de DPI). Windows hace que las aplicaciones sin compatibilidad DPI dibujen a 96 DPI y luego amplía el resultado como mapa de bits, por lo que el diseño no se rompe, pero sí se difumina.1
  • En cambio, que “se vea nítido pero el diseño se rompa” significa que la aplicación declaró compatibilidad con DPI, pero el diseño no logra seguirla. El desenfoque y la rotura tienen causas opuestas, así que antes de actuar hay que distinguir cuál de los dos casos se está dando.
  • El primer paso es averiguar en qué modo de reconocimiento de DPI (Unaware / System Aware / Per-Monitor V2) está funcionando actualmente la propia aplicación. Hay que comprobar si la declaración se hace mediante el manifiesto, app.config o una llamada a la API (o si no se declara nada).3
  • El método de configuración varía según la generación. En .NET (Core 3.1 a .NET 8) se usa el archivo de proyecto o Application.SetHighDpiMode; desde .NET Framework 4.7 se usa app.config; en 4.6 o anteriores, lo realista es llegar hasta declarar System Aware en el manifiesto.45
  • El diseñador siempre debe abrirse en un monitor al 100 % (96 DPI). Si se abre y guarda un formulario en un entorno al 150 %, AutoScaleDimensions se reescribe, lo que produce el clásico accidente de romper el diseño de todo el equipo.6
  • La compatibilidad total (Per-Monitor V2) exige un esfuerzo considerable. Es realista adoptar un enfoque en dos etapas: primero “System Aware para que al menos el monitor principal se vea nítido”; después, la compatibilidad completa con PMv2, invirtiendo en proporción a la vida útil de la aplicación y al entorno de uso.
  • Como medida provisional cuando la propia empresa no puede o no llega a tiempo de modificar la aplicación, conviene conocer también la “configuración de alto DPI” en Propiedades del ejecutable → Compatibilidad, que el propio usuario puede sobrescribir; esto facilita mucho la atención de consultas.2

2. Por qué se ve borroso ── El mecanismo de la virtualización de DPI

El escalado de pantalla de Windows se expresa como un porcentaje que toma 96 DPI como 100 %. Así, 125 % equivale a 120 DPI, 150 % a 144 DPI y 200 % a 192 DPI. Si un monitor 4K (3840 × 2160) de 27 pulgadas se usa al 100 %, el texto resulta demasiado pequeño, por lo que Windows recomienda por defecto alrededor del 150 %. Es decir, la difusión de las pantallas de alta resolución significa, directamente, la difusión de entornos que funcionan a un DPI distinto de 96.

El problema es que muchas aplicaciones de escritorio antiguas se escribieron bajo la premisa de que “la pantalla siempre está a 96 DPI”. Las coordenadas, las fuentes y los iconos están escritos directamente en píxeles, así que si se dibujan tal cual en un entorno al 150 %, todo se ve físicamente a 2/3 de su tamaño. Por eso Windows le miente a las aplicaciones que no declaran compatibilidad con DPI diciéndoles que “la pantalla está a 96 DPI”, y amplía como mapa de bits el resultado que dibujaron pensando que estaban a 96 DPI. Esto es la virtualización de DPI (bitmap stretching, o ampliación de mapa de bits).1

Con este mecanismo en mente, los síntomas y las causas se corresponden con claridad. Use esta tabla para la primera clasificación.

Síntoma Causa Estado
Todo se ve difuso y borroso de manera uniforme; el diseño no se rompe Virtualización de DPI (el SO amplía como mapa de bits) Sin compatibilidad DPI (Unaware). Seguro pero poco nítido
El texto se ve nítido, pero los controles se superponen o quedan cortados Se declaró compatibilidad con DPI, pero el diseño no la sigue Compatibilidad DPI a medias. A partir de aquí hay que corregir
Se ve nítido en el monitor principal, pero borroso al pasar a un monitor secundario System Aware (el DPI queda fijado al del monitor principal en el inicio) Comportamiento esperado. Hay que decidir si pasar a PMv2
El tamaño del texto es correcto, pero solo los iconos o imágenes se ven pequeños o toscos Los recursos de mapa de bits siguen pensados para 96 DPI Recursos de imagen sin adaptar (capítulo 6)
Solo se rompe una pantalla o control en concreto Diseño con coordenadas fijas, dibujo propio, control de terceros Objeto de corrección individual (capítulo 6)

Lo importante es que una aplicación “que se ve borrosa” en realidad no está rota: la medida de rescate del sistema operativo está funcionando correctamente. Modificarla significa rechazar esa medida de rescate y declarar “yo me encargo de mi propio escalado” (es decir, subir el modo de reconocimiento de DPI), así que, en el instante en que se hace esa declaración, todos los problemas de diseño pasan a ser responsabilidad propia. Las consultas del tipo “probé a pasar a PMv2 sin más y quedó todavía peor” surgen precisamente en este contexto.

2.1 Cómo reproducir y comprobar el síntoma en su propio equipo

Este artículo no incluye capturas de pantalla. La diferencia entre “borroso” y “roto” se entiende con más certeza alternando la configuración en un equipo real que viéndola en una imagen estática. Siga los pasos siguientes para distinguir con sus propios ojos la primera y la segunda fila de la tabla anterior. Esto se puede comprobar incluso con un solo monitor.

  1. Alterne el porcentaje de escalado entre 100 % y 150 %. Se selecciona en Inicio > Configuración > Sistema > Pantalla > “Escala y diseño” > “Cambiar el tamaño del texto, las aplicaciones y otros elementos”.7 La diferencia se nota más al comparar 100 % con 150 %.
  2. Reinicie siempre la aplicación. Ni las aplicaciones Unaware ni las System Aware siguen el nuevo porcentaje de escalado mientras están en ejecución. Reinícielas cada vez que cambie el escalado. En el caso de System Aware, además, solo cuando se cambia el monitor principal es necesario cerrar y volver a iniciar sesión (porque el DPI del sistema queda fijado en el momento de iniciar sesión).
  3. Compare los detalles con la Lupa. Se inicia con la tecla del logotipo de Windows + + (más).8 Suba el zoom hasta alrededor del 400 % y determine cuál de los dos casos siguientes se aplica.
    • El contorno del texto se ve borroso de manera uniforme y las líneas se difuminan en tonos intermedios ── es virtualización de DPI (primera fila de la tabla). Lo decisivo es que el texto, las líneas y los iconos se degradan por igual; no es un problema de la aplicación.
    • El texto se ve nítido, pero los botones y las etiquetas se superponen o los bordes quedan cortados ── es el estado en que se declaró compatibilidad con DPI pero el diseño no la sigue (segunda fila de la tabla). A partir de aquí es objeto de corrección en el capítulo 6.
  4. Observe solo los iconos. Si el texto se ve nítido pero los iconos del ToolStrip se ven diminutos o toscos, corresponde a la cuarta fila de la tabla (mapas de bits pegados directamente para 96 DPI).
  5. Mueva la ventana entre monitores (si dispone de dos o más). Arrastre la ventana a un monitor con un porcentaje de escalado distinto; si solo se ve borrosa en el destino, es System Aware (tercera fila de la tabla) y se trata del comportamiento esperado.

Por lo demás, casi se puede deducir en qué modo funciona la propia aplicación a partir de este aspecto visual. Si se ve nítida tanto al 100 % como al 150 %, y sigue nítida al moverla entre monitores, equivale a PMv2; si solo se ve nítida en el monitor principal, es System Aware; si se ve borrosa de manera uniforme en cualquier entorno, es Unaware. La comprobación del lugar de declaración (manifiesto, app.config, llamada a la API) se hace en el capítulo 4.

3. Los modos de reconocimiento de DPI, organizados

Cada aplicación declara al sistema operativo, por proceso (o, más exactamente, desde Windows 10 en adelante, por ventana de nivel superior), hasta qué punto sabe manejar el DPI. En la práctica son cuatro modos.12

Modo SO en que se introdujo DPI que ve la aplicación Al moverse entre monitores / cambiar el DPI Posición realista en WinForms
Unaware ── Siempre 96 El SO amplía como mapa de bits (se ve borroso) Valor por defecto cuando no se declara nada. Se ve borroso, pero no se rompe
System Aware Vista Fijo al DPI del monitor principal en el momento de iniciar sesión Fuera del monitor principal, o tras un cambio, el SO amplía (se ve borroso) Disponible en todas las generaciones. La opción principal para un enfoque pragmático
Per-Monitor (V1) 8.1 El DPI del monitor donde está la ventana Solo se notifica a la ventana de nivel superior. El escalado corre íntegramente por cuenta propia Sin soporte de framework y de poca utilidad práctica. No elegir
Per-Monitor V2 10 (1703) El DPI del monitor donde está la ventana Se notifica hasta las ventanas hijas. El área no cliente la escala el SO automáticamente La opción principal para la compatibilidad total. Disponible en .NET Framework 4.7+ y en .NET

En la práctica, la diferencia entre Per-Monitor V1 y V2 es decisiva. V1 es un mecanismo pensado para Win32 puro, en el que “llega la notificación pero todo lo demás corre por cuenta propia”, y no hay motivo para usarlo desde WinForms. En V2, el sistema operativo escala automáticamente el área no cliente —la barra de título, las barras de desplazamiento, los menús—, y el soporte del framework de WinForms (que se explica más adelante) también está construido partiendo de V2. Si se opta por Per-Monitor, V2 es la única opción razonable.2

Existe además otro modo: el escalado GDI (DPI_AWARENESS_CONTEXT_UNAWARE_GDISCALED, disponible desde Windows 10 1809).2 La aplicación permanece como Unaware, pero es una versión mejorada de la ampliación por mapa de bits en la que el sistema operativo amplía a nivel vectorial solo el texto y las figuras dibujados con GDI. Como puede reducir el desenfoque del texto sin cambiar el código en absoluto, vale la pena tenerlo presente como medida provisional para aplicaciones que no se pueden modificar (sección 4.3).

Cabe señalar que, desde Windows 10 1607, también existe el Mixed-Mode, que permite combinar distintos modos por ventana de nivel superior; a nivel de Win32 se admite una migración gradual del tipo “solo la pantalla principal en PMv2, mientras que los cuadros de diálogo que no se pueden corregir del todo se dejan como Unaware para que el SO los amplíe”.9 No es algo que se pueda usar sin más desde WinForms, pero la filosofía de diseño de que “no hace falta corregir toda la pantalla de una sola vez” enlaza con la estrategia por etapas del capítulo 7.

4. Método de configuración ── La respuesta correcta según la generación

La forma de declarar el modo de reconocimiento de DPI depende de la generación a la que pertenezca la propia aplicación. Confundir este punto hace perder tiempo con el clásico “lo configuré pero no funciona”, así que lo explico separado por generación.

4.1 WinForms en .NET (Core 3.1 a .NET 8)

En .NET existe Application.SetHighDpiMode, al que llama ApplicationConfiguration.Initialize() (generado por la plantilla desde .NET 6 en adelante). El valor por defecto es SystemAware.4 Se recomienda escribir la configuración en el archivo de proyecto, ya que el diseñador de Visual Studio también consulta ese valor.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWindowsForms>true</UseWindowsForms>
    <!-- SystemAware (predeterminado) / PerMonitorV2 / DpiUnaware / DpiUnawareGdiScaled -->
    <ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
  </PropertyGroup>
</Project>

Tenga en cuenta que ApplicationHighDpiMode en el archivo de proyecto y ApplicationConfiguration.Initialize() son mecanismos que se introdujeron en .NET 6.4 En proyectos de .NET Core 3.1 / .NET 5, escribir esta propiedad no tiene ningún efecto, así que hay que llamar directamente a Application.SetHighDpiMode desde el código, como se muestra a continuación (lo mismo aplica a un Main de estilo antiguo que no use ApplicationConfiguration.Initialize()). En ambos casos, la llamada debe hacerse antes de crear una sola ventana.

[STAThread]
static void Main()
{
    Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
}

Desde .NET 6 en adelante ha mejorado el escalado de los controles contenedores y las ventanas hijas MDI en PMv2, y problemas que persistían hasta .NET 5 —como que “los controles se desalinean al mover la ventana de un monitor al 200 % a uno al 100 %”— se han resuelto en buena medida.4 Si se va a abordar en serio la compatibilidad con PMv2, en la práctica resulta claramente más manejable hacerlo en .NET que en .NET Framework. Los elementos a considerar para plantearse una migración desde .NET Framework están reunidos en “Lista de verificación previa a la migración de .NET Framework a .NET”.

4.2 .NET Framework 4.7 en adelante

En .NET Framework, la versión 4.7 reforzó de forma considerable la compatibilidad con el alto DPI: se mejoró el escalado de los controles y se incorporaron los eventos de cambio de DPI (la familia DpiChanged) y la propiedad DeviceDpi, entre otros. Sin embargo, es una función opcional (opt-in) que solo se activa configurando en conjunto los dos puntos siguientes.5

En primer lugar, se declara la compatibilidad con Windows 10 en el manifiesto (sin esto, las funciones de alto DPI de la versión 4.7 no se activan en absoluto).

<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
  <application>
    <!-- Declaración de compatibilidad con Windows 10 -->
    <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
  </application>
</compatibility>

A continuación, se declara el modo de reconocimiento de DPI en System.Windows.Forms.ApplicationConfigurationSection dentro de app.config.

<configuration>
  <System.Windows.Forms.ApplicationConfigurationSection>
    <add key="DpiAwareness" value="PerMonitorV2" />
    <!-- Si ya existen pantallas escaladas manualmente, esta función individual se puede desactivar -->
    <!-- <add key="EnableWindowsFormsHighDpiAutoResizing" value="false" /> -->
  </System.Windows.Forms.ApplicationConfigurationSection>
</configuration>

Compruebe también que se llama a Application.EnableVisualStyles() al principio de Main. Hay un punto de atención importante aquí: el método tradicional de escribir <dpiAware> / <dpiAwareness> en el manifiesto sobrescribe la configuración de app.config, por lo que la recomendación oficial para WinForms en 4.7 es no combinar ambos.5 Si va a actualizar a 4.7 + PMv2 una aplicación en la que en su momento se escribió <dpiAware>true</dpiAware> en el manifiesto para pasar a System Aware, empiece por poner en orden esa declaración del manifiesto. La causa habitual de “lo escribí en app.config pero no funciona” suele ser esta.

4.3 .NET Framework 4.6 o anterior, y combinaciones con VB6/MFC

En WinForms de la versión 4.6 o anterior no existe código de framework que dé soporte a PMv2, así que declararlo a la fuerza obligaría a gestionar por cuenta propia toda la rotura del diseño. La solución realista para esta generación es llegar hasta declarar System Aware en el manifiesto. El manifiesto es el medio recomendado para la declaración a nivel de sistema operativo, y si se escriben juntos <dpiAware> (desde Vista) y <dpiAwareness> (desde Windows 10 1607), se interpretan correctamente tanto en versiones antiguas como nuevas de Windows.3

<asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
  <asmv3:windowsSettings>
    <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
    <dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">system</dpiAwareness>
  </asmv3:windowsSettings>
</asmv3:application>

Al pasar a System Aware, el escalado automático de WinForms (capítulo 5) se activa una sola vez, ajustándose al DPI del monitor principal en el momento del inicio, y en ese monitor se ve nítido. Con un formulario cuyo AutoScaleMode.Font esté correctamente configurado, esta sola declaración suele dejar la aplicación en un estado bastante presentable. También existe la posibilidad de declararlo en tiempo de ejecución mediante API como SetProcessDpiAwareness, pero no se puede cambiar una vez creada la ventana, y la recomendación oficial es declararlo en el manifiesto.10 En aplicaciones que combinan código de VB6 o MFC, el planteamiento es el mismo: el manifiesto del ejecutable determina el modo de todo el proceso (como MFC en sí no cuenta con soporte de escalado automático, la revisión de pantallas después de declarar System Aware es imprescindible; para el tratamiento de este tipo de activos, consulte también “Qué es MFC en Windows”).

También presento aquí una medida provisional que puede aplicar el propio usuario cuando la modificación en sí no es posible: desde las Propiedades del ejecutable, el usuario puede sobrescribir el escalado de DPI de esa aplicación.2 A continuación se enumeran, en orden, solo los nombres de los elementos en pantalla, para poder leerlos tal cual por teléfono.

  1. Haga clic con el botón derecho en el archivo .exe correspondiente y seleccione “Propiedades”. Si solo tiene a mano un acceso directo, use la opción “Abrir ubicación del archivo” del clic derecho para ir a la carpeta donde está el archivo real.
  2. Abra la pestaña “Compatibilidad”.
  3. Pulse el botón “Cambiar la configuración de alto DPI”.
  4. En el cuadro de diálogo que se abre, active la casilla “Reemplazar el comportamiento de escala en alto DPI”.
  5. Justo debajo, elija el comportamiento en el menú desplegable “Escalado realizado por”. La opción “Sistema” fuerza la virtualización de DPI (no se rompe, pero se ve borroso); la opción “Sistema (mejorado)” aplica el escalado GDI mencionado en el capítulo 3, y solo el texto dibujado con GDI se ve nítido.2
  6. Cierre los dos cuadros de diálogo con “Aceptar” y reinicie la aplicación para comprobar el resultado.

Cuál de las dos opciones, “Sistema” o “Sistema (mejorado)”, es mejor depende de cada aplicación, así que lo más seguro es decidirlo comparando con el procedimiento de la sección 2.1. No es una solución universal, pero vale la pena incluirla en el manual de soporte como recurso que se puede indicar por teléfono cuando “hoy mismo hay que resolverlo en casa del cliente”. Las propiedades de un acceso directo no tienen esta opción, así que, al dar las indicaciones, no olvide en ningún caso el único punto crítico: pedir que se abra el ejecutable real.

5. AutoScaleMode y la trampa del diseñador

Además del modo de reconocimiento de DPI, WinForms cuenta con un mecanismo de escalado automático propio de cada formulario. Sin entender esto, aunque se active System Aware, el escalado no funcionará correctamente.

El mecanismo funciona así: en tiempo de diseño, cada formulario (ContainerControl) registra el criterio de escalado en AutoScaleMode y el valor de referencia del entorno en que se diseñó en AutoScaleDimensions. En tiempo de ejecución, ese valor se compara con el del entorno actual (CurrentAutoScaleDimensions) y, si hay diferencia, se amplían o reducen en bloque los controles hijos.11 Es decir, es un mecanismo que absorbe la “diferencia de entorno entre el diseño y la ejecución”, y el valor de referencia queda grabado en el código (Designer.cs).

En la práctica, AutoScaleMode tiene dos opciones.

AutoScaleMode Criterio Características
Font (recomendado, valor por defecto) Las dimensiones de la fuente del formulario Como el tamaño real de la fuente del sistema también cambia al subir el DPI, esto sirve a la vez para seguir el DPI. También sigue los cambios de fuente que haga el usuario
Dpi El DPI de la pantalla Proporcional únicamente al DPI. Orientado a pantallas centradas en gráficos
None ── Sin escalado automático. Suele ser el caso de las aplicaciones que escriben directamente en 96 DPI

Si tiene dudas, use Font. Como punto de atención, está documentado oficialmente que mezclar modos distintos entre el formulario base y los formularios derivados produce resultados inesperados.11 En aplicaciones que usan herencia de formularios, lo primero es hacer un inventario y unificar el modo en todos los formularios.

Y aquí está la trampa central. Como AutoScaleDimensions es un mecanismo que registra “el valor del entorno en que se diseñó”, si se abre el diseñador de Visual Studio en un monitor al 150 % y se guarda el formulario, AutoScaleDimensions en Designer.cs se reescribe con el valor correspondiente al 150 % (en modo Font, por ejemplo, 6F, 12F pasa a ser 9F, 18F). Las coordenadas y los tamaños también se guardan multiplicados por 1,5. Cuando un miembro del equipo en un entorno al 100 % compila y ejecuta ese formulario, todo aparece encogido, y en la revisión de diferencias aparece un diff enorme con las coordenadas de todos los controles cambiadas. “A un nuevo miembro del equipo le dieron un portátil de alto DPI, tocó un solo formulario y el diseño de todo el repositorio se rompió”: este es el accidente clásico de las consultas sobre alto DPI.

Este accidente siempre aparece en el git diff antes del commit. Si al abrir y guardar un solo formulario aparece en Designer.cs una diferencia como la siguiente, es la prueba de que se abrió en un entorno al 150 %.

-            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
+            this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 18F);

A continuación de esta línea aparecen diferencias en las que ClientSize y las propiedades Location / Size de todos los controles quedan reescritas, en bloque, con valores multiplicados por 1,5. En la revisión solo hace falta mirar esta línea de AutoScaleDimensions: si ha cambiado, se puede rechazar el cambio sin necesidad de leer el resto de las diferencias de coordenadas.

En principio, la solución es una sola: abrir el diseñador al 100 % (96 DPI). Visual Studio en sí es una aplicación compatible con DPI, pero el diseñador de WinForms (para .NET Framework) no lo es, así que al abrirlo en un monitor de alto DPI aparece una barra de información amarilla que invita a “reiniciar Visual Studio con el escalado al 100 %”.6 No incluyo capturas de pantalla, pero el procedimiento es el siguiente.

  1. En Visual Studio, en un monitor de alto DPI, abra en el diseñador un formulario de un proyecto de .NET Framework.
  2. En la parte superior del diseñador aparece una barra de información amarilla que invita a reiniciar con el escalado al 100 %. No guarde el formulario en este estado; se produciría el diff mostrado antes.
  3. Desde el enlace de la barra de información, reinicie Visual Studio. Todo VS se inicia en modo sin compatibilidad DPI, por lo que su propia interfaz se ve un poco borrosa, pero ese es el estado normal.
  4. En ese estado, edite y guarde el formulario, y confirme con git diff que AutoScaleDimensions no ha cambiado.
  5. Cuando termine el trabajo, reinicie Visual Studio normalmente para desactivar el modo sin compatibilidad DPI.

Para iniciar directamente en este estado desde la línea de comandos, se usa devenv /noScale. Además, en proyectos de .NET 6 en adelante, a partir de Visual Studio 2022 17.8 se puede establecer <ForceDesignerDPIUnaware>true</ForceDesignerDPIUnaware> en el archivo de proyecto para que solo la pestaña del diseñador funcione sin compatibilidad DPI, sin necesidad de reiniciar todo VS (no está disponible en proyectos de .NET Framework).6 Si el proyecto es de .NET 6 en adelante, esto elimina por completo la necesidad de los pasos 3 a 5, así que considere primero esta opción.

Como forma sencilla y eficaz de automatizar la verificación, conviene “rechazar en el CI o en un hook de pre-commit cualquier diferencia en la que AutoScaleDimensions de Designer.cs tome un valor distinto del establecido”. Detener el accidente en la puerta de entrada del commit sale más barato que corregirlo después de que haya ocurrido.

6. Patrones típicos de rotura del diseño y cómo corregirlos

Los puntos que se rompen al subir (o al intentar subir) el modo de reconocimiento de DPI se agrupan, en general, en los siguientes patrones.

Qué se rompe Causa Cómo corregirlo
Controles superpuestos o cortados Diseño con coordenadas y tamaños fijos Sustituir por Anchor/Dock, TableLayoutPanel / FlowLayoutPanel. Aprovechar AutoSize
Iconos o imágenes pequeños o toscos Mapas de bits pegados directamente para 96 DPI Preparar imágenes en varias resoluciones y cambiar según el DPI. Si es posible, reducir a partir de una imagen más grande
Dibujo propio (gráficos, planos, vista previa de informes) Coordenadas escritas directamente en píxeles sobre Graphics Escalar coordenadas, grosor de línea y fuente tomando como base DeviceDpi
Las filas de DataGridView quedan apretadas RowHeight especificado en píxeles Usar AutoSizeRowsMode, o establecer un valor escalado según el DPI
Los iconos de ToolStrip se ven diminutos Tamaño fijo de 16×16 Establecer ImageScalingSize según el DPI
Solo se rompe un control de terceros o ActiveX en concreto El propio control no admite DPI Actualizar a la versión compatible del proveedor. Si no existe, ese es el límite de lo que se puede lograr

El grueso del esfuerzo está en sustituir el diseño. Dicho de otro modo, una pantalla ya construida con TableLayoutPanel y Dock apenas requiere trabajo aunque se suba el modo de reconocimiento de DPI. Basta con establecer como norma que las pantallas nuevas se construyan desde el principio con paneles de diseño para reducir la deuda futura.

La forma básica de escalar el dibujo propio consiste en convertir los valores calculados para 96 DPI tomando como referencia Control.DeviceDpi (disponible en .NET Framework 4.7+ y en .NET).

public partial class ChartPanel : Panel
{
    // Convierte el valor de diseño basado en 96 DPI al DPI actual
    private int Scale(int value96) => value96 * DeviceDpi / 96;

    protected override void OnPaint(PaintEventArgs e)
    {
        using var pen = new Pen(Color.Navy, Scale(2));
        e.Graphics.DrawRectangle(pen,
            Scale(16), Scale(16), Scale(320), Scale(120));
    }

    // En PMv2 el DPI cambia al mover la ventana entre monitores, así que se fuerza el redibujado
    protected override void OnDpiChangedAfterParent(EventArgs e)
    {
        base.OnDpiChangedAfterParent(e);
        Invalidate();
    }
}

Hasta System Aware, el DPI queda fijado en el inicio, así que basta con introducir el método Scale; pero en PMv2, DeviceDpi cambia cada vez que la ventana se mueve entre monitores, por lo que hace falta un diseño que reconstruya, mediante los eventos de la familia DpiChanged, las fuentes, imágenes y valores de diseño que se tengan en caché.5 “Cuántas pantallas tienen dibujo propio” repercute directamente en la estimación de esfuerzo para la compatibilidad con PMv2.

Los controles de terceros y los componentes ActiveX/OCX son el factor que determina el límite superior de esta modificación. Si el control en sí no es compatible con DPI, por más que se esfuerce el lado anfitrión, esa pantalla nunca quedará completamente resuelta. Compruebe el estado de soporte del proveedor y, para los ActiveX que no se puedan actualizar, decida cómo tratarlos con la tabla de decisión de “Mantener, envolver o reemplazar ActiveX/OCX” antes de fijar la meta de compatibilidad con alto DPI.

7. Estrategia de adopción por etapas ── Una tabla de decisión sobre hasta dónde llegar

Con lo visto hasta aquí, el nivel de adopción se puede organizar en tres etapas. Llevar todas las aplicaciones a PMv2 es el ideal técnico, pero, considerando el presupuesto de modificación y la vida útil de la aplicación, en muchos casos lo acertado es marcar un límite realista.

  (1) No hacer nada (Unaware) (2) Pasar a System Aware (3) Compatibilidad completa con PMv2
Aspecto visual Se ve borroso en todos los entornos (sin romperse) Nítido en el monitor principal. Borroso en el monitor secundario o al cambiar el DPI Nítido en todos los monitores
Trabajo principal Ninguno Declaración en el manifiesto o la configuración + revisión de AutoScaleMode + comprobación visual de todas las pantallas (2) + revisión completa del diseño + adaptación a DPI del dibujo propio y los recursos de imagen + gestión de DpiChanged
Esfuerzo estimado Cero Bajo a medio (principalmente trabajo de verificación proporcional al número de pantallas) Alto (principalmente corrección de roturas. Los controles de terceros pueden marcar el límite superior)
Casos adecuados Se va a retirar en pocos años. Los usuarios lo aceptan Aplicación interna centrada en un escritorio fijo y un solo monitor. Como primera etapa Uso mixto de portátil y monitor externo. Aplicación principal de larga vida. Producto que se distribuye a clientes

Hay tres ejes de decisión: la vida útil de la aplicación (cuántos años más se va a usar), el entorno de uso (si todos los usuarios trabajan en el mismo escritorio fijo, System Aware prácticamente basta por sí solo; si predomina la combinación de portátil más monitor externo, System Aware —que se ve borroso cada vez que se mueve entre monitores— deja cierta insatisfacción), y el presupuesto de modificación. El enfoque recomendado es aplicar primero (2) como estándar para todas las aplicaciones y, a partir del volumen de roturas encontradas en el proceso y de las limitaciones de los controles de terceros, decidir si avanzar a (3) o en qué pantallas hacerlo. La opción (2) se centra en “declarar y verificar”, con poco riesgo de fallo, y aun así mejora notablemente la experiencia percibida por el usuario.

También conviene mencionar las pruebas. Los problemas de alto DPI a menudo no se reproducen en el equipo de desarrollo, así que hay que crear deliberadamente los siguientes entornos para comprobarlos.1

  • Conectar dos monitores con escalados distintos (por ejemplo, la pantalla integrada del portátil al 150 % y un monitor externo al 100 %) y mover la ventana de uno a otro
  • Cambiar el monitor principal, volver a iniciar sesión y luego iniciar la aplicación (el DPI del sistema queda fijado en el momento de iniciar sesión, así que no cambia si no se vuelve a iniciar sesión)
  • Cambiar la configuración de escalado mientras la aplicación está en ejecución
  • Conectarse mediante escritorio remoto desde un cliente de alto DPI (en RDP se traslada el DPI del lado del cliente, por lo que es habitual el reporte de que “en la consola del servidor se ve bien, pero por RDP se rompe”)

La configuración, en forma de diagrama, es la siguiente. La base es un equipo físico con dos monitores, a lo que se añaden con máquinas virtuales los porcentajes de escalado que falten, y por último se suma la verificación a través de RDP.

Entorno de verificación de alto DPI para WinFormsDiagrama que muestra un equipo físico con dos monitores a distinta escala, máquinas virtuales con otras escalas y una conexión de escritorio remoto, todos apuntando a la aplicación WinForms bajo verificación.Máquinas virtuales ── cubren las escalas que faltanEquipo físico 1 ── base con 2 monitoresArrastrar la ventana de un lado a otro125%200%Pantalla integrada 150%configurada como monitor principalMonitor externo 100%Aplicación WinForms bajo verificaciónEscritorio remotoconexión desde un cliente de alto DPI

La flecha de ida y vuelta del diagrama representa la operación en la que más claramente se nota la diferencia entre System Aware y PMv2. Para cambiar el monitor principal, se intercambian los papeles de NB y EXT dentro de PHYS, volviendo a iniciar sesión cada vez para comprobarlo.

Cuando llega un reporte de que “solo se rompe al 125 %”, la experiencia dice que, al final, lo más rápido es reproducir ese porcentaje y verlo directamente. Como el escalado también se puede cambiar en una máquina virtual, tener preparados entornos de verificación al 100, 125, 150 y 200 % agiliza la clasificación de los casos.

8. Resumen

En un entorno de alto DPI, “se ve borroso” corresponde a una medida de rescate del sistema operativo (la virtualización de DPI), y “se rompe el diseño” corresponde a una compatibilidad con DPI a medias. Con solo tener clara esta correspondencia, es posible partir del síntoma para deducir la causa y la solución. El método de configuración varía por generación: en .NET, la propiedad ApplicationHighDpiMode del archivo de proyecto; desde .NET Framework 4.7, app.config (sin combinarlo con el dpiAware del manifiesto); en 4.6 o anteriores, llegar hasta System Aware en el manifiesto. Además, unificar AutoScaleMode.Font y establecer como norma operativa que el diseñador se abra siempre al 100 % son medidas poco vistosas, pero son las que más reducen los accidentes.

A partir de ahí, hasta dónde llegar se decide entre las tres etapas del capítulo 7. Primero, pasar a System Aware para que el monitor principal se vea nítido y, si el entorno de uso y la vida útil de la aplicación lo justifican, avanzar a PMv2: este enfoque en dos etapas es el que recomendamos en la práctica en nuestros propios proyectos de modificación. Si ha llegado el momento de replantearse el propio framework de UI (WPF está diseñado desde el principio para manejar bien el DPI), tenga también en cuenta “Cómo elegir entre WinForms, WPF y WinUI”. Si tiene dudas sobre hasta qué etapa se puede corregir su aplicación, podemos ayudarle empezando por un inventario de la composición de pantallas y controles.

Artículos relacionados

Áreas de consultoría relacionadas

KomuraSoft LLC atiende consultas sobre la compatibilidad con alto DPI de aplicaciones WinForms / MFC antiguas (investigación de la situación actual, decisión del nivel de adopción, modificación del diseño), la investigación de las causas de problemas de visualización derivados de la renovación de equipos, y la renovación de la interfaz de usuario.

Referencias

  1. Microsoft Learn, High DPI Desktop Application Development on Windows. Sobre el contexto del escalado de DPI, el comportamiento de cada modo de reconocimiento de DPI, el mecanismo por el que las aplicaciones sin compatibilidad DPI se amplían como mapa de bits, y los puntos a tener en cuenta al probar en entornos de DPI mixto.  2 3 4 5

  2. Microsoft Learn, DPI_AWARENESS_CONTEXT handle. Sobre la definición y el comportamiento de cada contexto: Unaware / System Aware / Per-Monitor / Per-Monitor V2 / UNAWARE_GDISCALED (escalado GDI, desde Windows 10 1809).  2 3 4 5 6 7 8 9

  3. Microsoft Learn, Setting the default DPI awareness for a process. Sobre el método de declaración mediante los elementos dpiAware / dpiAwareness del manifiesto, la relación de prioridad entre ambos, y por qué no se recomienda la declaración mediante API.  2

  4. Microsoft Learn, What’s new in Windows Forms .NET 6. Sobre el arranque mediante ApplicationConfiguration.Initialize, la configuración ApplicationHighDpiMode del archivo de proyecto (SystemAware por defecto), y las mejoras del escalado PerMonitorV2 en .NET 6.  2 3 4

  5. Microsoft Learn, High DPI support - Windows Forms. Sobre el contenido del refuerzo de alto DPI de .NET Framework 4.7, la sección System.Windows.Forms.ApplicationConfigurationSection de app.config (DpiAwareness=PerMonitorV2), por qué la declaración en el manifiesto no se recomienda al sobrescribir app.config, y los eventos de la familia DpiChanged junto con DeviceDpi.  2 3 4

  6. Microsoft Learn, Fix DPI display issues in Windows Form Designer. Sobre la falta de compatibilidad DPI del diseñador de WinForms, el reinicio con escalado al 100 % desde la barra de información que aparece en monitores de alto DPI, devenv /noScale, y ForceDesignerDPIUnaware para .NET 6+.  2 3

  7. Soporte de Microsoft, Cambiar la resolución de pantalla y el diseño en Windows. Sobre el procedimiento para elegir el porcentaje de escalado desde “Escala y diseño” > “Cambiar el tamaño del texto, las aplicaciones y otros elementos” en Inicio > Configuración > Sistema > Pantalla. 

  8. Soporte de Microsoft, Usar la Lupa para ver mejor los elementos de la pantalla. Sobre cómo iniciar la Lupa con la tecla del logotipo de Windows + el signo más para ampliar una parte de la pantalla. 

  9. Microsoft Learn, Mixed-Mode DPI Scaling and DPI-aware APIs. Sobre la combinación de modos de reconocimiento de DPI por ventana de nivel superior mediante SetThreadDpiAwarenessContext, y las API compatibles con DPI como GetDpiForWindow. 

  10. Microsoft Learn, SetProcessDpiAwareness function. Sobre la configuración del reconocimiento de DPI por defecto del proceso mediante API, por qué se recomienda la declaración en el manifiesto, y que no se puede cambiar una vez establecido. 

  11. Microsoft Learn, Automatic form scaling - Windows Forms. Sobre el funcionamiento del escalado automático mediante AutoScaleMode / AutoScaleDimensions / CurrentAutoScaleDimensions, y que la combinación de los modos Font y Dpi no es compatible.  2

Artículos recientes con las mismas etiquetas para profundizar en temas cercanos.

Estas páginas sitúan el tema en un contexto más amplio de servicios y decisiones.

El artículo está directamente relacionado con los siguientes servicios.

Preguntas frecuentes

Preguntas habituales en las consultas sobre el tema del artículo.

¿Por qué las aplicaciones WinForms se ven borrosas en un monitor 4K?
No es un error, sino una medida de rescate del sistema operativo (la virtualización de DPI). Para las aplicaciones que no declaran compatibilidad con DPI, Windows finge que "la pantalla es de 96 DPI" y amplía como si fuera un mapa de bits el resultado que la aplicación dibujó pensando que estaba a 96 DPI. Por eso el diseño no se rompe, pero sí se ve borroso. En cambio, cuando "se ve nítido pero el diseño se rompe", la aplicación sí declaró compatibilidad con DPI, pero el diseño no logra seguirla: la causa es exactamente la opuesta. El primer paso, antes de aplicar ninguna solución, es distinguir cuál de los dos síntomas se está presentando.
¿Cómo se configura la compatibilidad de WinForms con alto DPI?
El método depende de la generación. Desde .NET 6 en adelante se usa la propiedad ApplicationHighDpiMode del archivo de proyecto (SystemAware por defecto); en .NET Core 3.1 / .NET 5 hay que llamar a Application.SetHighDpiMode antes de crear cualquier ventana. Desde .NET Framework 4.7 en adelante se declara la compatibilidad con Windows 10 en el manifiesto y se usa la configuración DpiAwareness de app.config, pero como la declaración dpiAware del manifiesto sobrescribe la de app.config, la recomendación oficial es no combinar ambas. En 4.6 o versiones anteriores, lo realista es llegar hasta declarar System Aware en el manifiesto.
¿Qué conviene elegir, System Aware o Per-Monitor V2?
Recomendamos un enfoque en dos etapas. La primera etapa es pasar a System Aware: la aplicación se escala según el DPI del monitor principal en el momento del inicio y se ve nítida en ese monitor. Se trata principalmente de declarar y verificar, con poco riesgo de fallo, y para una aplicación interna centrada en un escritorio fijo esto suele bastar por sí solo. Per-Monitor V2 mantiene la nitidez en toda la pantalla incluso al moverse entre monitores, pero exige una revisión completa del diseño, adaptar a DPI el dibujo propio y los recursos de imagen, y gestionar el evento DpiChanged, lo que representa un esfuerzo considerable; si los controles de terceros no lo admiten, ese será el límite alcanzable. La decisión depende de la vida útil de la aplicación, el entorno de uso y el presupuesto de modificación.
¿Por qué se rompió el diseño al guardar un formulario en el diseñador?
Al abrir el diseñador de WinForms de Visual Studio en un monitor de alto DPI (por ejemplo, al 150 %) y guardar el formulario, AutoScaleDimensions en Designer.cs se reescribe con los valores correspondientes al 150 %, y las coordenadas y los tamaños también se guardan multiplicados por 1,5. Cuando un miembro del equipo que trabaja en un entorno al 100 % compila ese código, todo aparece encogido. La solución es abrir el diseñador al 100 % (96 DPI): en un monitor de alto DPI, la barra de información amarilla permite reiniciar con el escalado al 100 %. En proyectos de .NET 6 en adelante también se puede usar la configuración ForceDesignerDPIUnaware. Es útil además automatizar, mediante CI o un hook de pre-commit, el rechazo de diferencias inesperadas en AutoScaleDimensions.

Perfil del autor

Página de presentación del autor del artículo.

Go Komura

Representante de KomuraSoft LLC

Especializado en desarrollo de software para Windows, consultoría técnica e investigación de fallos, sobre todo en proyectos con sistemas existentes y errores difíciles de reproducir.

Volver al blog