it-swarm.com.de

Wie schreibe ich WinForms-Code, der sich automatisch an die Einstellungen für Systemschrift und dpi anpasst?

Intro: Es gibt eine Menge Kommentare, die besagen: "WinForms skaliert nicht automatisch auf DPI/Font-Einstellungen; wechseln Sie zu WPF." Ich denke jedoch, dass auf .NET 1.1 basiert; anscheinend haben sie die automatische Skalierung in .NET 2.0 ziemlich gut umgesetzt. Zumindest basierend auf unseren bisherigen Forschungen und Tests. Wenn jedoch einige von Ihnen es besser wissen, würden wir gerne von Ihnen hören. (Bitte streiten Sie nicht, wir sollten zu WPF wechseln ... das ist momentan keine Option.)

Fragen:

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

  • Welche Designrichtlinien sollten Programmierer beachten, wenn sie WinForms-Code so schreiben, dass er sich gut automatisch skalieren lässt?

Designrichtlinien, die wir bisher identifiziert haben:

Siehe Community-Wiki-Antwort unten.

Sind diese 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.

132
Brian Kennedy

Steuerelemente, die die Skalierung nicht richtig unterstützen:

  • Label mit _AutoSize = False_ und Font vererbt. Setzen Sie Font explizit auf das Steuerelement, damit es im Eigenschaftenfenster fett angezeigt wird.
  • ListView Spaltenbreiten werden nicht skaliert. Überschreiben Sie stattdessen die ScaleControl des Formulars. Siehe diese Antwort
  • SplitContainer 's _Panel1MinSize_, _Panel2MinSize_ und SplitterDistance Eigenschaften
  • TextBox mit _MultiLine = True_ und Font vererbt. Setzen Sie Font explizit auf das Steuerelement, damit es im Eigenschaftenfenster fett angezeigt wird.
  • Das Bild von ToolStripButton. Im Konstruktor des Formulars:

    • Set _ToolStrip.AutoSize = False_
    • Setze _ToolStrip.ImageScalingSize_ entsprechend _CreateGraphics.DpiX_ und _.DpiY_
    • Stellen Sie bei Bedarf _ToolStrip.AutoSize = True_ ein.

    Manchmal kann AutoSize bei True belassen werden, aber manchmal kann die Größe ohne diese Schritte nicht geändert werden. Funktioniert ohne dass sich das mit . NET Framework 4.5.2 und EnableWindowsFormsHighDpiAutoResizing ändert.

  • TreeViews Bilder. Stellen Sie _ImageList.ImageSize_ entsprechend _CreateGraphics.DpiX_ und _.DpiY_ ein. Funktioniert für StateImageList , ohne dass sich dies mit . NET Framework 4.5.1 und EnableWindowsFormsHighDpiAutoResizing ändert.
  • Größe von Form. Feste Größe skalieren Form 's manuell nach der Erstellung.

