web-dev-qa-db-fra.com

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

Intro: Il y a beaucoup de commentaires qui disent "WinForms ne met pas correctement à l'échelle automatiquement les paramètres DPI/font; passez à WPF". Cependant, je pense que cela est basé sur .NET 1.1; il semble qu'ils aient vraiment fait du bon travail en implémentant la mise à l'échelle automatique dans .NET 2.0. Au moins sur la base de nos recherches et de nos tests jusqu'à présent. Toutefois, si certains d'entre vous le savent mieux, nous aimerions avoir de vos nouvelles. (S'il vous plaît, ne vous dérangez pas en arguant que nous devrions passer à WPF ... ce n'est pas une option pour le moment.)

Des questions:

  • Qu'est-ce qui dans WinForm ne fait PAS une mise à l'échelle automatique correcte et doit donc être évité?

  • Quelles directives de conception les programmeurs devraient-ils suivre lors de l’écriture du code WinForms de telle sorte qu’il soit correctement mis à l’échelle?

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

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

Est-ce que certains d'entre eux sont incorrects ou inadéquats? Quelles autres directives devrions-nous adopter? Y a-t-il d'autres modèles à éviter? Toute autre indication à ce sujet serait très appréciée.

132
Brian Kennedy

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

  • Label avec AutoSize = False et Font hérité. Définissez explicitement Font sur le contrôle afin qu'il apparaisse en gras dans la fenêtre Propriétés.
  • ListView les largeurs de colonne ne sont pas mises à l'échelle. Remplacez ScaleControl du formulaire pour le faire à la place. Voir cette réponse
  • SplitContainer 'Panel1MinSize, Panel2MinSize et SplitterDistance propriétés
  • TextBox avec MultiLine = True et Font hérité. Définissez explicitement Font sur le contrôle afin qu'il apparaisse en gras dans la fenêtre Propriétés.
  • ToolStripButton's image. Dans le constructeur de la fiche:

    • Définir ToolStrip.AutoSize = False
    • Définissez ToolStrip.ImageScalingSize en fonction de CreateGraphics.DpiX et .DpiY
    • Définissez ToolStrip.AutoSize = True si nécessaire.

    Parfois, AutoSize peut rester à True mais parfois, il ne parvient pas à redimensionner sans ces étapes. Fonctionne sans que cela change avec . NET Framework 4.5.2 et EnableWindowsFormsHighDpiAutoResizing.

  • Les images de TreeView. Définissez ImageList.ImageSize en fonction de CreateGraphics.DpiX et de .DpiY. Pour StateImageList , fonctionne sans que cela change avec .NET Framework 4.5.1 et EnableWindowsFormsHighDpiAutoResizing.
  • La taille de Form. Échelle de taille fixe Form manuellement 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 de DPI et les modifications du paramètre de taille de la police système; DPI ne traitera que les modifications de DPI, et non les modifications apportées au paramètre de taille de la police système.)

  • Tous les ContainerControls doivent également être définis avec AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, en supposant 96 ppp (voir la puce suivante). Ceci est ajouté automatiquement par le concepteur en fonction du DPI dans lequel vous ouvrez le concepteur ... mais il manquait dans nombre 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.

  • Tous vos concepteurs travaillent en 96 ppp (nous pourrions peut-être passer à 120 ppp; mais la sagesse sur Internet recommande de s’en tenir à 96 ppp; l’expérimentation est dans l’ordre; par conception, cela ne devrait pas avoir d’importance, car cela ne fait que changer la ligne AutoScaleDimensions que le concepteur insère). Pour que Visual Studio s'exécute sur une résolution virtuelle de 96 ppp sur un écran haute résolution, recherchez son fichier .exe, cliquez avec le bouton droit de la souris pour modifier les propriétés, puis sous Compatibilité, sélectionnez "Ignorer le comportement de redimensionnement en DPI élevé. Redimensionnement effectué par: Système".

  • Assurez-vous de ne jamais définir la police au niveau du conteneur… uniquement sur les contrôles feuille. (La définition de la police sur un conteneur semble désactiver la mise à l'échelle automatique de ce conteneur.)

  • N'utilisez PAS Anchor Right ou Bottom ancré à un UserControl ... son positionnement ne sera pas mis à l'échelle automatiquement; déposez plutôt un panneau ou un autre conteneur dans votre UserControl et ancrez vos autres contrôles dans ce panneau; demandez au panneau d’utiliser Dock Right ou Dock Bottom dans votre UserControl.

  • Seuls les contrôles dans les listes de contrôles lorsque ResumeLayout à la fin de InitializeComponent est appelé seront automatiquement redimensionnés ... si vous ajoutez dynamiquement des contrôles, vous devez alors 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 FlowLayoutPanel ou TableLayoutPanel.

  • Les classes de base dérivées de ContainerControl doivent laisser AutoScaleMode défini sur Inherit (la valeur par défaut définie dans la classe ContainerControl; mais PAS celle définie par le concepteur). Si vous le définissez sur autre chose et que votre classe dérivée tente de le définir sur Police (comme il se doit), le fait de définir ce paramètre sur Font effacera le paramètre du concepteur défini sur AutoScaleDimensions, ce qui aura pour effet de désactiver la mise à l'échelle automatique. ! (Cette directive, combinée à 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 en tant que classes de base ou en tant que classes feuille!)

  • Évitez d’utiliser Form.MaxSize de manière statique/dans Designer. MinSize et MaxSize sur le formulaire ne sont pas aussi évolutifs que tout le reste. Donc, si vous faites tout votre travail en 96 dpi, alors quand avec une DPI plus élevée, votre MinSize ne posera pas de problèmes, mais ne sera peut-être pas aussi restrictif que prévu, mais votre MaxSize pourra limiter la mise à l'échelle de votre taille, ce qui peut causer des problèmes. Si vous voulez MinSize == Size == MaxSize, ne le faites pas dans le concepteur ... faites-le dans votre constructeur ou le remplacement de OnLoad ... définissez MinSize et MaxSize sur votre taille correctement mise à l'échelle.

  • Tous les contrôles sur un Panel ou un Container particulier doivent utiliser l'ancrage ou l'ancrage. Si vous les mélangez, la mise à l'échelle automatique effectuée par Panel se comportera souvent mal de manière subtile et étrange.

108
kjbartel

Mon expérience a été assez différente de la réponse actuelle la plus votée. 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ênait. Cela s'est avéré être vrai.

Si vous créez une disposition redimensionnable/de taille automatique correctement, presque tout fonctionne exactement comme il se doit, avec les paramètres par défaut utilisés par Visual Studio (à savoir AutoSizeMode = Police sur le formulaire parent et Hériter sur tout le reste).

Le seul casse-tête est si vous avez défini la propriété Font sur le formulaire dans le concepteur. Le code généré triera les assignations par ordre alphabétique, ce qui signifie que AutoScaleDimensions sera attribué avant Font. Malheureusement, cela rompt complètement la logique de mise à l'échelle automatique de WinForms.

La solution est simple cependant. Soit ne définissez pas du tout la propriété Font 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). . Voilà, la mise à l'échelle presque parfaite et entièrement automatique avec un minimum de tracas. Même les tailles de formulaire sont mises à l'échelle correctement.


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

