Wie schreibe ich WinForms-Code, der automatisch auf die Einstellungen für Systemschriftart und dpi skaliert wird?

143

Intro: Es gibt viele Kommentare, die besagen: "WinForms lässt sich nicht gut automatisch auf DPI- / Schriftarteinstellungen skalieren. Wechseln Sie zu WPF." Ich denke jedoch, dass dies auf .NET 1.1 basiert; Es scheint, dass sie die automatische Skalierung in .NET 2.0 ziemlich gut implementiert haben. Zumindest basierend auf unseren bisherigen Forschungen und Tests. Wenn einige von Ihnen es jedoch besser wissen, würden wir gerne von Ihnen hören. (Bitte argumentieren Sie nicht, dass wir zu WPF wechseln sollten ... das ist momentan keine Option.)

Fragen:

  • Was in WinForms wird NICHT automatisch automatisch skaliert und sollte daher vermieden werden?

  • Welche Designrichtlinien sollten Programmierer befolgen, wenn sie WinForms-Code so schreiben, dass er automatisch gut skaliert?

Designrichtlinien, die wir bisher identifiziert haben:

Siehe Community-Wiki-Antwort unten.

Sind einige davon falsch oder unzureichend? Gibt es noch andere Richtlinien, die wir übernehmen sollten? Gibt es andere Muster, die vermieden werden müssen? Jede andere Anleitung hierzu wäre sehr dankbar.

Brian Kennedy
quelle

Antworten:

127

Steuerelemente, die die Skalierung nicht ordnungsgemäß unterstützen:

  • Labelmit AutoSize = Falseund Fontgeerbt. Stellen Sie Fontdas Steuerelement explizit so ein, dass es im Eigenschaftenfenster fett angezeigt wird.
  • ListViewSpaltenbreiten skalieren nicht. Überschreiben ScaleControlSie stattdessen die Formulare . Siehe diese Antwort
  • SplitContainer‚s Panel1MinSize, Panel2MinSizeund SplitterDistanceEigenschaften
  • TextBoxmit MultiLine = Trueund Fontgeerbt. Stellen Sie Fontdas Steuerelement explizit so ein, dass es im Eigenschaftenfenster fett angezeigt wird.
  • ToolStripButtonBild. Im Konstruktor des Formulars:

    • einstellen ToolStrip.AutoSize = False
    • Stellen Sie ToolStrip.ImageScalingSizegemäß CreateGraphics.DpiXund ein.DpiY
    • Stellen Sie, ToolStrip.AutoSize = Truewenn nötig.

    Manchmal AutoSizekann bei belassen werden, Trueaber manchmal kann die Größe ohne diese Schritte nicht geändert werden. Funktioniert ohne diese Änderungen mit .NET Framework 4.5.2 und EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewBilder. Stellen Sie ImageList.ImageSizegemäß CreateGraphics.DpiXund ein .DpiY. Funktioniert StateImageListohne diese Änderungen mit .NET Framework 4.5.1 und EnableWindowsFormsHighDpiAutoResizing.
  • FormGröße. Skalieren Sie feste Größen Formnach der Erstellung manuell.

