Erstellen einer DPI-Aware-Anwendung


70

Ich habe eine Formularanwendung in C #. Wenn ich die DPI des Monitors ändere, bewegen sich alle Bedienelemente. Ich habe den Code verwendet this.AutoScaleMode = AutoScaleMode.Dpi, aber das Problem wurde dadurch nicht vermieden.

Hat jemand eine Idee?


Sie überprüfen überprüfen Sie diesen Blog auch in dieser Angelegenheit, ich denke, es bietet gute Informationen zum Thema: telerik.com/blogs/…
checho

FWIW, unter Windows 10 hatte ich einen besseren Erfolg, wenn ich das Gegenteil tat: Stellen Sie sicher, dass meine alte Windows Forms-App UNAWARE von DPI war. Dies zwang Windows 10, seine Standardskalierung für Winforms zu verwenden, was perfekt funktionierte. Um aus dem Code zu testen, setzen Sie SetProcessDpiAwareness (0). 0 = In einigen Aufzählungen nicht bekannt - Google für Details benötigt DllImport von "shcore.dll". Es wird empfohlen, dies in app.manifest durchzuführen. Ich erwähne Code nur als Test, um sicher zu sein.
ToolmakerSteve

Ich hatte ein Problem in einer C # Winforms-App, bei der sich die vom App-Fenster verwendete DPI von der Bildschirmeinstellung von 96 dpi (Skalierungsfaktor 125%) auf 120 dpi (Skalierungsfaktor 100%) änderte, aber nur beim Ausführen einer ausführbaren Datei - Problem tritt nicht auf, wenn in VS2013 IDE ausgeführt wird. Das Anhängen des Debuggers, um herauszufinden, wo Änderungen aufgetreten sind, führte zu nicht reproduzierbaren Ergebnissen. Wie oben durch Aufrufen von SetProcessDpiAwareness (0) behoben.
SimonKravis

Antworten:


111

BEARBEITEN: Ab .NET 4.7 hat Windows Forms die Unterstützung für High DPI verbessert. Weitere Informationen finden Sie auf docs.microsoft.com. Es funktioniert jedoch nur für Win 10 Creators Update und höher, sodass es je nach Benutzerbasis möglicherweise noch nicht möglich ist, dies zu verwenden.


Schwierig, aber nicht unmöglich. Ihre beste Option ist natürlich, zu WPF zu wechseln, aber das ist möglicherweise nicht machbar.

Ich habe viel Zeit mit diesem Problem verbracht. Hier sind einige Regeln / Richtlinien, damit es ohne FlowLayoutPanel oder TableLayoutPanel ordnungsgemäß funktioniert:

  • Bearbeiten / gestalten Sie Ihre Apps immer mit einer Standardeinstellung von 96 DPI (100%). Wenn Sie mit 120 DPI (125% f.ex) entwerfen, wird es sehr schlecht, wenn Sie zu 96 DPI zurückkehren, um später damit zu arbeiten.
  • Ich habe AutoScaleMode.Font mit Erfolg verwendet, ich habe AutoScaleMode.DPI nicht viel ausprobiert.
  • Stellen Sie sicher, dass Sie für alle Container (Formulare, Bedienfelder, Registerkarten, Benutzersteuerelemente usw.) die Standardschriftgröße verwenden. 8,25 px. Vorzugsweise sollte es nicht für alle Container in der Datei .Designer.cs festgelegt werden, damit die Standardschriftart aus der Containerklasse verwendet wird.
  • Alle Container müssen denselben AutoScaleMode verwenden
  • Stellen Sie sicher, dass für alle Container die folgende Zeile in der Datei Designer.cs festgelegt ist:

