Jak napisać kod WinForms, który automatycznie skaluje się do systemowych ustawień czcionek i dpi?


143

Wprowadzenie: jest wiele komentarzy, które mówią „WinForms nie skaluje się automatycznie do ustawień DPI / czcionki; przełącz się na WPF”. Myślę jednak, że jest to oparte na .NET 1.1; wygląda na to, że wykonali całkiem niezłą robotę, implementując automatyczne skalowanie w .NET 2.0. Przynajmniej na podstawie naszych dotychczasowych badań i testów. Jeśli jednak niektórzy z was wiedzą lepiej, chcielibyśmy usłyszeć od was. (Proszę, nie kłopocz się argumentowaniem, że powinniśmy przejść na WPF ... to obecnie nie jest opcja).

Pytania:

  • Co w WinForms NIE skaluje się automatycznie i dlatego należy tego unikać?

  • Jakich wytycznych projektowych powinni przestrzegać programiści pisząc kod WinForms, aby był on dobrze skalowany automatycznie?

Wytyczne projektowe, które dotychczas zidentyfikowaliśmy:

Zobacz odpowiedź wiki społeczności poniżej.

Czy któreś z nich są nieprawidłowe lub nieodpowiednie? Jakieś inne wytyczne, które powinniśmy przyjąć? Czy są jakieś inne wzorce, których należy unikać? Wszelkie inne wskazówki na ten temat byłyby bardzo mile widziane.

Odpowiedzi:


127

Elementy sterujące, które nie obsługują poprawnie skalowania:

  • Labelz AutoSize = Falsei Fontdziedziczone. Jawnie ustawiony Fontna formancie, tak aby był pogrubiony w oknie Właściwości.
  • ListViewszerokości kolumn nie są skalowane. Zastąp formularz, ScaleControlaby to zrobić. Zobacz tę odpowiedź
  • SplitContainer„S Panel1MinSize, Panel2MinSizei SplitterDistancewłaściwości
  • TextBoxz MultiLine = Truei Fontdziedziczone. Jawnie ustawiony Fontna formancie, tak aby był pogrubiony w oknie Właściwości.
  • ToolStripButtonobraz. W konstruktorze formularza:

    • Zestaw ToolStrip.AutoSize = False
    • Ustaw ToolStrip.ImageScalingSizezgodnie z CreateGraphics.DpiXi.DpiY
    • Ustaw w ToolStrip.AutoSize = Truerazie potrzeby.

    Czasami AutoSizemożna go pozostawić, Trueale czasami nie można zmienić rozmiaru bez tych kroków. Działa bez tych zmian z .NET Framework 4.5.2 i EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewobrazy użytkownika. Ustaw ImageList.ImageSizezgodnie z CreateGraphics.DpiXi .DpiY. Ponieważ StateImageListdziała bez tych zmian z .NET Framework 4.5.1 i EnableWindowsFormsHighDpiAutoResizing.
  • Formrozmiar. Skaluj stały rozmiar Formręcznie po utworzeniu.