Designrichtlinien:

  • Alle ContainerControls müssen gleich eingestellt sein AutoScaleMode = Font. (Die Schriftart behandelt sowohl DPI-Änderungen als auch Änderungen an der Einstellung der Systemschriftgröße. DPI behandelt nur DPI-Änderungen, keine Änderungen an der Einstellung der Systemschriftgröße.)

  • Alle ContainerControls müssen ebenfalls mit derselben AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);Einstellung eingestellt werden , vorausgesetzt 96 dpi (siehe nächster Punkt) und die Standardschriftart von MS Sans Serif (siehe Punkt 2 unten). Dies wird vom Designer basierend auf der DPI, in der Sie den Designer öffnen, automatisch hinzugefügt, fehlte jedoch in vielen unserer ältesten Designer-Dateien. Möglicherweise hat Visual Studio .NET (die Version vor VS 2005) dies nicht richtig hinzugefügt.

  • Arbeiten Sie alle Ihre Designer mit 96 dpi (wir können möglicherweise auf 120 dpi umschalten; aber die Weisheit im Internet besagt, dass Sie sich an 96 dpi halten müssen; Experimente sind dort angebracht; von Natur aus sollte es keine Rolle spielen, da es nur die AutoScaleDimensionsLinie ändert, die die Designereinsätze). Um festzulegen, dass Visual Studio auf einer hochauflösenden Anzeige mit einer virtuellen Auflösung von 96 dpi ausgeführt wird, suchen Sie die EXE-Datei, klicken Sie mit der rechten Maustaste, um die Eigenschaften zu bearbeiten, und wählen Sie unter Kompatibilität die Option "Skalierungsverhalten mit hoher DPI überschreiben. Skalierung durchgeführt von: System".

  • Stellen Sie sicher, dass Sie die Schriftart niemals auf Containerebene festlegen ... nur auf den Blattsteuerelementen ODER im Konstruktor Ihres Basisformulars, wenn Sie eine andere anwendungsweite Standardschrift als MS Sans Serif möchten. (Das Festlegen der Schriftart für einen Container scheint die automatische Skalierung dieses Containers zu deaktivieren, da sie alphabetisch nach dem Festlegen der Einstellungen für AutoScaleMode und AutoScaleDimensions erfolgt.) HINWEIS: Wenn Sie die Schriftart im Konstruktor Ihres Basisformulars ändern, führt dies dazu Ihre AutoScaleDimensions müssen anders als 6x13 berechnet werden. Insbesondere wenn Sie zur Segoe-Benutzeroberfläche (der Win 10-Standardschriftart) wechseln, ist diese 7 x 15 ... Sie müssen jedes Formular im Designer berühren, damit alle Dimensionen in dieser Designer-Datei neu berechnet werden können, einschließlich die AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • Verwenden Sie NICHT Anker Rightoder Bottomverankert in einem UserControl ... seine Positionierung wird nicht automatisch skaliert. Legen Sie stattdessen ein Bedienfeld oder einen anderen Container in Ihrem UserControl ab und verankern Sie Ihre anderen Steuerelemente in diesem Bedienfeld. hat die Panel Verwendung Dock Right, Bottomoder Fillin Ihrem Usercontrol.

  • Nur die Steuerelemente in den Steuerelementlisten, die ResumeLayoutam Ende von InitializeComponentaufgerufen werden, werden automatisch skaliert. Wenn Sie Steuerelemente dynamisch hinzufügen, müssen Sie SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();dieses Steuerelement aktivieren, bevor Sie es hinzufügen. Außerdem muss Ihre Positionierung angepasst werden Wenn Sie keine Dock-Modi oder einen Layout-Manager wie FlowLayoutPaneloder verwenden TableLayoutPanel.

  • Von abgeleitete Basisklassen ContainerControlsollten auf AutoScaleModegesetzt bleiben Inherit(der in der Klasse festgelegte Standardwert ContainerControl; aber NICHT der vom Designer festgelegte Standardwert). Wenn Sie es auf etwas anderes setzen und Ihre abgeleitete Klasse dann versucht, es auf Font zu setzen (wie es sollte), wird durch das Einstellen dieser FontEinstellung die Einstellung des Designers AutoScaleDimensionsgelöscht, was dazu führt, dass die automatische Skalierung tatsächlich deaktiviert wird! (Diese Richtlinie in Kombination mit der vorherigen bedeutet, dass Sie in einem Designer niemals Basisklassen instanziieren können. Alle Klassen müssen entweder als Basisklassen oder als Blattklassen entworfen werden!)

  • Vermeiden Sie die Verwendung Form.MaxSizestatisch / im Designer. MinSizeund MaxSizeauf Form skalieren Sie nicht so viel wie alles andere. Wenn Sie also Ihre gesamte Arbeit mit 96 dpi erledigen, verursachen Sie bei höheren DPI-Werten MinSizekeine Probleme, sind jedoch möglicherweise nicht so restriktiv wie erwartet, aber Sie MaxSizekönnen die Skalierung Ihrer Größe einschränken, was zu Problemen führen kann. Wenn Sie möchten MinSize == Size == MaxSize, tun Sie dies nicht im Designer ... tun Sie dies in Ihrem Konstruktor oder OnLoadüberschreiben Sie ... stellen Sie beide MinSizeund MaxSizeIhre richtig skalierte Größe ein.

  • Alle Steuerelemente eines bestimmten Paneloder Containersollten entweder Verankerung oder Andocken verwenden. Wenn Sie sie mischen, verhält sich die dadurch vorgenommene automatische Skalierung Paneloft auf subtile bizarre Weise schlecht.

  • Wenn es seine automatische Skalierung durchführt, wird es versuchen, das gesamte Formular zu skalieren. Wenn es dabei jedoch an die Obergrenze der Bildschirmgröße stößt, ist dies eine harte Grenze, die es dann vermasseln kann (Clip). die Skalierung. Stellen Sie daher sicher, dass alle Formulare im Designer mit 100% / 96 dpi nicht größer als 1024 x 720 sind (dies entspricht 150% auf einem 1080p-Bildschirm oder 300%, was dem von Windows empfohlenen Wert auf einem 4K-Bildschirm entspricht). Aber Sie müssen für die riesige Win10-Titel- / Beschriftungsleiste subtrahieren ... also eher 1000x680 max Size ... was im Designer 994x642 ClientSize entspricht. (Sie können also auf ClientSize FindAll-Referenzen erstellen, um Verstöße zu finden.)