25
Roman Starkov

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 au niveau de la DPI .

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

  • Améliorations apportées à la mise à l'échelle et à 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 .NET Framework 4.6 et les versions antérieures, la mise à l'échelle était effectuée en plusieurs passes, ce qui entraînait la mise à l'échelle de certains contrôles plus que nécessaire.

  • Prise en charge de scénarios DPI dynamiques dans lesquels l'utilisateur modifie le facteur 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.config et déclarez l'application Per Monitor Aware. Ceci est MAINTENANT fait 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 de Windows 10 Creators:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Aussi appelé Per Monitor v2. 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 par fenêtre de niveau supérieur.

  • Notifications de modification DPI de la fenêtre enfant - Dans les contextes de Per Monitor v2, l'arborescence de la fenêtre entière est avertie de tout changement DPI intervenu.

  • Mise à l'échelle de la zone non cliente - Toutes les fenêtres verront automatiquement leur zone non clientée dessinée en mode DPI. Les appels à EnableNonClientDpiScaling sont inutiles.

  • S caling des menus Win32 - Tous les menus NTUSER créés dans des contextes Per Monitor v2 seront redimensionnés à l’écran.

  • Mise à l'échelle des boîtes de dialogue - Les boîtes de dialogue Win32 créées dans des contextes Per Monitor v2 répondent automatiquement aux modifications de 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 descripteurs UxTheme ouverts 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 apportées à DPI:

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

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

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

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

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

  • Control.ScaleBitmapLogicalToDevice , qui redimensionne une image bitmap au DPI logique d'un périphérique.

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

