¿Cómo escribir el código de WinForms que escala automáticamente a las configuraciones de fuente y ppp del sistema?

Introducción: hay muchos comentarios que dicen “WinForms no escala automáticamente a la configuración de PPP / fuente, cambia a WPF”. Sin embargo, creo que está basado en .NET 1.1; parece que realmente hicieron un buen trabajo al implementar escalado automático en .NET 2.0. Al menos basado en nuestra investigación y pruebas hasta el momento. Sin embargo, si algunos de ustedes saben mejor, nos encantaría saber de usted. (Por favor, no se moleste en argumentar que deberíamos cambiar a WPF … eso no es una opción en este momento).

Preguntas:

  • ¿Qué en WinForms NO escala automáticamente y, por lo tanto, debe evitarse?

  • ¿Qué pautas de diseño deberían seguir los progtwigdores cuando escriben el código de WinForms para que se auto-escale bien?

Pautas de diseño que hemos identificado hasta ahora:

Vea la respuesta wiki de la comunidad a continuación.

¿Alguno de esos es incorrecto o inadecuado? ¿Alguna otra guía que debemos adoptar? ¿Hay algún otro patrón que deba evitarse? Cualquier otra orientación sobre esto sería muy apreciada.

Controles que no admiten escalado correctamente:

  • Label con AutoSize = False y Font heredada. Establezca explícitamente Font en el control para que aparezca en negrita en la ventana Propiedades.
  • ListView anchos de columna ListView no se escalan. ScaleControl el ScaleControl del ScaleControl para hacerlo en su lugar. Ver esta respuesta
  • SplitContainer Panel1MinSize , Panel2MinSize y SplitterDistance Panel2MinSize
  • TextBox con MultiLine = True y Font heredada. Establezca explícitamente Font en el control para que aparezca en negrita en la ventana Propiedades.
  • Imagen de ToolStripButton . En el constructor del formulario:

    • Establecer ToolStrip.AutoSize = False
    • Establezca ToolStrip.ImageScalingSize acuerdo con CreateGraphics.DpiX y .DpiY
    • Establecer ToolStrip.AutoSize = True si es necesario.

    A veces, AutoSize se puede dejar en True pero a veces no se puede cambiar el tamaño sin esos pasos. Funciona sin esos cambios con .NET Framework 4.5.2 y EnableWindowsFormsHighDpiAutoResizing .

  • Las imágenes de TreeView . Establezca ImageList.ImageSize acuerdo con CreateGraphics.DpiX y .DpiY . Para StateImageList , funciona sin esos cambios con .NET Framework 4.5.1 y EnableWindowsFormsHighDpiAutoResizing .
  • Tamaño del Form Escala de forma fija el Form manualmente después de la creación.