Wytyczne dotyczące projektowania:

  • Wszystkie elementy ContainerControls muszą być ustawione na to samo AutoScaleMode = Font. (Czcionka będzie obsługiwać zarówno zmiany DPI, jak i zmiany w ustawieniu rozmiaru czcionki systemowej; DPI będzie obsługiwać tylko zmiany DPI, a nie zmiany w ustawieniu rozmiaru czcionki systemowej).

  • Wszystkie elementy ContainerControl muszą być również ustawione na to samo AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, zakładając 96 dpi (patrz następny punkt) i domyślną czcionkę MS Sans Serif (patrz punkt drugi poniżej). Jest to automatycznie dodawane przez projektanta na podstawie DPI, w którym otworzyłeś projektanta ... ale brakowało go w wielu naszych najstarszych plikach projektanta. Być może program Visual Studio .NET (wersja przed VS 2005) nie dodawał go poprawnie.

  • Wykonuj wszystkie prace projektanta w 96 dpi (być może uda nam się przełączyć na 120 dpi; ale mądrość w Internecie mówi, aby trzymać się 96 dpi; eksperymentowanie jest w porządku; z założenia nie powinno to mieć znaczenia, ponieważ zmienia tylko AutoScaleDimensionslinię, która projektant wkładki). Aby ustawić program Visual Studio tak, aby działał w wirtualnym 96 dpi na wyświetlaczu o wysokiej rozdzielczości, znajdź jego plik .exe, kliknij prawym przyciskiem myszy, aby edytować właściwości, i w obszarze Zgodność wybierz opcję „Zastąp zachowanie skalowania w wysokiej rozdzielczości DPI. Skalowanie wykonywane przez: System”.

  • Upewnij się, że nigdy nie ustawiłeś Czcionki na poziomie kontenera ... tylko na kontrolkach liści LUB w konstruktorze najbardziej podstawowego formularza, jeśli chcesz mieć domyślną czcionkę dla całej aplikacji inną niż MS Sans Serif. (Ustawienie czcionki w kontenerze wydaje się wyłączać automatyczne skalowanie tego kontenera, ponieważ następuje to alfabetycznie po ustawieniu ustawień AutoScaleMode i AutoScaleDimensions). UWAGA, jeśli zmienisz czcionkę w konstruktorze najbardziej podstawowego formularza, spowoduje to Twoje AutoScaleDimensions do obliczania inaczej niż 6x13; w szczególności, jeśli zmienisz na Segoe UI (domyślna czcionka Win 10), to będzie to 7x15 ... będziesz musiał dotknąć każdego formularza w Projektancie, aby mógł ponownie obliczyć wszystkie wymiary w tym pliku .designer, w tym the AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • NIE używaj zakotwiczenia Rightlub Bottomzakotwiczenia w UserControl ... jego położenie nie będzie automatycznie skalowane; zamiast tego upuść Panel lub inny kontener do Kontroli Użytkownika i zakotwicz inne Kontrolki do tego Panelu; mają obsłudze panel Dock Right, Bottomlub Fillw swoim UserControl.

  • Tylko elementy sterujące na listach elementów sterujących, gdy wywoływana jest ResumeLayoutfunkcja na końcu, InitializeComponentbędą skalowane automatycznie ... jeśli dodasz elementy sterujące dynamicznie, SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();musisz je włączyć, zanim je dodasz. Pozycjonowanie również będzie wymagało dostosowania jeśli nie używasz trybów Docka ani Menedżera układu, takiego jak FlowLayoutPanellub TableLayoutPanel.

  • Klasy bazowe, z których pochodzą, ContainerControlnależy pozostawić AutoScaleModeustawione na Inherit(wartość domyślna ustawiona w klasie ContainerControl; ale NIE domyślna ustawiona przez projektanta). Jeśli ustawisz ją na cokolwiek innego, a następnie twoja klasa pochodna spróbuje ustawić ją na Font (tak, jak powinna), wtedy ustawienie tego na Fontwyczyści ustawienie projektanta AutoScaleDimensions, co w rzeczywistości spowoduje wyłączenie automatycznego skalowania! (Ta wytyczna w połączeniu z poprzednią oznacza, że ​​nigdy nie można utworzyć wystąpienia klas bazowych w projektancie ... wszystkie klasy muszą być zaprojektowane jako klasy bazowe lub jako klasy liści!)

  • Unikaj używania Form.MaxSizestatycznego / w Projektancie. MinSizea MaxSizena Formie nie skaluj się tak bardzo, jak wszystko inne. Tak więc, jeśli wykonasz całą swoją pracę w 96dpi, to przy wyższej DPI MinSizenie spowoduje to problemów, ale może nie być tak restrykcyjne, jak się spodziewałeś, ale MaxSizemożesz ograniczyć skalowanie rozmiaru, co może powodować problemy. Jeśli chcesz MinSize == Size == MaxSize, nie rób tego w Projektancie ... rób to w swoim konstruktorze lub OnLoadnadpisuj ... ustaw oba MinSizei MaxSizeodpowiednio wyskalowany rozmiar.

  • Wszystkie Kontrolki na konkretnym Panellub Containerpowinny używać Kotwiczenia lub Dokowania. Jeśli je pomieszasz, automatyczne skalowanie wykonane przez to Panelczęsto będzie źle działać w subtelny dziwaczny sposób.

  • Kiedy wykona swoje automatyczne skalowanie, będzie próbować skalować ogólną formę ... jednak jeśli w tym procesie osiągnie górną granicę rozmiaru ekranu, jest to sztywny limit, który może następnie zepsuć (klip) skalowanie. Dlatego należy upewnić się, że wszystkie formularze w Projektancie przy 100% / 96 dpi mają rozmiar nie większy niż 1024x720 (co odpowiada 150% na ekranie 1080p lub 300%, co jest wartością zalecaną przez system Windows na ekranie 4K). Ale musisz odjąć gigantyczny pasek tytułu / podpisu Win10 ... a więc bardziej jak maksymalny rozmiar 1000x680 ... który w projektancie będzie taki jak 994x642 ClientSize. (Możesz więc wykonać funkcję FindAll References w ClientSize, aby znaleźć osoby naruszające.)