Si vous rencontrez toujours des problèmes, vous pouvez désactiver les améliorations de 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).

enter image description here

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 par moniteur. Cela signifie que ces applications deviendront, comme par magie, conscientes du DPI par moniteur.

Effectuez 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 aurons peut-être d'autres améliorations.

19
magicandre1981

Un guide que j'ai écrit au travail:

WPF fonctionne en "unités indépendantes du périphérique", ce qui signifie que toutes les commandes s’adaptent parfaitement aux écrans à résolution élevée. Dans WinForms, cela prend plus de soin.

WinForms fonctionne en pixels. Le texte sera mis à l'échelle en fonction de la résolution 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 les opérations de dimensionnement et de positionnement explicites. Suivez ces règles:

  1. Où que vous trouviez (étiquettes, boutons, panneaux), définissez la propriété AutoSize sur True.
  2. Pour la mise en page, utilisez FlowLayoutPanel (à la WPF StackPanel) et TableLayoutPanel (à la la WPF Grid) pour la mise en page, plutôt que Vanilla Panel.
  3. Si vous développez sur un ordinateur avec une résolution élevée, le concepteur de Visual Studio peut être une frustration. Lorsque vous définissez AutoSize = True, le contrôle sera redimensionné à l'écran. Si le contrôle a AutoSizeMode = GrowOnly, il restera cette taille pour les personnes en dpi normales, c'est-à-dire. être plus gros que prévu. Pour résoudre ce problème, ouvrez le concepteur sur un ordinateur avec une résolution normale et effectuez un clic droit, réinitialiser.
12
Colonel Panic

J'ai trouvé très difficile d'obtenir que WinForms joue à Nice avec un DPI élevé. J'ai donc écrit une méthode VB.NET pour redéfinir 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
8
user3950597

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. J'ai trouvé qu'il était préférable de conserver AutoScaleMode = Font, mais de définir les polices de formulaire à la police par défaut, mais spécifiant la taille en pixels , pas de point, c’est-à-dire: Font = MS Sans; 11px. En code, je puis réinitialise la police à la valeur par défaut: Font = SystemFonts.DefaultFont et tout va bien.

Juste mes deux cents. Je pensais partager, car "conserver AutoScaleMode = Font" et "Définir la taille de la police en pixels pour le concepteur" était quelque chose 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

6
Knowleech

En plus des ancres qui ne fonctionnent pas très bien: je voudrais aller un peu plus loin et dire que le positionnement exact (en utilisant la propriété Location) ne fonctionne pas très bien avec la mise à l'échelle des polices. J'ai dû aborder ce problème dans deux projets différents. Dans les deux cas, nous devions 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 Remplir) à l'intérieur de TableLayoutPanel fonctionne très bien et s'adapte parfaitement à la police de caractères système DPI.

4
Brannon