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.

Brian Kennedy
źródło

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.)

kjbartel
źródło
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
W tym wątku reddit dotyczącym problemu skalowania winform znalazłem łącze do zastrzeżenia próbki DPI monitora demonstracyjnego Telerik, którego sam nie używałem. Ten artykuł
Telerika
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.
Roman Starkov
źródło
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ń.

magicandre1981
źródło
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.
Colonel Panic
źródło
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
jrh
źródło
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

Knowleech
źródło
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.

Brannon
źródło