kjbartel
quelle
NumericUpDownskaliert es auch nicht Marginrichtig. Es scheint, dass der Rand zweimal skaliert ist. Wenn ich es einmal verkleinere, sieht es gut aus.
Ygoe
AutoScaleMode = Fontfunktioniert nicht gut für Benutzer, die eine sehr große Schriftart verwenden und mit unter Ubuntu. Wir bevorzugenAutoScaleMode = DPI
KindDragon
> TextBox mit MultiLine = True und Font geerbt. Den ganzen Tag verrückt werden - das war das Problem! Vielen Dank! Das gleiche Update ist übrigens auch das Update für ListBox-Steuerelemente. : D
neminem
Listenfelder mit geerbter Schrift lassen sich für mich nicht gut skalieren. Sie tun nach explizit gesetzt. (.NET 4.7)
PulseJet
27

Meine Erfahrung war ziemlich anders als die aktuelle Antwort mit der höchsten Abstimmung. Durch Durchlaufen des .NET Framework-Codes und Durchsuchen des Referenzquellcodes kam ich zu dem Schluss, dass alles vorhanden ist, damit die automatische Skalierung funktioniert, und dass irgendwo nur ein subtiles Problem aufgetreten ist. Dies stellte sich als wahr heraus.

Wenn Sie ein ordnungsgemäß reflowfähiges Layout mit automatischer Größe erstellen, funktioniert fast alles genau so, wie es automatisch mit den von Visual Studio verwendeten Standardeinstellungen funktionieren sollte (nämlich AutoSizeMode = Schriftart im übergeordneten Formular und Inherit für alles andere).

Das einzige Problem ist, wenn Sie die Font-Eigenschaft für das Formular im Designer festgelegt haben. Der generierte Code sortiert die Zuordnungen alphabetisch, dh die Zuweisungen AutoScaleDimensionswerden zuvor zugewiesen Font. Leider bricht dies die automatische Skalierungslogik von WinForms vollständig.

Das Update ist jedoch einfach. Legen Sie die FontEigenschaft entweder überhaupt nicht im Designer fest (legen Sie sie in Ihrem Formularkonstruktor fest) oder ordnen Sie diese Zuweisungen manuell neu an (aber Sie müssen dies jedes Mal tun, wenn Sie das Formular im Designer bearbeiten). Voila, nahezu perfekte und vollautomatische Skalierung mit minimalem Aufwand. Auch die Formulargrößen sind korrekt skaliert.