NumericUpDownteż nie skaluje się Marginprawidłowo. Wygląda na to, że margines jest dwukrotnie przeskalowany. Jeśli raz go przeskaluję, wygląda dobrze.
— ygoe

AutoScaleMode = Fontnie działa dobrze dla użytkowników, którzy używają bardzo dużej czcionki w systemie Ubuntu. WolimyAutoScaleMode = DPI
— KindDragon

> TextBox z MultiLine = True i Font są dziedziczone. Szalenie przez cały dzień - to była poprawka! Dzięki wielkie! Nawiasem mówiąc, ta sama poprawka jest również poprawką dla kontrolek ListBox. : D
— neminem

Dla mnie pola listy z odziedziczoną czcionką nie są dobrze skalowane. Robią to po jawnym ustawieniu. (.NET 4.7)
— PulseJet


27

Moje doświadczenie różni się od odpowiedzi, która jest obecnie najczęściej wybierana. Przechodząc przez kod platformy .NET i przeglądając kod źródłowy odniesienia, doszedłem do wniosku, że wszystko jest na miejscu, aby automatyczne skalowanie działało, i istnieje tylko subtelny problem, który go zepsuł. Okazało się, że to prawda.

Jeśli utworzysz układ z możliwością poprawiania / automatycznego określania rozmiaru, prawie wszystko działa dokładnie tak, jak powinno, automatycznie, z domyślnymi ustawieniami używanymi przez program Visual Studio (a mianowicie AutoSizeMode = Font w formularzu nadrzędnym i Inherit we wszystkich pozostałych).

Jedynym problemem jest ustawienie właściwości Font w formularzu w projektancie. Wygenerowany kod posortuje zadania alfabetycznie, co oznacza, że AutoScaleDimensionszostaną przypisane wcześniej Font . Niestety, to całkowicie łamie logikę automatycznego skalowania WinForms.

Poprawka jest jednak prosta. Albo w ogóle nie ustawiaj Fontwłaściwości w projektancie (ustaw ją w konstruktorze formularza), albo ręcznie zmień kolejność tych przypisań (ale musisz to robić za każdym razem, gdy edytujesz formularz w projektancie). Voila, prawie idealne iw pełni automatyczne skalowanie przy minimalnym wysiłku. Nawet rozmiary formularzy są poprawnie skalowane.


Wymienię tutaj znane problemy, które napotkam:

  • Zagnieżdżone TableLayoutPanel niepoprawnie oblicza marginesy kontrolne . Nie ma znanego obejścia poza całkowitym unikaniem marginesów i dopełnień - lub unikaniem zagnieżdżonych paneli układu tabeli.

1
Nie ustawiaj ponownie Fontw projektancie: przychodzi mi do głowy myśl: śmiało ustaw czcionkę w projektancie, abyś mógł zaprojektować z żądaną czcionką. WTEDY w konstruktorze, po układzie, odczytać właściwość czcionki i ponownie ustawić tę samą wartość? A może po prostu poproś o ponowne wykonanie układu? [Uwaga: nie miałem powodu, aby testować to podejście.] Lub zgodnie z odpowiedzią Knowleech , w projektancie określ w pikselach (aby projektant Visual Studio nie przeskalował na monitorze o wysokiej rozdzielczości DPI), a w kodzie odczytaj tę wartość, przekonwertuj z pikseli do punktów (aby uzyskać prawidłowe skalowanie).
— ToolmakerSteve

1
Każdy bit naszego kodu ma ustawione wymiary automatycznego skalowania tuż przed trybem automatycznego skalowania i wszystko jest idealnie skalowane. W większości przypadków kolejność nie ma znaczenia.
— Josh

Przeszukałem mój kod pod kątem przypadków, w których AutoScaleDimensionsnie został ustawiony new SizeF(6F, 13F)zgodnie z zaleceniami w górnej odpowiedzi. Okazało się, że w każdym przypadku właściwość Font formularza została ustawiona (nie domyślna). Wydaje się, że kiedy AutoScaleMode = Font, to AutoScaleDimensionsjest obliczane na podstawie właściwości czcionki formularza. Wydaje się również, że ustawienie skalowania w Panelu sterowania systemu Windows ma wpływ na AutoScaleDimensions.
— Walter Stabosz

