¿Cómo escribir el código WinForms que se escala automáticamente a la fuente del sistema y la configuración de dpi?

143

Introducción: hay muchos comentarios que dicen "WinForms no se ajusta automáticamente a la configuración de DPI / fuente; cambia a WPF". Sin embargo, creo que está basado en .NET 1.1; parece que en realidad hicieron un trabajo bastante bueno al implementar el autoescalado en .NET 2.0. Al menos basado en nuestra investigación y pruebas hasta ahora. Sin embargo, si algunos de ustedes lo saben, nos encantaría saber de usted. (No se moleste en discutir que deberíamos cambiar a WPF ... esa no es una opción en este momento).

Preguntas:

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

  • ¿Qué pautas de diseño deben seguir los programadores al escribir código WinForms de modo que se escale bien?

Pautas de diseño que hemos identificado hasta ahora:

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

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

Brian Kennedy
fuente

Respuestas:

127

Controles que no admiten la escala adecuada:

  • Labelcon AutoSize = Falsey Fontheredado. Establecer explícitamente Fonten el control para que aparezca en negrita en la ventana Propiedades.
  • ListViewLos anchos de columna no se escalan. Anule los formularios ScaleControlpara hacerlo en su lugar. Ver esta respuesta
  • SplitContainer's Panel1MinSize, Panel2MinSizey SplitterDistancepropiedades
  • TextBoxcon MultiLine = Truey Fontheredado. Establecer explícitamente Fonten el control para que aparezca en negrita en la ventana Propiedades.
  • ToolStripButtonLa imagen de. En el constructor del formulario:

    • Conjunto ToolStrip.AutoSize = False
    • Establecer ToolStrip.ImageScalingSizesegún CreateGraphics.DpiXy.DpiY
    • Establecer ToolStrip.AutoSize = Truesi es necesario.

    A veces AutoSizese puede dejar en, Truepero a veces no se puede cambiar el tamaño sin esos pasos. Funciona sin que los cambios con .NET Framework 4.5.2 y EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewimágenes de Establecer ImageList.ImageSizesegún CreateGraphics.DpiXy .DpiY. Para StateImageList, funciona sin que los cambios con .NET Framework 4.5.1 y EnableWindowsFormsHighDpiAutoResizing.
  • FormEl tamaño de Escale el tamaño fijo Formmanualmente después de la creación.

Guía de diseño:

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

  • Todos los ContainerControls también se deben configurar con el mismo AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, suponiendo 96 ppp (consulte la siguiente viñeta) y la Fuente predeterminada de MS Sans Serif (consulte la viñeta dos abajo). El diseñador lo agrega automáticamente en función del DPI en el que 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.

  • Realice todo su trabajo de diseñador en 96 ppp (es posible que podamos cambiar a 120 ppp; pero la sabiduría en Internet dice que se mantenga en 96 ppp; la experimentación está en orden allí; por diseño, no debería importar ya que solo cambia la AutoScaleDimensionslínea que el diseñador inserta). Para configurar Visual Studio para que se ejecute a 96 ppp virtuales en una pantalla de alta resolución, busque su archivo .exe, haga clic con el botón derecho para editar propiedades y, en Compatibilidad, seleccione "Anular el comportamiento de escalado de DPI alto. Escalado realizado por: Sistema".

  • Asegúrese de nunca configurar la Fuente en el nivel del contenedor ... solo en los controles de hoja O en el constructor de su Formulario más básico si desea una Fuente predeterminada para toda la aplicación que no sea MS Sans Serif. (Establecer la Fuente en un Contenedor parece desactivar la escala automática de ese contenedor porque viene alfabéticamente después de la configuración de las configuraciones AutoScaleMode y AutoScaleDimensions). TENGA EN CUENTA que si cambia la Fuente en el constructor de su Forma más básica, eso causará sus AutoScaleDimensions para calcular de manera diferente a 6x13; en particular, si cambia a la interfaz de usuario de Segoe (la fuente predeterminada de Win 10), entonces será 7x15 ... deberá tocar todos los formularios del Diseñador para que pueda volver a calcular todas las dimensiones en ese archivo .designer, incluido el AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • NO use Anclar Righto Bottomanclado a un UserControl ... su posicionamiento no se escalará automáticamente; en su lugar, suelte un Panel u otro contenedor en su Control de usuario y ancle sus otros Controles en ese Panel; tiene el uso del panel del muelle Right, Bottomo Fillen su control de usuario.

  • Solo los controles en las listas de Controles cuando se llama ResumeLayoutal final de InitializeComponentse escalarán automáticamente ... si agrega controles dinámicamente, entonces debe SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();activar ese control antes de agregarlo. Y su posición también necesitará ser ajustada si no está utilizando los modos Dock o un Layout Manager como FlowLayoutPanelo TableLayoutPanel.

  • Las clases base derivadas de ContainerControldeberían dejarse AutoScaleModeestablecidas en Inherit(el valor predeterminado establecido en la clase ContainerControl; pero NO el predeterminado establecido por el diseñador). Si lo configura en cualquier otra cosa, y su clase derivada intenta establecerlo en Fuente (como debería), entonces el acto de establecer eso Fontborrará la configuración del diseñador AutoScaleDimensions, ¡lo que en realidad desactivará la escala automática! (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 hoja!)

  • Evite usar Form.MaxSizeestáticamente / en el Diseñador. MinSizey MaxSizeen Form no escalan tanto como todo lo demás. Por lo tanto, si hace todo su trabajo en 96 ppp, cuando tenga un DPI más alto MinSize, no causará problemas, pero puede que no sea tan restrictivo como esperaba, pero MaxSizepuede limitar la escala de su Tamaño, lo que puede causar problemas. Si lo desea MinSize == Size == MaxSize, no lo haga en el Diseñador ... hágalo en su constructor o OnLoadanule ... establezca ambos MinSizey MaxSizesu Tamaño debidamente escalado.

  • Todos los controles en un particular Panelo Containerdeben usar anclaje o acoplamiento. Si los mezcla, el autoescalado realizado Panela menudo se comportará mal de maneras sutiles y extrañas.

  • Cuando haga su autoescalado, intentará escalar el formulario general ... sin embargo, si en ese proceso se encuentra con el límite superior del tamaño de la pantalla, ese es un límite difícil que luego puede arruinarse (clip) La escala. Por lo tanto, debe asegurarse de que todos los formularios en el diseñador al 100% / 96 ppp tengan un tamaño no mayor a 1024x720 (que corresponde al 150% en una pantalla de 1080p o 300%, que es el valor recomendado de Windows en una pantalla 4K). Pero debes restar la barra de título / título Win10 gigante ... así que más como 1000x680 max Size ... que en el diseñador será como 994x642 ClientSize. (Por lo tanto, puede hacer un FindAll References en ClientSize para encontrar infractores)