this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); // for design in 96 DPI

  • Wenn Sie unterschiedliche Schriftgrößen für Beschriftungen / Textfelder usw. festlegen müssen, legen Sie diese pro Steuerelement fest, anstatt die Schriftart für die Containerklasse festzulegen, da winforms die Schriftarteneinstellung für Container verwendet, um den Inhalt zu skalieren und f.ex ein Bedienfeld mit einer anderen Schriftgröße zu verwenden als es Form enthält, wird garantiert Probleme machen. Es könnte funktionieren, wenn das Formular und alle Container im Formular dieselbe Schriftgröße verwenden, aber ich habe es nicht ausprobiert.
  • Verwenden Sie einen anderen Computer oder eine virtuelle Windows-Installation (VMware, Virtual PC, VirtualBox) mit einer höheren DPI-Einstellung, um Ihr Design sofort zu testen. Führen Sie einfach die kompilierte EXE-Datei aus dem Ordner / bin / Debug auf dem DEV-Computer aus.

Ich garantiere, dass Sie in Ordnung sind, wenn Sie diese Richtlinien befolgen, auch wenn Sie Steuerelemente mit bestimmten Ankern platziert haben und kein Flowpanel verwenden. Wir haben eine auf diese Weise erstellte App auf Hunderten von Computern mit unterschiedlichen DPI-Einstellungen bereitgestellt und haben keine Beschwerden mehr. Alle Formulare / Container / Raster / Schaltflächen / Textfelder usw. sind korrekt skaliert, ebenso wie die Schriftart. Bilder funktionieren auch, aber bei hohen DPI-Werten neigen sie dazu, ein wenig pixelig zu werden.

BEARBEITEN: Dieser Link enthält viele gute Informationen, insbesondere wenn Sie AutoScaleMode.DPI verwenden: Link zu verwandten Fragen zum Stapelüberlauf


Ich habe bereits FlowLayoutPanel. Schreibe ich "this.AutoScaleDimensions = new System.Drawing.SizeF (6F, 13F);" in jedes Panel? und wo schreibe ich
RRR

2
Ich habe dies nicht mit FlowLayoutPanel versucht, aber in jeder Ihrer Formulare oder Benutzersteuerelemente .Designer.cs-Datei (die vom Visual Studio-Designer generierte Teilklassendatei) benötigen Sie die Sätze AutoScaleDimensions und AutoScaleMode. Dies gilt für eine "normale" Form, in der Sie die Steuerelemente mit Ankern platzieren, z. Sie haben eine bestimmte x- und y-Koordinate für den Standort
Trygve

3
danke, sehr nützlicher Beitrag. Ich hatte dieses Problem in einem Projekt, das ich nach jemand anderem beheben musste. Entfernen Sie einfach alle AutoScale * -Zeilen aus allen * .Designer.cs-Dateien, und alles funktioniert jetzt.
Alexey Yakovenko

3
Unser Experimentieren mit Ihrem obigen Rat zeigt, dass es ein guter Rat ist. ** Haben Sie, seit Sie das vor 3 Jahren gepostet haben, zusätzliche Richtlinien gelernt, die Sie befolgen müssen? ** Wir haben ein paar andere gefunden ... hier gepostet: stackoverflow.com/questions/22735174/…
Brian Kennedy

2
@MuratfromDaminionSoftware: Ja, es hat seine Vorbehalte. Zum Zeitpunkt der Veröffentlichung (vor 5 Jahren) waren hochauflösende Bildschirme nicht so häufig, aber das hat sich schnell geändert. Das Problem bei der Entwicklung mit einer höheren DPI besteht darin, dass der gesamte von .Designer generierte Code spezifisch für die höhere DPI wird und die Rückkehr zu einer niedrigeren DPI nach der Erstellung dazu führt, dass alles falsch skaliert. Das Aufsteigen auf eine höhere DPI nach dem Erstellen funktioniert jedoch einwandfrei.
Trygve

22

Hinweis: Dadurch werden die Steuerelemente nicht verschoben, wenn sich die Auflösung ändert. Dadurch wird nur verschwommener Text behoben !!.


