Comment écrire du code WinForms qui s'adapte automatiquement aux paramètres de police et de dpi du système?

143

Intro: Il y a beaucoup de commentaires qui disent que "WinForms ne s'adapte pas automatiquement aux paramètres DPI / police; passez à WPF." Cependant, je pense que cela est basé sur .NET 1.1; il semble qu'ils ont fait un très bon travail d'implémentation de la mise à l'échelle automatique dans .NET 2.0. Au moins sur la base de nos recherches et tests jusqu'à présent. Cependant, si certains d'entre vous savent mieux, nous serions ravis de vous entendre. (S'il vous plaît, ne vous inquiétez pas de dire que nous devrions passer à WPF ... ce n'est pas une option pour le moment.)

Des questions:

  • Qu'est-ce qui dans WinForms ne se met PAS à l'échelle automatiquement et doit donc être évité?

  • Quelles directives de conception les programmeurs doivent-ils suivre lors de l'écriture de code WinForms de manière à ce qu'il s'adapte bien automatiquement?

Lignes directrices de conception que nous avons identifiées jusqu'à présent:

Voir la réponse du wiki de la communauté ci-dessous.

Certains de ces éléments sont-ils incorrects ou inadéquats? Y a-t-il d'autres lignes directrices que nous devrions adopter? Y a-t-il d'autres schémas à éviter? Toute autre indication à ce sujet serait très appréciée.

Brian Kennedy
la source

Réponses:

127

Contrôles qui ne prennent pas en charge la mise à l'échelle correctement:

  • Labelavec AutoSize = Falseet Fonthérité. Défini explicitement Fontsur le contrôle pour qu'il apparaisse en gras dans la fenêtre Propriétés.
  • ListViewles largeurs de colonne ne sont pas mises à l'échelle. Remplacez le formulaire ScaleControlpour le faire à la place. Voir cette réponse
  • SplitContainer« s Panel1MinSize, Panel2MinSizeet SplitterDistancepropriétés
  • TextBoxavec MultiLine = Trueet Fonthérité. Défini explicitement Fontsur le contrôle pour qu'il apparaisse en gras dans la fenêtre Propriétés.
  • ToolStripButtonl'image de. Dans le constructeur du formulaire:

    • Ensemble ToolStrip.AutoSize = False
    • Régler ToolStrip.ImageScalingSizeselon CreateGraphics.DpiXet.DpiY
    • Réglez ToolStrip.AutoSize = Truesi nécessaire.

    Parfois, AutoSizepeut être laissé à Truemais parfois il ne parvient pas à se redimensionner sans ces étapes. Fonctionne sans que cela change avec .NET Framework 4.5.2 et EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewles images de. Réglez ImageList.ImageSizeselon CreateGraphics.DpiXet .DpiY. Pour StateImageList, fonctionne sans que cela change avec .NET Framework 4.5.1 et EnableWindowsFormsHighDpiAutoResizing.
  • Formla taille de. Mettre à l'échelle la taille fixe Formmanuellement après la création.