kjbartel
fuente
NumericUpDownno escala Marginadecuadamente, también. Parece que el margen se escala dos veces. Si lo escalo una vez, se ve bien.
ygoe
AutoScaleMode = Fontno funciona bien para usuarios que usan una fuente muy grande y con Ubuntu. PreferimosAutoScaleMode = DPI
KindDragon
> TextBox con MultiLine = True y Font heredado. Volverse loco todo el día, ¡esa fue la solución! ¡Muchas gracias! Por cierto, la misma solución también es la solución para los controles ListBox. : D
neminem
Para mí, los cuadros de lista con fuente heredada no se escalan bien. Lo hacen después de establecer explícitamente. (.NET 4.7)
PulseJet
27

Mi experiencia ha sido bastante diferente a la respuesta actual más votada. 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 funcione el autoescalado, y solo hubo un problema sutil en algún lugar que lo estropeó. Esto resultó ser cierto.

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

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

Sin embargo, la solución es simple. No establezca la Fontpropiedad en el diseñador en absoluto (configúrela en su constructor de formularios), o reordene manualmente estas asignaciones (pero luego debe seguir haciendo esto cada vez que edite el formulario en el diseñador). Voila, escalamiento casi perfecto y totalmente automático con una molestia mínima. Incluso los tamaños de los formularios se escalan correctamente.


Enumeraré los problemas conocidos aquí cuando los encuentre:

Roman Starkov
fuente
1
No establecer Fonten el diseñador: se me ocurre una idea: siga adelante y configure la fuente en el diseñador, de modo que pueda diseñar con la fuente deseada. ¿ENTONCES en el constructor, después del diseño, lea esa propiedad de fuente y vuelva a establecer el mismo valor? ¿O tal vez solo pedir que se vuelva a hacer el diseño? [Advertencia: no he tenido motivos para probar este enfoque.] O según la respuesta de Knowleech , en el diseñador, especifique en píxeles (para que el diseñador de Visual Studio no cambie la escala en un monitor de DPI alto), y en el código lea ese valor, convierta de píxeles a puntos (para obtener la escala correcta).
ToolmakerSteve
1
Cada bit de nuestro código tiene las dimensiones de escala automática establecidas justo antes del modo de escala automática y todo escala perfectamente. Parece que el orden no importa en la mayoría de los casos.
Josh
Busqué en mi código instancias donde AutoScaleDimensionsno estaba configurado new SizeF(6F, 13F)como se recomienda en la respuesta superior. Resultó que en cada caso, la propiedad Fuente del formulario se había establecido (no predeterminada). Parece que cuando AutoScaleMode = Font, entonces AutoScaleDimensionsse calcula en función de la propiedad de fuente del formulario. Además, la configuración de Escalado en el Panel de control de Windows parece tener un efecto AutoScaleDimensions.
Walter Stabosz
24