Ich werde hier bekannte Probleme auflisten, wenn ich auf sie stoße:

  • Verschachtelt TableLayoutPanel berechnet die Kontrollränder falsch . Keine bekannte Problemumgehung, außer Ränder und Auffüllungen insgesamt zu vermeiden - oder verschachtelte Tabellenlayoutfelder zu vermeiden.
Roman Starkov
quelle
1
Keine Einstellung Fontim Designer: Ein Gedanke kommt in den Sinn: Stellen Sie die Schriftart im Designer ein, damit Sie mit der gewünschten Schriftart entwerfen können. DANN im Konstruktor nach dem Layout diese Schrifteigenschaft lesen und denselben Wert wieder zurücksetzen? Oder fragen Sie einfach nach dem Layout, das noch einmal gemacht werden soll? [Vorsichtsmaßnahme: Ich hatte keinen Grund, diesen Ansatz zu testen.] Oder gemäß der Antwort von Knowleech im Designer in Pixel angeben (damit der Visual Studio-Designer auf einem Monitor mit hoher DPI nicht neu skaliert) und im Code diesen Wert lesen, aus Pixeln konvertieren zu Punkten (um die richtige Skalierung zu erhalten).
ToolmakerSteve
1
Für jedes einzelne Bit unseres Codes werden die Abmessungen für die automatische Skalierung direkt vor dem automatischen Skalierungsmodus festgelegt, und alles wird perfekt skaliert. Scheint, als ob die Reihenfolge in den meisten Fällen keine Rolle spielt.
Josh
Ich habe meinen Code nach Fällen durchsucht, in denen AutoScaleDimensionsnicht die new SizeF(6F, 13F)in der oberen Antwort empfohlenen Werte festgelegt wurden. Es stellte sich heraus, dass in jedem Fall die Font-Eigenschaft des Formulars festgelegt wurde (nicht die Standardeinstellung). Es scheint, dass wenn AutoScaleMode = Font, dann AutoScaleDimensionsbasierend auf der Schrifteigenschaft des Formulars berechnet wird. Auch die Skalierungseinstellung in der Windows-Systemsteuerung scheint Auswirkungen zu haben AutoScaleDimensions.
Walter Stabosz
24

Richten Sie Ihre Anwendung auf .Net Framework 4.7 aus und führen Sie sie unter Windows 10 v1703 aus (Creators Update Build 15063). Mit .Net 4.7 unter Windows 10 (v1703) hat MS viele DPI-Verbesserungen vorgenommen .

Ab .NET Framework 4.7 enthält Windows Forms Verbesserungen für gängige Szenarien mit hoher und dynamischer DPI. Diese beinhalten:

  • Verbesserungen bei der Skalierung und dem Layout einer Reihe von Windows Forms-Steuerelementen, z. B. des MonthCalendar-Steuerelements und des CheckedListBox-Steuerelements.

  • Single-Pass-Skalierung. In .NET Framework 4.6 und früheren Versionen wurde die Skalierung über mehrere Durchgänge durchgeführt, wodurch einige Steuerelemente mehr als erforderlich skaliert wurden.

  • Unterstützung für dynamische DPI-Szenarien, in denen der Benutzer den DPI- oder Skalierungsfaktor nach dem Start einer Windows Forms-Anwendung ändert.

Fügen Sie Ihrer Anwendung ein Anwendungsmanifest hinzu und signalisieren Sie, dass Ihre App Windows 10 unterstützt:

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

Fügen Sie als Nächstes eine hinzu app.configund deklarieren Sie die App Per Monitor Aware. Dies geschieht JETZT in app.config und NICHT wie zuvor im Manifest!

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

