Come scrivere il codice WinForms che si adatta automaticamente alle impostazioni di font e dpi del sistema?


143

Intro: ci sono molti commenti là fuori che dicono "WinForms non si adatta automaticamente alle impostazioni DPI / font; passa a WPF". Tuttavia, penso che sia basato su .NET 1.1; sembra che abbiano effettivamente fatto un ottimo lavoro nell'implementazione del ridimensionamento automatico in .NET 2.0. Almeno sulla base delle nostre ricerche e prove finora. Tuttavia, se alcuni di voi là fuori lo sapessero meglio, ci piacerebbe avere vostre notizie. (Per favore, non preoccuparti di sostenere che dovremmo passare a WPF ... al momento non è un'opzione.)

Domande:

  • Cosa in WinForms NON si ridimensiona automaticamente e quindi dovrebbe essere evitato?

  • Quali linee guida di progettazione dovrebbero seguire i programmatori quando scrivono il codice WinForms in modo tale che si ridimensioni automaticamente?

Linee guida di progettazione che abbiamo identificato finora:

Vedi la risposta wiki della community di seguito.

Qualcuno di questi è errato o inadeguato? Altre linee guida che dovremmo adottare? Ci sono altri schemi che devono essere evitati? Qualsiasi altra guida su questo sarebbe molto apprezzata.

Risposte:


127

Controlli che non supportano correttamente il ridimensionamento:

  • Labelcon AutoSize = Falseed Fontereditato. Impostato esplicitamente Fontsul controllo in modo che appaia in grassetto nella finestra Proprietà.
  • ListViewle larghezze di colonna non si ridimensionano. Sostituisci invece il modulo ScaleControlper farlo. Vedi questa risposta
  • SplitContainer's Panel1MinSize, Panel2MinSizee SplitterDistancele proprietà
  • TextBoxcon MultiLine = Trueed Fontereditato. Impostato esplicitamente Fontsul controllo in modo che appaia in grassetto nella finestra Proprietà.
  • ToolStripButtonimmagine di. Nel costruttore del modulo:

    • Impostato ToolStrip.AutoSize = False
    • Impostato ToolStrip.ImageScalingSizesecondo CreateGraphics.DpiXe.DpiY
    • Impostare ToolStrip.AutoSize = Truese necessario.

    A volte AutoSizepuò essere lasciato a, Truema a volte non riesce a ridimensionare senza quei passaggi. Funziona senza modifiche con .NET Framework 4.5.2 e EnableWindowsFormsHighDpiAutoResizing.

  • TreeViewimmagini di. Impostato ImageList.ImageSizesecondo CreateGraphics.DpiXe .DpiY. Per StateImageList, funziona senza che le modifiche con .NET Framework 4.5.1 e EnableWindowsFormsHighDpiAutoResizing.
  • Formdimensioni. Ridimensiona Formmanualmente le dimensioni fisse dopo la creazione.