24

Skieruj swoją aplikację na .Net Framework 4.7 i uruchom ją pod Windows 10 v1703 (Creators Update Build 15063). Dzięki .Net 4.7 pod Windows 10 (v1703) firma MS wprowadziła wiele ulepszeń DPI .

Począwszy od .NET Framework 4.7, Windows Forms zawiera ulepszenia dla typowych scenariuszy wysokiej rozdzielczości DPI i dynamicznej rozdzielczości DPI. Obejmują one:

  • Ulepszenia w skalowaniu i układzie wielu kontrolek Windows Forms, takich jak kontrolka MonthCalendar i kontrolka CheckedListBox.

  • Skalowanie jednoprzebiegowe. W .NET Framework 4.6 i wcześniejszych wersjach skalowanie było wykonywane przez wiele przebiegów, co powodowało, że niektóre kontrolki były skalowane bardziej niż było to konieczne.

  • Obsługa dynamicznych scenariuszy DPI, w których użytkownik zmienia DPI lub współczynnik skali po uruchomieniu aplikacji Windows Forms.

Aby to obsługiwać, dodaj manifest aplikacji do aplikacji i zasygnalizuj, że Twoja aplikacja obsługuje system Windows 10:

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

Następnie dodaj app.configi zadeklaruj aplikację Per Monitor Aware. Jest to TERAZ zrobione w app.config, a NIE w manifeście, jak wcześniej!

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

Ten PerMonitorV2 jest nowy od aktualizacji Windows 10 Creators Update:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Znany również jako Per Monitor v2. Ulepszenie w stosunku do oryginalnego trybu świadomości DPI na monitor, który umożliwia aplikacjom dostęp do nowych zachowań skalowania związanych z DPI na podstawie okna najwyższego poziomu.

  • Powiadomienia o zmianie DPI w oknie podrzędnym - w kontekstach Per Monitor v2 całe drzewo okna jest powiadamiane o wszelkich zmianach DPI, które mają miejsce.

  • Skalowanie obszaru nieklienckiego - wszystkie okna będą automatycznie narysowane w sposób wrażliwy na DPI. Wywołania EnableNonClientDpiScaling są niepotrzebne.

  • S caling menu Win32 - Menu Wszystkie NTUSER utworzone w kontekstach Per monitor v2 będzie skalowania w sposób per-monitor.

  • Skalowanie okien dialogowych - okna dialogowe Win32 utworzone w kontekstach Per Monitor v2 będą automatycznie reagować na zmiany DPI.

  • Ulepszone skalowanie kontrolek comctl32 - Różne kontrolki comctl32 mają ulepszone zachowanie skalowania DPI w kontekstach Per Monitor v2.

  • Ulepszone zachowanie motywów - uchwyty UxTheme otwarte w kontekście okna Per Monitor v2 będą działać pod względem DPI skojarzonego z tym oknem.

Teraz możesz zasubskrybować 3 nowe wydarzenia, aby otrzymywać powiadomienia o zmianach DPI:

  • Control.DpiChangedAfterParent , który jest wyzwalany Występuje, gdy ustawienie DPI formantu zostanie zmienione programowo po wystąpieniu zdarzenia zmiany DPI dla jego kontrolki nadrzędnej lub formularza.

  • Control.DpiChangedBeforeParent , który jest uruchamiany, gdy ustawienie DPI dla formantu zostanie zmienione programowo przed wystąpieniem zdarzenia zmiany DPI dla jego kontrolki nadrzędnej lub formularza.

  • Form.DpiChanged , który jest uruchamiany, gdy ustawienie DPI zmienia się na urządzeniu wyświetlającym, na którym formularz jest obecnie wyświetlany.

Masz również 3 pomocnicze metody obsługi / skalowania DPI:

  • Control.LogicalToDeviceUnits , która konwertuje wartość z pikseli logicznych na piksele urządzenia.

  • Control.ScaleBitmapLogicalToDevice , która skaluje obraz bitmapowy do logicznego DPI urządzenia.

  • Control.DeviceDpi , która zwraca wartość DPI dla bieżącego urządzenia.

Jeśli nadal widzisz problemy, możesz zrezygnować z ulepszeń DPI za pośrednictwem wpisów app.config .