Designrichtlinien:

  • Alle ContainerControls müssen auf das gleiche _AutoScaleMode = Font_ gesetzt sein. (Die Schriftart verarbeitet sowohl DPI-Änderungen als auch Änderungen der Systemschriftgröße. DPI verarbeitet nur DPI-Änderungen, nicht jedoch Änderungen der Systemschriftgröße.)

  • Alle ContainerControls müssen auch mit AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); gesetzt werden, vorausgesetzt 96 dpi (siehe nächster Punkt). Dies wird vom Designer basierend auf der DPI, in der Sie den Designer öffnen, automatisch hinzugefügt. In vielen unserer ältesten Designdateien fehlte dies jedoch. Möglicherweise hat Visual Studio .NET (die Version vor VS 2005) dies nicht ordnungsgemäß hinzugefügt.

  • Arbeiten Sie bei all Ihren Designern mit 96 dpi (möglicherweise können wir auf 120 dpi umstellen; im Internet heißt es jedoch, dass Sie sich an 96 dpi halten müssen. Hier ist Experimentieren angesagt. Designbedingt sollte das keine Rolle spielen, da es nur den AutoScaleDimensions Zeile, die der Designer einfügt). Um Visual Studio so einzustellen, dass es 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 bei hoher DPI überschreiben. Skalierung durchgeführt von: System".

  • Stellen Sie sicher, dass Sie die Schriftart niemals auf Containerebene festlegen ... nur auf den Blattsteuerelementen. (Durch das Festlegen der Schriftart für einen Container wird die automatische Skalierung dieses Containers anscheinend deaktiviert.)

  • Verwenden Sie NICHT Anchor Right oder Bottom, die an einem UserControl verankert sind. Die 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. Lassen Sie das Panel Dock Right oder Dock Bottom in Ihrem UserControl verwenden.

  • Nur die Steuerelemente in den Steuerelementlisten, wenn ResumeLayout am Ende von InitializeComponent aufgerufen wird, werden automatisch skaliert. Wenn Sie Steuerelemente dynamisch hinzufügen, müssen Sie SuspendLayout();AutoScaleDimensions = new SizeF(6F, 13F); _AutoScaleMode = AutoScaleMode.Font;_ ResumeLayout(); auf diesem Steuerelement, bevor Sie es hinzufügen. Außerdem muss Ihre Positionierung angepasst werden, wenn Sie nicht die Dock-Modi oder ein Layout verwenden Manager wie FlowLayoutPanel oder TableLayoutPanel.

  • Von ContainerControl abgeleitete Basisklassen sollten AutoScaleMode auf Inherit setzen (der in Klasse ContainerControl festgelegte Standardwert; NICHT der von festgelegte Standardwert) der Designer). Wenn Sie einen anderen Wert festlegen und Ihre abgeleitete Klasse versucht, den Wert (wie gewünscht) auf Font zu setzen, wird durch das Festlegen von Font die Einstellung von AutoScaleDimensions, was dazu führt, dass die automatische Skalierung deaktiviert wird! (Diese mit der vorherigen kombinierte Richtlinie bedeutet, dass Sie in einem Designer niemals Basisklassen instanziieren können ... alle Klassen müssen entweder als Basisklassen oder als Blattklassen entworfen werden!)

  • Verwenden Sie _Form.MaxSize_ nicht statisch/im Designer. MinSize und MaxSize in Form skalieren nicht so stark wie alles andere. Wenn Sie also Ihre gesamte Arbeit mit 96 dpi erledigen, wird Ihre MinSize bei höheren DPI-Werten keine Probleme verursachen, aber möglicherweise nicht so restriktiv sein, wie Sie es erwartet haben, aber Ihre MaxSize kann Begrenzen Sie die Skalierung Ihrer Größe, was zu Problemen führen kann. Wenn Sie _MinSize == Size == MaxSize_ wollen, tun Sie das nicht im Designer ... tun Sie das in Ihrem Konstruktor oder setzen Sie OnLoad außer Kraft ... setzen Sie sowohl MinSize als auch MaxSize auf Ihre richtig skalierte Größe.

  • Alle Steuerelemente für eine bestimmte Panel oder Container sollten entweder Anchoring oder Docking verwenden. Wenn Sie sie mischen, verhält sich die von dieser Panel durchgeführte automatische Skalierung häufig auf subtile bizarre Weise falsch.

108
kjbartel

Meine Erfahrung war ziemlich anders als die derzeitige Antwort mit den meisten Stimmen. Als ich durch den .NET Framework-Code schritt und den Referenzquellcode durchlas, kam ich zu dem Schluss, dass alles vorhanden ist, damit die automatische Skalierung funktioniert, und es gab nur ein subtiles Problem, das irgendwo die Daten durcheinander brachte. Dies stellte sich als wahr heraus.

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

Das einzige Problem ist, wenn Sie die Font-Eigenschaft im Formular im Designer festgelegt haben. Der generierte Code sortiert die Zuweisungen alphabetisch, was bedeutet, dass AutoScaleDimensions zugewiesen wird vor Font. Leider bricht dies WinForms automatische Skalierungslogik vollständig.

Die Lösung ist jedoch einfach. Entweder legen Sie die Eigenschaft Font im Designer überhaupt nicht fest (legen Sie sie im Formularkonstruktor fest) oder ordnen Sie diese Zuweisungen manuell neu an (dies muss jedoch jedes Mal erfolgen, wenn Sie das Formular im Designer bearbeiten ). Voila, nahezu perfekte und vollautomatische Skalierung mit minimalem Aufwand. Auch die Formulargrößen werden korrekt skaliert.


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

  • Verschachteltes TableLayoutPanelberechnet die Kontrollränder falsch . Es ist keine Umgehung bekannt, die nicht ausreicht, um Ränder und Abstände zu vermeiden - oder geschachtelte Tabellenlayoutfelder zu vermeiden.
25
Roman Starkov