So beheben Sie verschwommene Windows Forms in Einstellungen mit hoher Auflösung:

  1. Gehen Sie zum Formular-Designer und wählen Sie Ihr Formular aus (indem Sie auf die Titelleiste klicken).
  2. Drücken Sie F4, um das Eigenschaftenfenster zu öffnen.
  3. Suchen Sie dann die AutoScaleMode- Eigenschaft
  4. Ändern Sie es von Schriftart (Standard) in Dpi .

Gehen Sie nun zu Program.cs (oder der Datei, in der sich Ihre Main-Methode befindet) und ändern Sie sie wie folgt:

namespace myApplication
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            // ***this line is added***
            if (Environment.OSVersion.Version.Major >= 6)
                SetProcessDPIAware();

            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new MainForm());
        }

        // ***also dllimport of that function***
        [System.Runtime.InteropServices.DllImport("user32.dll")]
        private static extern bool SetProcessDPIAware();
    }
}

Speichern und kompilieren. Jetzt sollte Ihre Form wieder knusprig aussehen.


Quelle: http://crsouza.com/2015/04/13/how-to-fix-blurry-windows-forms-windows-in-high-dpi-settings/


Dies funktioniert sehr gut auf win10 mit VS2017! Vielen Dank
Andrea

Wenn Sie sich die Bildschirmbilder im Quelllink ansehen, sind sie ganz anders. Während der Ansatz der Verwendung der DPI-Modus-Skalierung die Unschärfe beheben kann, ändert sich die Größe der Steuerelemente, was sich auf das Layout auswirkt. Dies ist daher eine Lösung, die spröde ist und sorgfältig angewendet werden muss.
Jazimov

Funktioniert sehr gut gegen 2019 (Resulion 1920 x 1080), aber die Größe der Kontrolle hat eine andere Größe, aber ich bin damit
einverstanden

16

Ich habe endlich eine Lösung für das Problem der Bildschirmorientierung und der DPI-Handhabung gefunden.
Microsoft hat bereits ein Dokument zur Erläuterung bereitgestellt, das jedoch einen kleinen Fehler aufweist, der die DPI-Verarbeitung vollständig beeinträchtigt. Befolgen Sie einfach die im folgenden Dokument unter "Erstellen eines separaten Layoutcodes für jede Ausrichtung" angegebene Lösung. Http://msdn.microsoft.com/en-us/library/ms838174.aspx

Dann WICHTIGER Teil! Fügen Sie im Code für die Methoden Landscape () und Portrait () ganz am Ende jeweils die folgenden Zeilen hinzu:

this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;

Der Code für diese beiden Methoden wäre also wie folgt:

protected void Portrait()
{
   this.SuspendLayout();
   this.crawlTime.Location = new System.Drawing.Point(88, 216);
   this.crawlTime.Size = new System.Drawing.Size(136, 16);
   this.crawlTimeLabel.Location = new System.Drawing.Point(10, 216);
   this.crawlTimeLabel.Size = new System.Drawing.Size(64, 16);
   this.crawlStartTime.Location = new System.Drawing.Point(88, 200);
   this.crawlStartTime.Size = new System.Drawing.Size(136, 16);
   this.crawlStartedLabel.Location = new System.Drawing.Point(10, 200);
   this.crawlStartedLabel.Size = new System.Drawing.Size(64, 16);
   this.light1.Location = new System.Drawing.Point(208, 66);
   this.light1.Size = new System.Drawing.Size(16, 16);
   this.light0.Location = new System.Drawing.Point(192, 66);
   this.light0.Size = new System.Drawing.Size(16, 16);
   this.linkCount.Location = new System.Drawing.Point(88, 182);
   this.linkCount.Size = new System.Drawing.Size(136, 16);
   this.linkCountLabel.Location = new System.Drawing.Point(10, 182);
   this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
   this.currentPageBox.Location = new System.Drawing.Point(10, 84);
   this.currentPageBox.Size = new System.Drawing.Size(214, 90);
   this.currentPageLabel.Location = new System.Drawing.Point(10, 68);
   this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
   this.addressLabel.Location = new System.Drawing.Point(10, 4);
   this.addressLabel.Size = new System.Drawing.Size(214, 16);
   this.noProxyCheck.Location = new System.Drawing.Point(10, 48);
   this.noProxyCheck.Size = new System.Drawing.Size(214, 20);
   this.startButton.Location = new System.Drawing.Point(8, 240);
   this.startButton.Size = new System.Drawing.Size(216, 20);
   this.addressBox.Location = new System.Drawing.Point(10, 24);
   this.addressBox.Size = new System.Drawing.Size(214, 22);

   //note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
   this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
   this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;   //IMPORTANT
   this.ResumeLayout(false);
}