Guía de diseño:

  • Todos los ContainerControls se deben establecer en el mismo AutoScaleMode = Font . (La fuente manejará los cambios de DPI y los cambios a la configuración de tamaño de fuente del sistema; DPI solo manejará los cambios de DPI, no los cambios a la configuración de tamaño de fuente del sistema).

  • Todos los ContainerControls también se deben establecer con AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); , suponiendo 96 ppp (ver la siguiente viñeta). Eso es auto-agregado por el diseñador basado en el DPI en el que se abre el diseñador … pero faltaba en muchos de nuestros archivos de diseñador más antiguos. Quizás Visual Studio .NET (la versión anterior a VS 2005) no estaba agregando eso correctamente.

  • Haga todo su trabajo de diseñador en 96 ppp (podríamos cambiar a 120 ppp, pero la sabiduría en Internet dice que se adhieren a 96 ppp; la experimentación está en orden allí, por diseño, no debería importar ya que solo cambia la línea AutoScaleDimensions que el diseñador inserta). Para configurar Visual Studio para que se ejecute en un 96dpi virtual en una pantalla de alta resolución, busque su archivo .exe, haga clic con el botón derecho para editar las propiedades y en Compatibilidad seleccione “Anular comportamiento de escalamiento de DPI elevado. Escala realizada por: Sistema”.

  • Asegúrese de no establecer nunca la Fuente en el nivel del contenedor … solo en los controles de la hoja. (Establecer la fuente en un contenedor parece desactivar el escalado automático de ese contenedor).

  • NO use Anchor Right o Bottom anclado a un UserControl … su posicionamiento no se escalará automáticamente; en su lugar, suelte un Panel u otro contenedor en su UserControl y Ancle sus otros controles a ese Panel; haga que el Panel use Dock Right o Dock Bottom en su UserControl.

  • Solo los controles en las listas de controles cuando se ResumeLayout a ResumeLayout al final de InitializeComponent se escalarán automáticamente … si dinámicamente agrega controles, entonces necesita SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout(); en ese control antes de agregarlo. Y su posición también deberá ajustarse si no está utilizando los modos Dock o un Administrador de diseño como FlowLayoutPanel o TableLayoutPanel .

  • Las clases base derivadas de ContainerControl deben dejar AutoScaleMode establecido en Inherit (el valor predeterminado establecido en la clase ContainerControl , pero NO el predeterminado establecido por el diseñador). Si lo configura para cualquier otra cosa, y luego su clase derivada intenta establecerlo en Fuente (como debería), el acto de configurarlo en Font borrará la configuración del diseñador de AutoScaleDimensions , lo que resultará en alternar entre escalado automático ! (Esta guía combinada con la anterior significa que nunca puede crear instancias de clases base en un diseñador … ¡todas las clases deben diseñarse como clases base o como clases de hojas!)

  • Evite usar Form.MaxSize estáticamente / en el Diseñador. MinSize y MaxSize en Formulario no escalan tanto como todo lo demás. Por lo tanto, si realiza todo su trabajo en 96 ppp, cuando en PPP superior, su MinSize no causará problemas, pero puede no ser tan restrictivo como esperaba, pero su MaxSize puede limitar el escalado de su Tamaño, lo que puede causar problemas. Si quiere MinSize == Size == MaxSize , no lo haga en el Diseñador … haga eso en su constructor o sobreescriba OnLoad … establezca tanto MinSize como MaxSize en su Tamaño de escala apropiada.

  • Todos los Controles en un Panel o Container en particular deberían usar Anclaje o Acoplamiento. Si los mezclas, la escala automática realizada por ese Panel a menudo se portará mal en formas sutilmente extrañas.

Mi experiencia ha sido bastante diferente a la respuesta actual mejor votado. Al recorrer el código del framework .NET y leer detenidamente el código fuente de referencia, llegué a la conclusión de que todo está en su lugar para que el escalado automático funcione, y que solo había un problema sutil en algún lugar al desordenarlo. Esto resultó ser cierto.

Si crea un diseño correctamente reflujo / tamaño automático, entonces casi todo funciona exactamente como debería, automáticamente, con la configuración predeterminada utilizada por Visual Studio (es decir, AutoSizeMode = Font en el formulario principal, y Heredar en todo lo demás).

El único problema es si ha establecido la propiedad Font en el formulario del diseñador. El código generado ordenará las asignaciones alfabéticamente, lo que significa que AutoScaleDimensions se asignará antes que Font . Desafortunadamente, esto rompe por completo la lógica de escalado automático de WinForms.

La solución es simple sin embargo. O bien, no establezca la propiedad Font en el diseñador en absoluto (configúrelo en su constructor de formularios), o reordene manualmente estas asignaciones (pero debe seguir haciendo esto cada vez que edite el formulario en el diseñador). Voila, escalado casi perfecto y totalmente automático con una molestia mínima. Incluso los tamaños de forma se escalan correctamente.


Enumeraré problemas conocidos aquí cuando los encuentro:

  • El TableLayoutPanel nested calcula los márgenes de control incorrectamente . No se conocen soluciones para evitar márgenes y rellenos por completo, o evitar paneles de diseño de tablas nesteds.