Directives de conception:

  • Tous les ContainerControls doivent être définis sur le même AutoScaleMode = Font. (La police gérera à la fois les modifications PPP et les modifications apportées au paramètre de taille de police système; DPI ne gérera que les modifications DPI, pas les modifications apportées au paramètre de taille de police système.)

  • Tous les ContainerControls doivent également être définis avec le même AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, en supposant 96 dpi (voir la puce suivante) et la police par défaut de MS Sans Serif (voir la puce deux vers le bas). Cela est ajouté automatiquement par le concepteur en fonction du DPI dans lequel vous ouvrez le concepteur ... mais il manquait dans beaucoup de nos fichiers de concepteur les plus anciens. Peut-être que Visual Studio .NET (la version antérieure à VS 2005) n'ajoutait pas cela correctement.

  • Faites tout votre travail de concepteur en 96 dpi (nous pourrions peut-être passer à 120 dpi; mais la sagesse sur Internet dit de s'en tenir à 96 dpi; l'expérimentation est en ordre là-bas; de par la conception, cela ne devrait pas avoir d'importance car cela change simplement la AutoScaleDimensionsligne qui le concepteur insère). Pour définir Visual Studio pour qu'il s'exécute à un 96 dpi virtuel sur un affichage haute résolution, recherchez son fichier .exe, cliquez avec le bouton droit pour modifier les propriétés et sous Compatibilité, sélectionnez «Remplacer le comportement de mise à l'échelle DPI élevé. Mise à l'échelle effectuée par: Système».

  • Assurez-vous de ne jamais définir la police au niveau du conteneur ... uniquement sur les contrôles feuille OU dans le constructeur de votre formulaire le plus basique si vous voulez une police par défaut à l'échelle de l'application autre que MS Sans Serif. (La définition de la police sur un conteneur semble désactiver la mise à l'échelle automatique de ce conteneur car elle vient par ordre alphabétique après la définition des paramètres AutoScaleMode et AutoScaleDimensions.) Notez que si vous modifiez la police dans le constructeur de votre formulaire le plus vos AutoScaleDimensions pour calculer différemment de 6x13; en particulier, si vous passez à Segoe UI (la police par défaut de Win 10), alors ce sera 7x15 ... vous devrez toucher chaque formulaire dans le concepteur afin qu'il puisse recalculer toutes les dimensions de ce fichier .designer, y compris le AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • N'utilisez PAS Anchor Rightou Bottomancré à un UserControl ... son positionnement ne s'échelonnera pas automatiquement; à la place, déposez un Panel ou un autre conteneur dans votre UserControl et ancrez vos autres Contrôles à ce Panel; ont l'utilisation Panneau Dock Right, Bottomou Filldans votre UserControl.

  • Seuls les contrôles dans les listes de contrôles lorsque ResumeLayoutà la fin de InitializeComponentest appelé seront mis à l'échelle automatiquement ... si vous ajoutez des contrôles de manière dynamique, vous devez SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();sur ce contrôle avant de l'ajouter. Et votre positionnement devra également être ajusté si vous n'utilisez pas les modes Dock ou un gestionnaire de mise en page comme FlowLayoutPanelou TableLayoutPanel.

  • Classes de base dérivées de ContainerControldoivent abandonner AutoScaleModefixé à Inherit(l'ensemble de la valeur par défaut dans la classe ContainerControl, mais pas le jeu par défaut par le concepteur). Si vous le définissez sur autre chose et que votre classe dérivée essaie de le définir sur Font (comme il se doit), le fait de le définir Fonteffacera le paramètre du concepteur AutoScaleDimensions, ce qui entraînera la désactivation de la mise à l'échelle automatique! (Cette directive combinée avec la précédente signifie que vous ne pouvez jamais instancier des classes de base dans un concepteur ... toutes les classes doivent être conçues comme des classes de base ou comme des classes feuilles!)

  • Évitez d'utiliser de Form.MaxSizemanière statique / dans le Designer. MinSizeet MaxSizesur la forme ne s'adaptent pas autant que tout le reste. Donc, si vous effectuez tout votre travail en 96 dpi, lorsque vous utilisez un DPI plus élevé, vous ne causerez MinSizepas de problèmes, mais vous ne serez peut-être pas aussi restrictif que prévu, mais vous MaxSizerisquez de limiter la mise à l'échelle de votre taille, ce qui peut causer des problèmes. Si vous le souhaitez MinSize == Size == MaxSize, ne le faites pas dans le concepteur ... faites cela dans votre constructeur ou OnLoadremplacez ... définissez les deux MinSizeet MaxSizesur votre taille correctement mise à l'échelle.

  • Tous les contrôles sur un particulier Panelou Containerdevraient utiliser l'ancrage ou l'ancrage. Si vous les mélangez, la mise à l'échelle automatique effectuée par cela Panelse comportera souvent mal de manière subtile et bizarre.

  • Lorsqu'il effectue sa mise à l'échelle automatique, il essaiera de mettre à l'échelle le formulaire global ... cependant, si dans ce processus il se heurte à la limite supérieure de la taille de l'écran, c'est une limite stricte qui peut alors visser (clip) la mise à l'échelle. Par conséquent, vous devez vous assurer que tous les formulaires dans le concepteur à 100% / 96 dpi ne sont pas dimensionnés plus grand que 1024x720 (ce qui correspond à 150% sur un écran 1080p ou 300% qui est la valeur recommandée par Windows sur un écran 4K). Mais vous devez soustraire la barre de titre / légende de Win10 géante ... donc plus comme 1000x680 Max Size ... qui dans le concepteur sera comme 994x642 ClientSize. (Ainsi, vous pouvez faire un FindAll References sur ClientSize pour trouver les contrevenants.)

kjbartel
la source
NumericUpDown ne met pas sa Margin correctement, aussi. Il semble que la marge soit redimensionnée deux fois. Si je le réduis une fois, ça a l'air bien.
ygoe le
AutoScaleMode = Fontne fonctionne pas bien pour les utilisateurs qui utilisent une très grande police et avec sur Ubuntu. Nous préféronsAutoScaleMode = DPI
KindDragon
> TextBox avec MultiLine = True et Font hérité. Devenir fou toute la journée - c'était la solution! Merci beaucoup! À propos, le même correctif est également le correctif pour les contrôles ListBox. :RÉ
neminem
Pour moi, les zones de liste avec une police héritée ne sont pas bien mises à l'échelle. Ils le font après avoir été explicitement définis. (.NET 4.7)
PulseJet
27

Mon expérience a été assez différente de la réponse actuelle aux votes les plus élevés. En parcourant le code du framework .NET et en parcourant le code source de référence, j'ai conclu que tout était en place pour que la mise à l'échelle automatique fonctionne, et qu'il n'y avait qu'un problème subtil quelque part qui le gâchait. Cela s'est avéré être vrai.

Si vous créez une mise en page correctement redimensionnable / auto-dimensionnée, alors presque tout fonctionne exactement comme il se doit, automatiquement, avec les paramètres par défaut utilisés par Visual Studio (à savoir, AutoSizeMode = Font sur le formulaire parent et Inherit sur tout le reste).

Le seul problème est si vous avez défini la propriété Font sur le formulaire dans le concepteur. Le code généré triera les affectations par ordre alphabétique, ce qui signifie qu'elles AutoScaleDimensionsseront affectées avant Font . Malheureusement, cela rompt complètement la logique de mise à l'échelle automatique de WinForms.

Le correctif est simple cependant. Soit ne définissez pas du tout la Fontpropriété dans le concepteur (définissez-la dans votre constructeur de formulaire), soit réorganisez manuellement ces affectations (mais vous devez continuer à le faire chaque fois que vous modifiez le formulaire dans le concepteur). Voila, mise à l'échelle presque parfaite et entièrement automatique avec un minimum de tracas. Même les tailles de formulaire sont correctement mises à l'échelle.


Je vais énumérer les problèmes connus ici au fur et à mesure que je les rencontre:

Roman Starkov
la source
1
Ne pas définir Fontdans le concepteur: Une pensée me vient à l'esprit: allez-y et définissez la police dans le concepteur, afin que vous puissiez concevoir avec la police souhaitée. ALORS dans le constructeur, après la mise en page, lire cette propriété de police et redéfinir la même valeur? Ou peut-être simplement demander à nouveau la mise en page? [ Mise en garde : je n'ai pas eu de raison de tester cette approche.] Ou selon la réponse de Knowleech , dans le concepteur, spécifiez en pixels (afin que le concepteur Visual Studio ne soit pas redimensionné sur un moniteur à haute résolution), et dans le code, lisez cette valeur, convertissez à partir de pixels aux points (pour obtenir une mise à l'échelle correcte).
ToolmakerSteve
1
Chaque bit de notre code a les dimensions de mise à l'échelle automatique définies juste avant le mode de mise à l'échelle automatique et tout est parfaitement mis à l'échelle. On dirait que l'ordre n'a pas d'importance dans la plupart des cas.
Josh
J'ai recherché dans mon code les instances où AutoScaleDimensionsn'était pas défini sur new SizeF(6F, 13F)comme recommandé dans la première réponse. Il s'est avéré que dans tous les cas, la propriété Font du formulaire avait été définie (pas par défaut). Il semble que quand AutoScaleMode = Font, alors AutoScaleDimensionsest calculé en fonction de la propriété de police du formulaire. En outre, le paramètre de mise à l' échelle dans le panneau de configuration de Windows semble avoir un effet sur AutoScaleDimensions.
Walter Stabosz
24

Ciblez votre application pour .Net Framework 4.7 et exécutez-la sous Windows 10 v1703 (Creators Update Build 15063). Avec .Net 4.7 sous Windows 10 (v1703), MS a apporté de nombreuses améliorations DPI .

À partir du .NET Framework 4.7, Windows Forms inclut des améliorations pour les scénarios DPI élevés et dynamiques. Ceux-ci inclus:

  • Améliorations de la mise à l'échelle et de la disposition d'un certain nombre de contrôles Windows Forms, tels que le contrôle MonthCalendar et le contrôle CheckedListBox.

  • Mise à l'échelle en un seul passage. Dans le .NET Framework 4.6 et les versions antérieures, la mise à l'échelle était effectuée via plusieurs passes, ce qui entraînait une mise à l'échelle de certains contrôles plus que nécessaire.

  • Prise en charge des scénarios DPI dynamiques dans lesquels l'utilisateur modifie le DPI ou le facteur d'échelle après le lancement d'une application Windows Forms.

Pour le prendre en charge, ajoutez un manifeste d'application à votre application et signalez que votre application prend en charge Windows 10:

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

Ensuite, ajoutez un app.configet déclarez l'application Per Monitor Aware. Cela se fait MAINTENANT dans app.config et PAS dans le manifeste comme avant!

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

Ce PerMonitorV2 est nouveau depuis la mise à jour Windows 10 Creators:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Aussi connu sous le nom de Per Monitor v2. Une avancée par rapport au mode de prise de conscience DPI par moniteur d'origine, qui permet aux applications d'accéder à de nouveaux comportements de mise à l'échelle liés à DPI sur une base de fenêtre de niveau supérieur.

  • Notifications de modification PPP de la fenêtre enfant - Dans les contextes Par moniteur v2, la totalité de l'arborescence de la fenêtre est notifiée de toute modification PPP qui se produit.

  • Mise à l'échelle de la zone non-client - Toutes les fenêtres auront automatiquement leur zone non-client dessinée d'une manière sensible DPI. Les appels à EnableNonClientDpiScaling sont inutiles.

  • S intitulant des menus Win32 - Tous les menus NTUSER créés dans les contextes de par moniteur sera mise à l' échelle d'une manière par moniteur.

  • Mise à l'échelle des dialogues - Les boîtes de dialogue Win32 créées dans les contextes Per Monitor v2 répondront automatiquement aux modifications DPI.

  • Mise à l'échelle améliorée des contrôles comctl32 - Divers contrôles comctl32 ont amélioré le comportement de mise à l'échelle DPI dans les contextes Per Monitor v2.

  • Amélioration du comportement de thématisation - Les poignées UxTheme ouvertes dans le contexte d'une fenêtre Per Monitor v2 fonctionneront en termes de DPI associé à cette fenêtre.

Vous pouvez désormais vous abonner à 3 nouveaux événements pour être averti des modifications DPI:

  • Control.DpiChangedAfterParent , qui est déclenché Se produit lorsque le paramètre PPP d'un contrôle est modifié par programme après qu'un événement de modification PPP pour son contrôle parent ou son formulaire s'est produit.

  • Control.DpiChangedBeforeParent , qui est déclenché lorsque le paramètre PPP d'un contrôle est modifié par programme avant qu'un événement de modification PPP pour son contrôle ou son formulaire parent ne se soit produit.

  • Form.DpiChanged , qui est déclenché lorsque le paramètre PPP change sur le périphérique d'affichage sur lequel le formulaire est actuellement affiché.

Vous disposez également de 3 méthodes d'assistance sur la gestion / mise à l'échelle DPI:

  • Control.LogicalToDeviceUnits , qui convertit une valeur de pixels logiques en pixels de périphérique.

  • Control.ScaleBitmapLogicalToDevice , qui met à l'échelle une image bitmap au DPI logique d'un périphérique.

  • Control.DeviceDpi , qui renvoie le DPI pour le périphérique actuel.

Si des problèmes persistent, vous pouvez désactiver les améliorations DPI via les entrées app.config .

Si vous n'avez pas accès au code source, vous pouvez accéder aux propriétés de l'application dans l'Explorateur Windows, accéder à la compatibilité et sélectionner System (Enhanced)

entrez la description de l'image ici

qui active la mise à l'échelle GDI pour améliorer également la gestion DPI:

Pour les applications basées sur GDI, Windows peut désormais les mettre à l'échelle DPI sur une base par moniteur. Cela signifie que ces applications deviendront, comme par magie, une prise en charge DPI par moniteur.

Suivez toutes ces étapes et vous devriez obtenir une meilleure expérience DPI pour les applications WinForms. Mais rappelez-vous, vous devez cibler votre application pour .net 4.7 et au moins Windows 10 Build 15063 (Creators Update). Dans la prochaine mise à jour 1709 de Windows 10, nous pourrions obtenir d'autres améliorations.

magicandre1981
la source
12

Un guide que j'ai écrit au travail:

WPF fonctionne dans des `` unités indépendantes de l'appareil '', ce qui signifie que toutes les commandes s'adaptent parfaitement aux écrans à haute résolution. Dans WinForms, cela prend plus de soin.

WinForms fonctionne en pixels. Le texte sera mis à l'échelle en fonction du dpi du système, mais il sera souvent rogné par un contrôle non mis à l'échelle. Pour éviter de tels problèmes, vous devez éviter le dimensionnement et le positionnement explicites. Suivez ces règles:

  1. Où que vous le trouviez (étiquettes, boutons, panneaux), définissez la propriété AutoSize sur True.
  2. Pour la disposition, utilisez FlowLayoutPanel (à la WPF StackPanel) et TableLayoutPanel (à la WPF Grid) pour la disposition, plutôt que Vanilla Panel.
  3. Si vous développez sur une machine à résolution élevée, le concepteur de Visual Studio peut être une frustration. Lorsque vous définissez AutoSize = True, il redimensionnera le contrôle à votre écran. Si le contrôle a AutoSizeMode = GrowOnly, il restera cette taille pour les personnes en dpi normal, c'est-à-dire. être plus grand que prévu. Pour résoudre ce problème, ouvrez le concepteur sur un ordinateur avec un dpi normal et faites un clic droit, réinitialisez.
Colonel Panic
la source
3
pour les boîtes de dialogue qui peuvent être redimensionnées AutoSize sur tout serait un cauchemar, je ne veux pas que mes boutons deviennent de plus en plus petits car j'augmente manuellement la taille de mes boîtes de dialogue tout en exécutant le programme.
Josh
10

J'ai trouvé très difficile d'obtenir WinForms pour jouer correctement avec un DPI élevé. Donc, j'ai écrit une méthode VB.NET pour remplacer le comportement du formulaire:

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
la source
6

Je suis récemment tombé sur ce problème, en particulier en combinaison avec le redimensionnement de Visual Studio lorsque l'éditeur est ouvert sur un système à haute résolution. Je l' ai trouvé mieux pour garder AutoScaleMode = Font , mais pour définir les formes de police à la police par défaut, mais en spécifiant la taille en pixels , pas le point, à savoir: Font = MS Sans; 11px. Dans le code, je réinitialise ensuite la police par défaut:Font = SystemFonts.DefaultFont et tout va bien.

Juste mes deux cents. Je pensais partager, car «garder AutoScaleMode = Font» et «Définir la taille de la police en pixel pour le Designer» était quelque chose que je n'ai pas trouvé sur Internet.

J'ai quelques détails supplémentaires sur mon blog: http://www.sgrottel.de/?p=1581&lang=en

Knowleech
la source
4

En plus des ancres qui ne fonctionnent pas très bien: j'irais plus loin et je dirais que le positionnement exact (c'est-à-dire en utilisant la propriété Location) ne fonctionne pas très bien avec la mise à l'échelle de la police. J'ai dû aborder ce problème dans deux projets différents. Dans les deux, nous avons dû convertir le positionnement de tous les contrôles WinForms en utilisant TableLayoutPanel et FlowLayoutPanel. L'utilisation de la propriété Dock (généralement définie sur Fill) dans le TableLayoutPanel fonctionne très bien et s'adapte parfaitement à la police système DPI.

Brannon
la source