Richten Sie Ihre Anwendung auf .Net Framework 4.7 aus und führen Sie sie unter Windows 10 v1703 (Creators Update Build 15063) aus. 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 hohen und dynamischen DPI-Werten. Diese schließen ein:

  • Verbesserungen in der Skalierung und im Layout einiger Windows Forms-Steuerelemente, z. B. des MonthCalendar-Steuerelements und des CheckedListBox-Steuerelements.

  • Single-Pass-Skalierung. In .NET Framework 4.6 und früheren Versionen wurde die Skalierung in mehreren Durchläufen durchgeführt, wodurch einige Steuerelemente stärker als erforderlich skaliert wurden.

  • Unterstützung für dynamische DPI-Szenarien, in denen der Benutzer den DPI- oder Skalierungsfaktor ändert, nachdem eine Windows Forms-Anwendung gestartet wurde.

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 einen app.config Hinzu und 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> 

Dies PerMonitorV2 ist neu seit dem Windows 10 Creators Update:

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 Fensterebene auf neue DPI-bezogene Skalierungsverhalten zugreifen können.

  • Benachrichtigungen über untergeordnete DPI-Änderungen im Fenster - In Pro-Monitor-v2-Kontexten wird der gesamte Fensterbaum über auftretende DPI-Änderungen benachrichtigt.

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

  • S Aufrufen von Win32-Menüs - Alle in Per Monitor v2-Kontexten erstellten NTUSER-Menüs werden auf Monitorebene skaliert.

  • Dialogskalierung - In Pro Monitor v2-Kontexten erstellte Win32-Dialoge reagieren automatisch auf DPI-Änderungen.

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

  • Verbessertes Theming-Verhalten - UxTheme-Handles, die im Kontext eines Fensters für Pro Monitor v2 geöffnet wurden, werden in Bezug auf die mit diesem Fenster verknüpfte 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 DPI-Bearbeitung/Skalierung:

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

  • Control.ScaleBitmapLogicalToDevice , das ein Bitmap-Bild auf die logische DPI für ein Gerät 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, die Kompatibilität aufrufen und System (Enhanced) auswählen.

enter image description here

aktiviert die GDI Skalierung, um auch das DPI-Handling zu verbessern:

Für Anwendungen, die auf GDI basieren, kann Windows diese nun auf Monitorbasis per DPI skalieren. Dies bedeutet, dass diese Anwendungen auf magische Weise DPI-fähig für jeden Monitor werden.

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

19
magicandre1981

Ein Leitfaden, den ich bei der Arbeit schrieb:

WPF arbeitet in "geräteunabhängigen Einheiten", dh alle Steuerelemente lassen sich perfekt auf hochauflösende Bildschirme skalieren. In WinForms nimmt es mehr Sorgfalt.

WinForms arbeitet in Pixeln. Text wird entsprechend der System-DPI skaliert, aber häufig durch ein nicht skaliertes Steuerelement beschnitten. Um solche Probleme zu vermeiden, müssen Sie die explizite Dimensionierung und Positionierung vermeiden. Befolgen Sie diese Regeln:

  1. Stellen Sie die AutoSize-Eigenschaft, wo immer Sie sie finden (Beschriftungen, Schaltflächen, Bedienfelder), auf True ein.
  2. Verwenden Sie für das Layout FlowLayoutPanel (ein WPF-StackPanel) und TableLayoutPanel (ein WPF-Grid) 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 einstellen, wird die Größe des Steuerelements an Ihren Bildschirm angepasst. Wenn das Steuerelement AutoSizeMode = GrowOnly hat, bleibt diese Größe für Personen mit normaler Auflösung 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.
12
Colonel Panic

Ich fand es sehr schwierig, WinForms dazu zu bringen, Nice mit hoher DPI zu spielen. Deshalb 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
8
user3950597

Ich bin kürzlich auf dieses Problem gestoßen, insbesondere in Verbindung mit der Neuskalierung von Visual Studio, wenn der Editor auf einem System mit hoher Auflösung geöffnet wird. Ich fand es am besten, AutoScaleMode = Font, aber um die Schriftart für Formulare auf die Standardschriftart zu setzen, aber um die Größe in Pixel anzugeben , nicht zeigen, dh: Font = MS Sans; 11px. Im Code setze ich dann die Schriftart auf die Standardeinstellung zurück: Font = SystemFonts.DefaultFont und alles ist gut.

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

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

6
Knowleech

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

4
Brannon