protected void Landscape()
{
   this.SuspendLayout();
   this.crawlTime.Location = new System.Drawing.Point(216, 136);
   this.crawlTime.Size = new System.Drawing.Size(96, 16);
   this.crawlTimeLabel.Location = new System.Drawing.Point(160, 136);
   this.crawlTimeLabel.Size = new System.Drawing.Size(48, 16);
   this.crawlStartTime.Location = new System.Drawing.Point(64, 120);
   this.crawlStartTime.Size = new System.Drawing.Size(248, 16);
   this.crawlStartedLabel.Location = new System.Drawing.Point(8, 120);
   this.crawlStartedLabel.Size = new System.Drawing.Size(48, 16);
   this.light1.Location = new System.Drawing.Point(296, 48);
   this.light1.Size = new System.Drawing.Size(16, 16);
   this.light0.Location = new System.Drawing.Point(280, 48);
   this.light0.Size = new System.Drawing.Size(16, 16);
   this.linkCount.Location = new System.Drawing.Point(80, 136);
   this.linkCount.Size = new System.Drawing.Size(72, 16);
   this.linkCountLabel.Location = new System.Drawing.Point(8, 136);
   this.linkCountLabel.Size = new System.Drawing.Size(64, 16);
   this.currentPageBox.Location = new System.Drawing.Point(10, 64);
   this.currentPageBox.Size = new System.Drawing.Size(302, 48);
   this.currentPageLabel.Location = new System.Drawing.Point(10, 48);
   this.currentPageLabel.Size = new System.Drawing.Size(100, 16);
   this.addressLabel.Location = new System.Drawing.Point(10, 4);
   this.addressLabel.Size = new System.Drawing.Size(50, 16);
   this.noProxyCheck.Location = new System.Drawing.Point(168, 16);
   this.noProxyCheck.Size = new System.Drawing.Size(152, 24);
   this.startButton.Location = new System.Drawing.Point(8, 160);
   this.startButton.Size = new System.Drawing.Size(304, 20);
   this.addressBox.Location = new System.Drawing.Point(10, 20);
   this.addressBox.Size = new System.Drawing.Size(150, 22);

   //note! USING JUST AUTOSCALEMODE WILL NOT SOLVE ISSUE. MUST USE BOTH!
   this.AutoScaleDimensions = new System.Drawing.SizeF(96F, 96F); //IMPORTANT
   this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Dpi;   //IMPORTANT
   this.ResumeLayout(false);
}

Funktioniert wie Charme für mich.


Creating Separate Layout Code for Each OrientationTeil ist für die ursprüngliche Frage, eine Winform-Anwendung, irrelevant.
Neolisk


3

Es ist wirklich schwierig, DPI-fähige Anwendungen in Windows Forms zu entwerfen. Sie müssten Layout-Container verwenden, deren Größe sich ändert, wenn die DPI geändert wird (z. B. TableLayoutPanel oder FlowLayoutPanel). Alle Steuerelemente müssen ebenfalls in der Größe geändert werden. Die Konfiguration dieser Container kann eine Herausforderung sein.

Für einfache Anwendungen kann dies innerhalb eines angemessenen Zeitraums durchgeführt werden, für große Anwendungen ist es jedoch sehr viel Arbeit.


3