Jeśli nie masz dostępu do kodu źródłowego, możesz przejść do właściwości aplikacji w Eksploratorze Windows, przejść do zgodności i wybrać System (Enhanced)

wprowadź opis obrazu tutaj

który aktywuje skalowanie GDI, aby również poprawić obsługę DPI:

W przypadku aplikacji opartych na GDI system Windows może teraz skalować DPI w zależności od monitora. Oznacza to, że te aplikacje w magiczny sposób będą rozpoznawać DPI każdego monitora.

Wykonaj wszystkie te kroki, a powinieneś uzyskać lepsze wrażenia z DPI dla aplikacji WinForms. Pamiętaj jednak, że musisz kierować swoją aplikację na .net 4.7 i potrzebujesz co najmniej Windows 10 Build 15063 (Creators Update). W następnej aktualizacji Windows 10 1709 możemy uzyskać więcej ulepszeń.


12

Poradnik, który napisałem w pracy:

WPF działa w „jednostkach niezależnych od urządzenia”, co oznacza, że ​​wszystkie elementy sterujące skalują się idealnie do ekranów o wysokiej rozdzielczości. W WinForms wymaga to większej ostrożności.

WinForms działa w pikselach. Tekst zostanie przeskalowany zgodnie z systemową rozdzielczością dpi, ale często będzie przycinany przez nieskalowaną kontrolkę. Aby uniknąć takich problemów, musisz unikać jawnego określania rozmiaru i pozycjonowania. Przestrzegaj tych zasad:

  1. Gdziekolwiek go znajdziesz (etykiety, przyciski, panele) ustaw właściwość AutoSize na True.
  2. W przypadku układu użyj FlowLayoutPanel (a la WPF StackPanel) i TableLayoutPanel (a la WPF Grid) dla układu zamiast panelu waniliowego.
  3. Jeśli tworzysz na maszynie o wysokiej rozdzielczości, projektant Visual Studio może być frustracją. Po ustawieniu AutoSize = True, zmieni rozmiar kontrolki na ekran. Jeśli kontrolka ma AutoSizeMode = GrowOnly, pozostanie w tym rozmiarze dla osób o normalnej rozdzielczości, tj. być większe niż oczekiwano. Aby to naprawić, otwórz projektanta na komputerze z normalną rozdzielczością i kliknij prawym przyciskiem myszy, zresetuj.

3
w przypadku okien dialogowych, których rozmiar można zmieniać AutoSize na wszystkim byłby koszmarem, nie chcę, aby moje przyciski stawały się coraz większe i mniejsze, gdy ręcznie zwiększam rozmiar okna dialogowego podczas uruchamiania programu.
— Josh

10

Okazało się, że bardzo trudno jest sprawić, by WinForms grało dobrze z wysoką rozdzielczością. Dlatego napisałem metodę VB.NET, aby zastąpić zachowanie formularza:

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

6

Niedawno natknąłem się na ten problem, szczególnie w połączeniu z przeskalowaniem programu Visual Studio, gdy edytor jest otwarty w systemie o wysokiej rozdzielczości. Uważam, że najlepiej jest zachować AutoScaleMode = Font , ale ustawić czcionkę formularzy na czcionkę domyślną, ale określając rozmiar w pikselach , a nie w punktach, tj Font = MS Sans; 11px. : . W kodzie, I następnie zresetować czcionkę domyślną: Font = SystemFonts.DefaultFonti wszystko jest w porządku.

Tylko moje dwa centy. Myślałem, że się tym podzielę, ponieważ „zachowanie AutoScaleMode = Font” i „Ustaw rozmiar czcionki w pikselach dla Projektanta” było czymś, czego nie znalazłem w Internecie.

Więcej szczegółów znajduje się na moim blogu: http://www.sgrottel.de/?p=1581&lang=en


4

Oprócz tego, że kotwice nie działają zbyt dobrze: poszedłbym o krok dalej i powiedział, że dokładne pozycjonowanie (czyli użycie właściwości Location) nie działa zbyt dobrze ze skalowaniem czcionki. Musiałem rozwiązać ten problem w dwóch różnych projektach. W obu z nich musieliśmy przekonwertować pozycjonowanie wszystkich kontrolek WinForms na używanie TableLayoutPanel i FlowLayoutPanel. Używanie właściwości Dock (zwykle ustawionej na Fill) wewnątrz TableLayoutPanel działa bardzo dobrze i dobrze skaluje się z DPI czcionki systemowej.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.