Dirija su aplicación para .Net Framework 4.7 y ejecútela en Windows 10 v1703 (Creators Update Build 15063). Con .Net 4.7 en Windows 10 (v1703), MS realizó muchas mejoras en DPI .

Comenzando con .NET Framework 4.7, Windows Forms incluye mejoras para escenarios comunes de DPI alto y DPI dinámico. Éstos incluyen:

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

  • Escalado de una sola pasada. En .NET Framework 4.6 y versiones anteriores, el escalado se realizó a través de múltiples pases, lo que provocó que algunos controles se escalaran más de lo necesario.

  • Soporte para escenarios dinámicos de DPI en los que el usuario cambia el DPI o el factor de escala después de que se haya lanzado una aplicación de 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:

<compatibility xmlns="urn:schemas-microsoft.comn:compatibility.v1">
    <application>
        <!-- Windows 10 compatibility -->
        <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
    </application>
</compatibility>

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

<System.Windows.Forms.ApplicationConfigurationSection>
   <add key="DpiAwareness" value="PerMonitorV2" />
</System.Windows.Forms.ApplicationConfigurationSection> 

Este PerMonitorV2 es nuevo desde Windows 10 Creators Update:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

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

  • Notificaciones de cambio de DPI de ventana secundaria : en contextos por monitor v2, se notifica a todo el árbol de ventana de cualquier cambio de DPI que ocurra.

  • Escalado 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.

  • S titulándose de menús Win32 - Todos los menús creados en contextos NTUSER por monitor v2 será escalando de una forma per-monitor.

  • Escala de diálogo : los cuadros de diálogo Win32 creados en contextos por monitor v2 responderán automáticamente a los cambios de DPI.

  • Escala mejorada de controles comctl32 : varios controles comctl32 han mejorado el comportamiento de escala de DPI en contextos por monitor v2.

  • Comportamiento de temática mejorado : los identificadores UxTheme abiertos en el contexto de una ventana Por monitor v2 funcionarán en términos de DPI asociado con esa ventana.

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

  • Control.DpiChangedAfterParent , que se activa Se produce 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 o formulario principal.

  • Control.DpiChangedBeforeParent , que se activa cuando la configuración de DPI para un control se cambia programáticamente antes de que ocurra 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 actualmente el formulario.

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

  • Control.LogicalToDeviceUnits , que convierte un valor de lógico a píxeles del dispositivo.

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

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

Si sigue apareciendo problemas, puede darse de baja de las mejoras DPI a través de las entradas 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)

ingrese la descripción de la imagen aquí

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

Para las aplicaciones que están basadas en GDI, Windows ahora puede escalarlas DPI por monitor. Esto significa que estas aplicaciones serán, mágicamente, conscientes de DPI por monitor.

Siga todos esos pasos y debería obtener una mejor experiencia DPI para las aplicaciones WinForms. Pero recuerde, debe orientar su aplicación a .net 4.7 y necesita 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.

magicandre1981
fuente
12

Una guía que escribí en el trabajo:

WPF funciona en 'unidades independientes del dispositivo', lo que significa que todos los controles se adaptan perfectamente a pantallas de alta resolución. En WinForms, se necesita más cuidado.

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

  1. Donde sea que lo encuentre (etiquetas, 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 este tamaño para personas con ppp normal, es decir. Ser más grande de lo esperado. Para solucionar esto, abra el diseñador en una computadora con ppp normal y haga clic derecho, reinicie.
Coronel Panic
fuente
3
para los diálogos que se pueden cambiar de tamaño, AutoSize en todo sería una pesadilla, no quiero que mis botones se agranden cada vez más a medida que aumente el tamaño de mis diálogos manualmente mientras ejecuto el programa.
Josh
10

Descubrí que es muy difícil hacer que WinForms juegue bien con un DPI alto. 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
jrh
fuente
6

Recientemente me encontré con este problema, especialmente en combinación con el cambio de escala de Visual Studio cuando el editor se abre en un sistema de alta resolución. He encontrado que la mejor manera de mantener AutoScaleMode = Font , sino para establecer las Formas de fuente a la fuente por defecto, pero se especifica el tamaño en píxeles , no punto, es decir: Font = MS Sans; 11px. En el código, luego restablezco la fuente al valor predeterminado: Font = SystemFonts.DefaultFonty todo está bien.

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

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

Knowleech
fuente
4

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

Brannon
fuente