Linee guida di progettazione:

  • Tutti i ContainerControls devono essere impostati sullo stesso AutoScaleMode = Font. (Il carattere gestirà sia le modifiche DPI sia le modifiche all'impostazione della dimensione del carattere del sistema; DPI gestirà solo le modifiche DPI, non le modifiche all'impostazione della dimensione del carattere del sistema.)

  • Tutti i ContainerControls devono anche essere impostati con lo stesso AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);, assumendo 96 dpi (vedi il prossimo punto elenco) e il carattere predefinito di MS Sans Serif (vedi il punto 2 in basso). Viene aggiunto automaticamente dal designer in base al DPI in cui si apre il designer ... ma mancava da molti dei nostri file di designer più vecchi. Forse Visual Studio .NET (la versione precedente a VS 2005) non lo stava aggiungendo correttamente.

  • Fai lavorare tutti i tuoi designer a 96 dpi (potremmo essere in grado di passare a 120 dpi; ma la saggezza su Internet dice di attenersi a 96 dpi; la sperimentazione è in ordine lì; in base alla progettazione, non dovrebbe importare perché cambia solo la AutoScaleDimensionslinea che il designer inserisce). Per impostare Visual Studio per l'esecuzione a 96 dpi virtuali su uno schermo ad alta risoluzione, trova il suo file .exe, fai clic con il pulsante destro del mouse per modificare le proprietà e in Compatibilità seleziona "Sostituisci comportamento di ridimensionamento DPI elevato. Ridimensionamento eseguito da: Sistema".

  • Assicurarsi di non impostare mai il carattere a livello di contenitore ... solo sui controlli foglia OPPURE nel costruttore del modulo più di base se si desidera un carattere predefinito a livello di applicazione diverso da MS Sans Serif. (L'impostazione del carattere su un contenitore sembra disattivare il ridimensionamento automatico di quel contenitore perché viene in ordine alfabetico dopo l'impostazione delle impostazioni AutoScaleMode e AutoScaleDimensions). NOTA che se si modifica il carattere nel costruttore del modulo più base, ciò causerà AutoScaleDimensions per il calcolo in modo diverso da 6x13; in particolare, se passi all'interfaccia utente di Segoe (il carattere predefinito di Win 10), sarà 7x15 ... dovrai toccare ogni Modulo nel Designer in modo che possa ricalcolare tutte le dimensioni in quel file .designer, incluso il AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);.

  • NON utilizzare Anchor Righto Bottomancorato a un UserControl ... il suo posizionamento non si ridimensionerà automaticamente; invece, rilasciare un Pannello o un altro contenitore nel tuo UserControl e ancorare gli altri Controlli a quel Pannello; avere l'uso del pannello Dock Right, Bottomo Fillnella vostra UserControl.

  • Solo i controlli negli elenchi Controlli quando viene chiamato ResumeLayoutalla fine di InitializeComponentverranno ridimensionati automaticamente ... se aggiungi dinamicamente controlli, allora devi farlo SuspendLayout(); AutoScaleDimensions = new SizeF(6F, 13F); AutoScaleMode = AutoScaleMode.Font; ResumeLayout();su quel controllo prima di aggiungerlo. E anche il tuo posizionamento dovrà essere regolato se non si utilizzano le modalità Dock o un Gestore layout come FlowLayoutPanelo TableLayoutPanel.

  • Le classi di base derivate ContainerControldevono essere AutoScaleModeimpostate su Inherit(il valore predefinito impostato nella classe ContainerControl; ma NON il valore predefinito impostato dal progettista). Se lo imposti su qualcos'altro, e quindi la tua classe derivata prova a impostarlo su Font (come dovrebbe), allora l'atto di impostare quello per Fontcancellare le impostazioni del designer AutoScaleDimensions, con il risultato di disattivare il ridimensionamento automatico! (Questa linea guida combinata con la precedente significa che non è mai possibile creare un'istanza di classi base in un designer ... tutte le classi devono essere progettate come classi base o come classi foglia!)

  • Evitare l'uso Form.MaxSizestatico / in Designer. MinSizee MaxSizesu Form non scalare tanto quanto tutto il resto. Quindi, se fai tutto il tuo lavoro a 96 dpi, quando a DPI più alti MinSizenon causerai problemi, ma potresti non essere così restrittivo come ti aspettavi, ma MaxSizepotresti limitare il ridimensionamento delle dimensioni, che può causare problemi. Se lo desideri MinSize == Size == MaxSize, non farlo nel Designer ... fallo nel costruttore o OnLoadesegui l'override ... imposta entrambi MinSizee MaxSizele dimensioni correttamente ridimensionate.

  • Tutti i controlli su un particolare Panelo Containerdovrebbero usare Ancoraggio o Ancoraggio. Se li mescoli, il ridimensionamento automatico fatto da quello Panelspesso si comporterà in modo bizzarro e sottile.

  • Quando esegue il ridimensionamento automatico, tenterà di ridimensionare il modulo generale ... tuttavia, se in quel processo si imbatte nel limite superiore delle dimensioni dello schermo, questo è un limite rigido che può quindi rovinare (clip) il ridimensionamento. Pertanto, è necessario assicurarsi che tutti i moduli in Designer al 100% / 96 dpi abbiano dimensioni non superiori a 1024x720 (che corrisponde al 150% su uno schermo 1080p o al 300% che è il valore consigliato da Windows su uno schermo 4K). Ma devi sottrarre la barra del titolo / didascalia gigante di Win10 ... quindi più come dimensione massima 1000x680 ... che nel progettista sarà come dimensione client 994x642. (Quindi, puoi fare un Trova tutti i riferimenti su ClientSize per trovare i trasgressori.)


NumericUpDownnon si ridimensiona Margincorrettamente, anche. Sembra che il margine sia ridimensionato due volte. Se lo ridimensiono una volta, sembra buono.
giovedì

AutoScaleMode = Fontnon funziona bene per gli utenti che usano un font molto grande e con Ubuntu. PreferiamoAutoScaleMode = DPI
KindDragon il

> TextBox con MultiLine = True e Font ereditati. Impazzire tutto il giorno - quella era la soluzione! Grazie mille! A proposito, la stessa correzione è anche la correzione per i controlli ListBox. : D
neminem,

Per me, le caselle di riepilogo con carattere ereditato non si adattano bene. Lo fanno dopo aver impostato esplicitamente. (.NET 4.7)
PulseJet,


27

La mia esperienza è stata abbastanza diversa dall'attuale risposta più votata. Esaminando il codice del framework .NET e esaminando il codice sorgente di riferimento, ho concluso che tutto è pronto per il funzionamento del ridimensionamento automatico e che da qualche parte c'era solo un sottile problema. Questo si è rivelato essere vero.

Se si crea un layout ridisegnabile / ridimensionato automaticamente, quasi tutto funziona esattamente come dovrebbe, automaticamente, con le impostazioni predefinite utilizzate da Visual Studio (vale a dire AutoSizeMode = Font nel modulo principale ed Eredita in tutto il resto).

L'unico gotcha è se hai impostato la proprietà Font sul modulo nel designer. Il codice generato ordinerà le assegnazioni in ordine alfabetico, il che significa che AutoScaleDimensionsverranno assegnate prima Font . Sfortunatamente, questo rompe completamente la logica di ridimensionamento automatico di WinForms.

La correzione è semplice però. O non impostare affatto la Fontproprietà nel designer (impostala nel costruttore del modulo) o riordina manualmente questi compiti (ma poi devi continuare a farlo ogni volta che modifichi il modulo nel designer). Voila, ridimensionamento quasi perfetto e completamente automatico con il minimo fastidio. Anche le dimensioni del modulo sono ridimensionate correttamente.


Elencherò i problemi noti qui quando li incontro:


1
Non ambientarti Fontnel designer: mi viene in mente un pensiero: vai avanti e imposta il font nel designer, in modo che tu possa progettare con il font desiderato. ALLORA nel costruttore, dopo il layout, leggi quella proprietà del font e ripristina lo stesso valore? O forse semplicemente chiedere di ripetere il layout? [Avvertenza: non ho avuto motivo di testare questo approccio.] O per la risposta di Knowleech , in designer specificare in pixel (quindi il designer di Visual Studio non ridimensionerà su monitor DPI elevato), e in codice leggere quel valore, convertire da pixel ai punti (per ottenere il ridimensionamento corretto).
ToolmakerSteve

1
Ogni singolo bit del nostro codice ha le dimensioni della scala automatica impostate proprio prima della modalità di scala automatica e tutto si adatta perfettamente. Sembra che l'ordine non abbia importanza nella maggior parte dei casi.
Josh,

Ho cercato il mio codice per istanze in cui AutoScaleDimensionsnon era impostato new SizeF(6F, 13F)come raccomandato nella risposta in alto. Si è scoperto che in ogni istanza era stata impostata la proprietà Font del modulo (non predefinita). Sembra che quando AutoScaleMode = Font, quindi, AutoScaleDimensionsviene calcolato in base alla proprietà del carattere del modulo. Inoltre, l' impostazione di ridimensionamento nel Pannello di controllo di Windows sembra influire AutoScaleDimensions.
Walter Stabosz,

24

Targeting dell'applicazione per .Net Framework 4.7 ed esecuzione su Windows 10 v1703 (Creators Update Build 15063). Con .Net 4.7 in Windows 10 (v1703), MS ha apportato molti miglioramenti DPI .

A partire da .NET Framework 4.7, Windows Form include miglioramenti per scenari DPI elevati e DPI dinamici comuni. Questi includono:

  • Miglioramenti nel ridimensionamento e nel layout di numerosi controlli di Windows Form, come il controllo MonthCalendar e il controllo CheckedListBox.

  • Ridimensionamento a passaggio singolo. In .NET Framework 4.6 e versioni precedenti, il ridimensionamento veniva eseguito attraverso più passaggi, il che causava il ridimensionamento di alcuni controlli più del necessario.

  • Supporto per scenari DPI dinamici in cui l'utente modifica il DPI o il fattore di scala dopo l'avvio di un'applicazione Windows Form.

Per supportarlo, aggiungi un manifest dell'applicazione alla tua applicazione e segnala che l'app supporta Windows 10:

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

Quindi, aggiungere un app.confige dichiarare l'app per monitor consapevole. Questo è ORA fatto in app.config e NON nel manifest come prima!

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

Questo PerMonitorV2 è nuovo dall'aggiornamento dei creatori di Windows 10:

DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2

Conosciuto anche come Per Monitor v2. Un avanzamento rispetto alla modalità di consapevolezza DPI per monitor originale, che consente alle applicazioni di accedere a nuovi comportamenti di ridimensionamento relativi a DPI su una finestra di livello superiore.

  • Notifiche di modifica DPI finestra figlio - Nei contesti Per Monitor v2, l'intero albero della finestra viene informato di eventuali modifiche DPI che si verificano.

  • Ridimensionamento dell'area non client - Tutte le finestre avranno automaticamente l'area non client disegnata in modo DPI sensibile. Le chiamate a EnableNonClientDpiScaling non sono necessarie.

  • S caling dei menu Win32 - menu Tutte NTUSER create in contesti per monitor v2 sarà di scala in modo per-monitor.

  • Dialog Scaling - Le finestre di dialogo Win32 create in contesti Per Monitor v2 risponderanno automaticamente alle modifiche DPI.

  • Ridimensionamento migliorato dei controlli comctl32 - Vari controlli comctl32 hanno migliorato il comportamento del ridimensionamento DPI nei contesti Per Monitor v2.

  • Miglioramento del comportamento dei temi : gli handle UxTheme aperti nel contesto di una finestra Per Monitor v2 funzioneranno in termini di DPI associato a quella finestra.

Ora puoi iscriverti a 3 nuovi eventi per ricevere notifiche sulle modifiche DPI:

  • Control.DpiChangedAfterParent , che viene attivato Si verifica quando l'impostazione DPI per un controllo viene modificata a livello di codice dopo che si è verificato un evento di modifica DPI per il relativo controllo o modulo padre.

  • Control.DpiChangedBeforeParent , che viene generato quando l'impostazione DPI per un controllo viene modificata a livello di codice prima che si verifichi un evento di modifica DPI per il relativo controllo o modulo.

  • Form.DpiChanged , che viene attivato quando l'impostazione DPI cambia sul dispositivo di visualizzazione in cui il modulo è attualmente visualizzato.

Hai anche 3 metodi di supporto per la gestione / ridimensionamento DPI:

  • Control.LogicalToDeviceUnits , che converte un valore da pixel logici a pixel del dispositivo.

  • Control.ScaleBitmapLogicalToDevice , che ridimensiona un'immagine bitmap al DPI logico per un dispositivo.

  • Control.DeviceDpi , che restituisce il DPI per il dispositivo corrente.

Se i problemi persistono, puoi disattivare i miglioramenti DPI tramite le voci app.config .

Se non hai accesso al codice sorgente, puoi andare alle proprietà dell'applicazione in Esplora risorse, andare alla compatibilità e selezionare System (Enhanced)

inserisci qui la descrizione dell'immagine

che attiva il ridimensionamento GDI per migliorare anche la gestione DPI:

Per le applicazioni basate su GDI, Windows può ora ridimensionare DPI in base al monitor. Ciò significa che queste applicazioni diventeranno magicamente consapevoli del DPI per monitor.

Fai tutti questi passaggi e dovresti ottenere una migliore esperienza DPI per le applicazioni WinForms. Ma ricorda, devi scegliere come target la tua app per .net 4.7 e hai bisogno almeno di Windows 10 Build 15063 (Creators Update). Nel prossimo aggiornamento di Windows 10 1709, potremmo ottenere ulteriori miglioramenti.


12

Una guida che ho scritto al lavoro:

WPF funziona in "unità indipendenti dal dispositivo", il che significa che tutti i controlli si adattano perfettamente agli schermi ad alto dpi. In WinForms, ci vuole più cura.

WinForms funziona in pixel. Il testo verrà ridimensionato in base al dpi di sistema, ma spesso verrà ritagliato da un controllo non scalato. Per evitare tali problemi, è necessario evitare il dimensionamento e il posizionamento espliciti. Segui queste regole:

  1. Ovunque lo trovi (etichette, pulsanti, pannelli) imposta la proprietà AutoSize su True.
  2. Per il layout, utilizzare FlowLayoutPanel (come WPF StackPanel) e TableLayoutPanel (come WPF Grid) per il layout, anziché il pannello vanilla.
  3. Se stai sviluppando su una macchina ad alto dpi, il designer di Visual Studio può essere una frustrazione. Quando si imposta AutoSize = True, ridimensionerà il controllo sullo schermo. Se il controllo ha AutoSizeMode = GrowOnly, rimarrà di queste dimensioni per le persone con dpi normali, ad es. essere più grande del previsto. Per risolvere questo problema, apri il designer su un computer con dpi normali e fai clic con il tasto destro, ripristina.

3
per i dialoghi che possono essere ridimensionati AutoSize su tutto sarebbe un incubo, non voglio che i miei pulsanti diventino sempre più grandi mentre aumento le dimensioni dei dialoghi manualmente durante l'esecuzione del programma.
Josh,

10

Ho trovato molto difficile far funzionare bene WinForms con DPI alti. Quindi, ho scritto un metodo VB.NET per sovrascrivere il comportamento del modulo:

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

Di recente ho riscontrato questo problema, soprattutto in combinazione con il ridimensionamento di Visual Studio quando l'editor viene aperto su un sistema ad alto dpi. Ho trovato che la cosa migliore per mantenere AutoScaleMode = Font , ma per impostare le forme di carattere per il carattere predefinito, ma specificando le dimensioni in pixel , non è il punto, vale a dire: Font = MS Sans; 11px. Nel codice, ho quindi ripristinato il carattere predefinito: Font = SystemFonts.DefaultFonte tutto va bene.

Solo i miei due centesimi. Ho pensato di condividere, perché "mantenere AutoScaleMode = Font" e "Imposta la dimensione del carattere in pixel per il Designer" era qualcosa che non ho trovato su Internet.

Ho qualche dettaglio in più sul mio blog: http://www.sgrottel.de/?p=1581&lang=en


4

Oltre alle ancore che non funzionano molto bene: farei un passo avanti e direi che il posizionamento esatto (ovvero, usando la proprietà Location) non funziona molto bene con il ridimensionamento dei caratteri. Ho dovuto affrontare questo problema in due diversi progetti. In entrambi, abbiamo dovuto convertire il posizionamento di tutti i controlli WinForms in TableLayoutPanel e FlowLayoutPanel. L'uso della proprietà Dock (di solito impostata su Fill) all'interno di TableLayoutPanel funziona molto bene e si adatta perfettamente al carattere DPI di sistema.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.