Aus Erfahrung:

  • Verwenden Sie die DPI-Erkennung nicht für Windows-Formulare, es sei denn, dies ist kritisch
  • Setzen Sie zu diesem Zweck die AutoScaleModeEigenschaft für Nonealle Formulare und Benutzersteuerelemente in Ihrer App immer auf
  • Das Ergebnis: WYSIWYG-Schnittstellentyp, wenn sich die DPI-Einstellungen ändern

Ich hatte eine Situation, in der das Festlegen von AutoScaleMode in den Schriftmodus dazu führte, dass die App abstürzte, sobald ich versuchte, Windows in Medium (125% oder 120ppi) anstelle des üblichen "Smaller" (96ppi) auszuführen. Ich habe AutoScaleMode auf None gesetzt, wie Sie sagten, und alles scheint soweit in Ordnung zu sein ...
Dan W

1
  1. Wenn Ihre WinForms-Anwendung eine DPI-bewusste Anwendung sein soll, zusätzlich zu Trygve eine gute Antwort: Wenn Sie ein großes Projekt haben, möchten Sie Ihre Formulare und deren Inhalt möglicherweise automatisch skalieren. Sie können dies tun, indem Sie die ScaleByDPI-Funktion erstellen:

Die ScaleByDPI-Funktion empfängt einen Control-Parameter, der normalerweise ein Formular ist, und durchläuft dann rekursiv alle Untersteuerelemente (if (control.HasChildren == true)) Betriebssystem konfigurierte DPI. Sie können versuchen, es auch für Bilder, Symbole und Grafiken zu implementieren.

Besondere Hinweise für die ScaleByDPI-Funktion:

ein. Für alle Steuerelemente mit Standardschriftgrößen müssen Sie ihre Schriftgröße auf 8,25 festlegen.

b. Sie können die Werte für devicePixelRatioX und devicePixelRatioY über (control.CreateGraphics (). DpiX / 96) und (control.CreateGraphics (). DpiY / 96) abrufen.

c. Sie benötigen die Skalierung Control.Size & Control.Location nach Algorithmus, der auf control.Dock & control.Anchor-Werten basiert. Beachten Sie, dass control.Dock möglicherweise 1 von 6 möglichen Werten und control.Anchor 1 von 16 möglichen Werten hat.

d. Für diesen Algorithmus müssen Werte für die nächsten Bool-Variablen festgelegt werden.

e. Wenn Ihr Projekt eine andere Steuerelementbibliothek als Microsoft-Steuerelemente verwendet, müssen diese Steuerelemente möglicherweise speziell behandelt werden.

Weitere Informationen zu den oben genannten (d.) Bool-Variablen:

* Manchmal muss eine Gruppe von Steuerelementen (möglicherweise eine Schaltfläche) nacheinander auf derselben vertikalen Linie platziert werden, und ihr Ankerwert umfasst Rechts, aber nicht Links, oder sie müssen nacheinander auf derselben horizontalen Linie platziert werden, und ihre Der Ankerwert umfasst "Unten", aber nicht "Oben". In diesem Fall müssen Sie die Positionswerte der Steuerelemente neu berechnen.

* Bei Steuerelementen, die Anchor oben und unten sowie \ oder links und rechts enthält, müssen Sie die Werte für Größe und Position der Steuerelemente neu faktorisieren.

Verwendung der ScaleByDPI-Funktion:

ein. Fügen Sie am Ende eines beliebigen Formularkonstruktors den nächsten Befehl hinzu: ScaleByDPI (this);