Dieser PerMonitorV2 ist seit dem Windows 10 Creators Update neu:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Wird auch als Per Monitor v2 bezeichnet. Eine Weiterentwicklung des ursprünglichen DPI-Erkennungsmodus pro Monitor, mit dem Anwendungen auf Fensterbasis der obersten Ebene auf neue DPI-bezogene Skalierungsverhalten zugreifen können.

  • Benachrichtigungen über untergeordnete Fenster-DPI-Änderungen - In Per Monitor v2-Kontexten wird der gesamte Fensterbaum über alle auftretenden DPI-Änderungen benachrichtigt.

  • Skalierung des Nicht-Client-Bereichs - In allen Fenstern wird der Nicht-Client-Bereich automatisch DPI-sensitiv gezeichnet. Aufrufe von EnableNonClientDpiScaling sind nicht erforderlich.

  • S caling von Win32 - Menüs - Alle NTUSER Menüs in Pro - Monitor v2 Kontexte geschaffen wird in einer Pro-Monitor Mode werden die Skalierung.

  • Dialogskalierung - Win32-Dialoge, die in Per Monitor v2-Kontexten erstellt wurden, reagieren automatisch auf DPI-Änderungen.

  • Verbesserte Skalierung von comctl32-Steuerelementen - Verschiedene comctl32-Steuerelemente haben das DPI-Skalierungsverhalten in Per Monitor v2-Kontexten verbessert.

  • Verbessertes Theming-Verhalten - UxTheme-Handles, die im Kontext eines Per Monitor v2-Fensters geöffnet wurden, werden in Bezug auf die diesem Fenster zugeordnete DPI ausgeführt.

Jetzt können Sie 3 neue Ereignisse abonnieren, um über DPI-Änderungen informiert zu werden:

  • Control.DpiChangedAfterParent , das ausgelöst wird Tritt auf, wenn die DPI-Einstellung für ein Steuerelement programmgesteuert geändert wird, nachdem ein DPI-Änderungsereignis für das übergeordnete Steuerelement oder Formular aufgetreten ist.

  • Control.DpiChangedBeforeParent , das ausgelöst wird, wenn die DPI-Einstellung für ein Steuerelement programmgesteuert geändert wird, bevor ein DPI-Änderungsereignis für das übergeordnete Steuerelement oder Formular aufgetreten ist.

  • Form.DpiChanged , das ausgelöst wird, wenn sich die DPI-Einstellung auf dem Anzeigegerät ändert, auf dem das Formular derzeit angezeigt wird.

Sie haben auch 3 Hilfsmethoden zur Behandlung / Skalierung von DPI:

  • Control.LogicalToDeviceUnits , das einen Wert von logischen in Gerätepixel konvertiert.

  • Control.ScaleBitmapLogicalToDevice , das ein Bitmap-Image auf die logische DPI eines Geräts skaliert.

  • Control.DeviceDpi , das die DPI für das aktuelle Gerät zurückgibt.

Wenn weiterhin Probleme auftreten, können Sie die DPI-Verbesserungen über app.config-Einträge deaktivieren .

Wenn Sie keinen Zugriff auf den Quellcode haben, können Sie die Anwendungseigenschaften im Windows Explorer aufrufen, zur Kompatibilität wechseln und auswählen System (Enhanced)

Geben Sie hier die Bildbeschreibung ein

Hiermit wird die GDI-Skalierung aktiviert, um auch die DPI-Verarbeitung zu verbessern:

Für Anwendungen, die GDI-basiert sind, kann Windows diese jetzt pro Monitor DPI-skalieren. Dies bedeutet, dass diese Anwendungen auf magische Weise DPI-fähig werden.

Wenn Sie all diese Schritte ausführen, sollten Sie eine bessere DPI-Erfahrung für WinForms-Anwendungen erhalten. Denken Sie jedoch daran, dass Sie Ihre App auf .net 4.7 ausrichten müssen und mindestens Windows 10 Build 15063 (Creators Update) benötigen. Im nächsten Windows 10 Update 1709 werden wir möglicherweise weitere Verbesserungen erhalten.