Oriente su aplicación para .Net Framework 4.7 y ejecútelo en Windows 10 v1703 (Creators Update Build 15063). Con .Net 4.7 bajo Windows 10 (v1703), MS hizo una gran cantidad de mejoras DPI .

A partir de .NET Framework 4.7, Windows Forms incluye mejoras para escenarios comunes de alto DPI y dynamic DPI. Éstas incluyen:

  • Mejoras en la escala y el diseño de varios controles de Windows Forms, como el control MonthCalendar y el control CheckedListBox.

  • Escala de un solo paso. En .NET Framework 4.6 y versiones anteriores, el escalado se realizó a través de varias pasadas, lo que ocasionó que algunos controles se escalasen más de lo necesario.

  • Soporte para escenarios dynamics DPI en los cuales el usuario cambia el DPI o el factor de escala después de que se ha lanzado una aplicación Windows Forms.

Para admitirlo, agregue un manifiesto de aplicación a su aplicación y señale que su aplicación es compatible con Windows 10:

       

A continuación, agregue un app.config y declare la aplicación Per Monitor Aware. ¡Esto se hace AHORA en app.config y NO en el manifiesto como antes!

    

Este PerMonitorV2 es nuevo desde la Actualización de Creadores de Windows 10:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

También conocido como Per Monitor v2. Un avance sobre el modo de percepción DPI original por monitor, que permite a las aplicaciones acceder a nuevos comportamientos de escalado relacionados con DPI en una ventana de nivel superior.

  • Notificaciones de cambio de DPI de ventana infantil : en los contextos de Per Monitor v2, se notifica a toda la ventana de ventana de cualquier cambio de DPI que se produzca.

  • Ampliación del área no cliente : todas las ventanas tendrán automáticamente su área no cliente dibujada de manera sensible a DPI. Las llamadas a EnableNonClientDpiScaling son innecesarias.

  • Scaling de los menús de Win32 : todos los menús NTUSER creados en los contextos de Per Monitor v2 se escalarán por cada monitor.

  • Escalado de diálogo : los cuadros de diálogo de Win32 creados en contextos de Per Monitor v2 responderán automáticamente a los cambios de DPI.

  • Escala mejorada de los controles comctl32 : diversos controles comctl32 han mejorado el comportamiento de escalado DPI en contextos Per Monitor v2.

  • Comportamiento teórico mejorado: los controles UxTheme abiertos en el contexto de una ventana Per Monitor v2 funcionarán en términos del DPI asociado con esa ventana.

Ahora puede suscribirse a 3 nuevos eventos para recibir notificaciones sobre los cambios de DPI:

  • Control.DpiChangedAfterParent , que se dispara. Ocurre cuando la configuración de DPI para un control se cambia programáticamente después de que se haya producido un evento de cambio de DPI para su control principal o formulario.

  • Control.DpiChangedBeforeParent , que se activa cuando la configuración de DPI para un control se cambia programáticamente antes de que se haya producido un evento de cambio de DPI para su control o formulario principal.

  • Form.DpiChanged , que se activa cuando la configuración de DPI cambia en el dispositivo de visualización donde se muestra el formulario actualmente.

También tiene 3 métodos de ayuda sobre el manejo / escalado DPI:

  • Control.LogicalToDeviceUnits , que convierte un valor de píxeles lógicos a dispositivos.

  • Control.ScaleBitmapLogicalToDevice , que escala una imagen de bitmap al DPI lógico para un dispositivo.

  • Control.DeviceDpi , que devuelve el DPI para el dispositivo actual.

Si aún ve problemas, puede optar por no recibir las mejoras de DPI a través de las entradas de app.config .

Si no tiene acceso al código fuente, puede ir a las propiedades de la aplicación en el Explorador de Windows, ir a compatibilidad y seleccionar System (Enhanced)

enter image description here

que activa la escala de GDI para mejorar también el manejo de DPI:

Para las aplicaciones basadas en GDI, Windows ahora puede escalarlas por DPI. Esto significa que estas aplicaciones, por arte de magia, se volverán conscientes del DPI por monitor.

Haga todos esos pasos y debería obtener una mejor experiencia DPI para las aplicaciones WinForms. Pero recuerde, debe orientar su aplicación para .net 4.7 y necesitará al menos Windows 10 Build 15063 (Actualización de creadores). En la próxima actualización de Windows 10 1709, podríamos obtener más mejoras.

Una guía que escribí en el trabajo:

WPF funciona en ‘unidades independientes del dispositivo’, lo que significa que todos los controles se escalan perfectamente a pantallas de alto dpi. En WinForms, se necesita más cuidado.

WinForms funciona en píxeles. El texto se escalará de acuerdo con los ppp del sistema, pero a menudo será recortado por un control sin escala. Para evitar estos problemas, debe evitar el tamaño y el posicionamiento explícitos. Sigue estas reglas:

  1. Donde sea que lo encuentre (tags, botones, paneles) establezca la propiedad AutoSize en True.
  2. Para el diseño, use FlowLayoutPanel (a la WPF StackPanel) y TableLayoutPanel (a la WPF Grid) para el diseño, en lugar de vanilla Panel.
  3. Si está desarrollando en una máquina de alta resolución, el diseñador de Visual Studio puede ser una frustración. Cuando configura AutoSize = True, cambiará el tamaño del control a su pantalla. Si el control tiene AutoSizeMode = GrowOnly, seguirá siendo de este tamaño para personas con ppp normal, es decir. ser más grande de lo esperado Para solucionar este problema, abra el diseñador en una computadora con ppp normal y haga clic con el botón derecho, restablezca.

Descubrí que es muy difícil hacer que WinForms juegue bien con alta DPI. Entonces, escribí un método VB.NET para anular el comportamiento del formulario:

 Public Shared Sub ScaleForm(WindowsForm As System.Windows.Forms.Form) Using g As System.Drawing.Graphics = WindowsForm.CreateGraphics Dim sngScaleFactor As Single = 1 Dim sngFontFactor As Single = 1 If g.DpiX > 96 Then sngScaleFactor = g.DpiX / 96 'sngFontFactor = 96 / g.DpiY End If If WindowsForm.AutoScaleDimensions = WindowsForm.CurrentAutoScaleDimensions Then 'ucWindowsFormHost.ScaleControl(WindowsForm, sngFontFactor) WindowsForm.Scale(sngScaleFactor) End If End Using End Sub 

Además de que los anclajes no funcionan muy bien: daré un paso más y diré que el posicionamiento exacto (también conocido como el uso de la propiedad Ubicación) no funciona muy bien con la escala de la fuente. Tuve que abordar este problema en dos proyectos diferentes. En ambos, tuvimos que convertir el posicionamiento de todos los controles de WinForms al uso de TableLayoutPanel y FlowLayoutPanel. El uso de la propiedad Dock (generalmente configurada para rellenar) dentro de TableLayoutPanel funciona muy bien y se adapta perfectamente a la fuente del sistema DPI.

Recientemente me encontré con este problema, especialmente en combinación con el escalado de Visual Studio cuando el editor se abre en un sistema de alta ppp. Me pareció mejor mantener AutoScaleMode = Font , pero para configurar la fuente de Forms a la fuente predeterminada, pero especificando el tamaño en píxeles , no en puntos, es decir: Font = MS Sans; 11px Font = MS Sans; 11px . En el código, luego reinicié la fuente al valor predeterminado: Font = SystemFonts.DefaultFont y todo está bien.

Solo mis dos centavos. Pensé compartir, porque “mantener AutoScaleMode = Font” y “Establecer tamaño de fuente en píxeles para el Diseñador” fue algo que no encontré en Internet.

Tengo más detalles sobre mi blog: http://www.sgrottel.de/?p=1581&lang=en