b. Auch beim dynamischen Hinzufügen eines Steuerelements zu einem Formularaufruf an ScaleByDPI ([ControlName]).

  1. Wenn Sie die Größe oder Position eines Steuerelements nach dem Beenden des Konstruktors dynamisch festlegen, erstellen und verwenden Sie eine der nächsten Funktionen, um die skalierten Werte für Größe oder Position abzurufen: ScaleByDPI_X \ ScaleByDPI_Y \ ScaleByDPI_Size \ ScaleByDPI_Point

  2. Fügen Sie das dpiAware-Element zum Assembly-Manifest Ihrer Anwendung hinzu, um Ihre Anwendung als DPI-fähig zu markieren.

  3. Setzen Sie GraphicsUnit aller Control.Font auf System.Drawing.GraphicsUnit.Point

  4. Setzen Sie in * .Designer.cs-Dateien aller Container den AutoScaleMode-Wert auf System.Windows.Forms.AutoScaleMode.None

  5. In Steuerelementen wie ComboBox und TextBox hat das Ändern von Control.Size.Hieght keine Auswirkungen. In diesem Fall wird durch Ändern von Control.Font.Size die Höhe des Steuerelements festgelegt.

  6. Wenn der Wert für Form StartPosition FormStartPosition.CenterScreen ist, müssen Sie die Position des Fensters neu berechnen.


Ist AutoScaleMode.Font nicht für die meisten Anwendungen bevorzugt, wobei AutoScaleMode.DPI nur für Apps nützlich ist, die versuchen, einen bestimmten Prozentsatz des Bildschirms einzunehmen?
Jon Coombs

-1

Da ein Winform-Antragsformular möglicherweise Inhaltssteuerelemente UND Bilder enthält, ist es KEINE Lösung, dem System die Größe Ihres Fensters zu ändern. Wenn Sie jedoch ein Formular pro DPI-Auflösung mit ordnungsgemäß skalierten Bildern erstellen könnten, ist dies keine gute Idee. da mit zunehmender Bildschirmgröße die Schriftgröße abnimmt.

Wenn Sie eine andere DPI-Auflösung verwenden, zwingt das System Ihr Formular, die Größe, Position und Schriftart des Steuerelements neu zu definieren. ABER KEINE BILDER. Die Lösung besteht darin, die DPI des Formulars zur Laufzeit beim Laden so zu ändern, dass alles auf die ursprüngliche Größe und Position zurückkehrt.

Dies ist eine mögliche Lösung, die ich mit einer Kartenspielanwendung getestet habe, bei der ich etwa 80 Bildschaltflächen, TabControls usw. habe.

Fügen Sie in jedem form_Load-Ereignis dieses Code-Snippet hinzu:

  Dim dpi As Graphics = Me.CreateGraphics
    Select Case dpi.DpiX
        Case 120
            '-- Do nothing if your app has been desigbned with 120 dpi
        Case Else
    '-- I use 125 AND NOT 120 because 120 is 25% more than 96
            Me.Font = New Font(Me.Font.FontFamily, Me.Font.Size * 125 / dpi.DpiX)
    End Select

Außerdem ein schneller Trick zum Testen verschiedener Auflösungen auf demselben Computer ohne Neustart:

Ändern Sie über das Bedienfeld die Auflösung. Nicht neu starten! Schließen Sie stattdessen Ihre Sitzung und öffnen Sie eine neue mit demselben Benutzer.

Es gibt noch eine weitere Einschränkung: Wenn Sie die Größe und Position eines Steuerelements zur Laufzeit festlegen, sollten Sie denselben DPI-Faktor (z. B. 125 / Dpi.Dpix) auf die neuen Koordinaten anwenden. Sie sollten also eine globale DPIFactor-Variable aus dem Ereignis application.startup einrichten.

Zu guter Letzt:

Öffnen Sie Ihre Anwendung in Visual Studio NICHT mit einer anderen Auflösung als der ursprünglichen. Andernfalls werden ALLE IHRE STEUERUNGEN beim Öffnen jedes Formulars verschoben und in der Größe geändert, und es gibt keinen Weg zurück ...

Hoffe das hilft, viel Spaß beim Programmieren.


Haben Sie versucht, AutoScaleMode.None zu verwenden, anstatt die DPI Ihres Formulars auf 96 zurückzusetzen ?
Neolisk

5
Dies funktioniert so lange, bis Ihr Benutzer über einen HD-Monitor verfügt. Dann erhalten Sie unlesbare Inhalte. Dieser Ansatz deaktiviert im Wesentlichen die HD-Unterstützung.
Steve Sheldon
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.