magicandre1981
quelle
12

Ein Leitfaden, den ich bei der Arbeit geschrieben habe:

WPF arbeitet in "geräteunabhängigen Einheiten", was bedeutet, dass alle Steuerelemente perfekt auf Bildschirme mit hoher Auflösung skaliert werden können. In WinForms ist mehr Vorsicht geboten.

WinForms arbeitet in Pixel. Text wird entsprechend der System-dpi skaliert, aber häufig von einem nicht skalierten Steuerelement beschnitten. Um solche Probleme zu vermeiden, müssen Sie eine explizite Dimensionierung und Positionierung vermeiden. Befolgen Sie diese Regeln:

  1. Überall dort, wo Sie es finden (Beschriftungen, Schaltflächen, Bedienfelder), setzen Sie die AutoSize-Eigenschaft auf True.
  2. Verwenden Sie für das Layout FlowLayoutPanel (a la WPF StackPanel) und TableLayoutPanel (a la WPF Grid) für das Layout anstelle von Vanilla Panel.
  3. Wenn Sie auf einem Computer mit hoher Auflösung entwickeln, kann der Visual Studio-Designer frustrierend sein. Wenn Sie AutoSize = True festlegen, wird die Größe des Steuerelements auf Ihrem Bildschirm geändert. Wenn das Steuerelement AutoSizeMode = GrowOnly hat, bleibt diese Größe für Personen mit normaler dpi erhalten, d. H. größer sein als erwartet. Um dies zu beheben, öffnen Sie den Designer auf einem Computer mit normaler Auflösung und klicken Sie mit der rechten Maustaste und setzen Sie ihn zurück.
Oberst Panik
quelle
3
Für Dialoge, deren Größe geändert werden kann, wäre AutoSize für alles ein Albtraum. Ich möchte nicht, dass meine Schaltflächen größer und kleiner werden, da ich meine Dialogfelder manuell vergrößere, während ich das Programm ausführe.
Josh
10

Ich fand es sehr schwierig, WinForms dazu zu bringen, mit hoher DPI gut zu spielen. Also habe ich eine VB.NET-Methode geschrieben, um das Formularverhalten zu überschreiben:

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
quelle
6

Ich bin kürzlich auf dieses Problem gestoßen, insbesondere in Kombination mit der Neuskalierung von Visual Studio, wenn der Editor auf einem System mit hoher Auflösung geöffnet wird. Ich fand es am besten zu halten AutoScaleMode = Font , aber die Formen setzen Schriftart auf die Standardschrift, aber die Größe in Pixel spezifiziert , nicht Punkt, das heißt: Font = MS Sans; 11px. Im Code habe ich dann die Schriftart auf die Standardeinstellung zurückgesetzt: Font = SystemFonts.DefaultFontund alles ist in Ordnung.

Nur meine zwei Cent. Ich dachte, ich teile, weil "AutoScaleMode = Schriftart beibehalten" und "Schriftgröße in Pixel für den Designer festlegen " etwas war, das ich im Internet nicht gefunden habe.

Ich habe einige weitere Details in meinem Blog: http://www.sgrottel.de/?p=1581&lang=de

Knowleech
quelle
4

Zusätzlich zu den Ankern, die nicht sehr gut funktionieren: Ich würde einen Schritt weiter gehen und sagen, dass die genaue Positionierung (auch bekannt als Verwendung der Location-Eigenschaft) mit der Schriftartenskalierung nicht sehr gut funktioniert. Ich musste dieses Problem in zwei verschiedenen Projekten ansprechen. In beiden Fällen mussten wir die Positionierung aller WinForms-Steuerelemente in die Verwendung von TableLayoutPanel und FlowLayoutPanel konvertieren. Die Verwendung der Dock-Eigenschaft (normalerweise auf Fill gesetzt) ​​im TableLayoutPanel funktioniert sehr gut und lässt sich gut mit der DPI-Systemschriftart skalieren.

Brannon
quelle