Come faccio a far funzionare bene la mia GUI quando il ridimensionamento dei caratteri di Windows è maggiore del 100%


107

Quando si scelgono caratteri di grandi dimensioni nel pannello di controllo di Windows (come 125% o 150%), ci sono problemi in un'applicazione VCL, ogni volta che qualcosa è stato impostato pixelwise.

Prendi il TStatusBar.Panel. Ho impostato la sua larghezza in modo che contenga esattamente un'etichetta, ora con caratteri grandi l'etichetta "overflow". Stesso problema con altri componenti.

Alcuni nuovi laptop Dell vengono forniti già con il 125% come impostazione predefinita, quindi mentre in passato questo problema era piuttosto raro ora è davvero importante.

Cosa si può fare per superare questo problema?

Risposte:


56

Nota: vedere le altre risposte poiché contengono tecniche molto preziose. La mia risposta qui fornisce solo avvertenze e avvertenze contro il presupposto che la consapevolezza dei DPI sia facile.

In genere evito il ridimensionamento sensibile ai DPI con TForm.Scaled = True. La consapevolezza dei DPI è importante per me solo quando diventa importante per i clienti che mi chiamano e sono disposti a pagare per questo. La ragione tecnica alla base di questo punto di vista è che la consapevolezza DPI o meno, stai aprendo una finestra su un mondo di dolore. Molti controlli VCL standard e di terze parti non funzionano bene con DPI elevato. La notevole eccezione è che le parti VCL che racchiudono i controlli comuni di Windows funzionano molto bene con DPI elevati. Un numero enorme di controlli personalizzati Delphi VCL di terze parti e incorporati non funzionano bene, o per niente, a DPI elevati. Se prevedi di attivare TForm.Scaled assicurati di testare a 96, 125 e 150 DPI per ogni singolo modulo nel tuo progetto e ogni singola terza parte e controllo integrato che utilizzi.

Delphi stesso è scritto in Delphi. Ha il flag di consapevolezza High DPI attivato, per la maggior parte dei moduli, sebbene anche di recente come in Delphi XE2, gli stessi autori dell'IDE abbiano deciso di NON attivare quel flag manifest di High DPI Awareness. Si noti che in Delphi XE4 e versioni successive, il flag di consapevolezza HIGH DPI è attivato e l'IDE ha un bell'aspetto.

Suggerisco di non utilizzare TForm.Scaled = true (che è un'impostazione predefinita in Delphi quindi, a meno che tu non lo abbia modificato, la maggior parte dei tuoi moduli ha Scaled = true) con i flag High DPI Aware (come mostrato nelle risposte di David) con Applicazioni VCL create utilizzando il designer di moduli delphi integrato.

In passato ho provato a fare un campione minimo del tipo di rottura che puoi aspettarti di vedere quando TForm.Scaled è vero e quando il ridimensionamento del modulo Delphi ha un problema tecnico. Questi difetti non sono sempre e solo innescati da un valore DPI diverso da 96. Non sono stato in grado di determinare un elenco completo di altre cose, che include le modifiche alle dimensioni dei caratteri di Windows XP. Ma poiché la maggior parte di questi glitch compaiono solo nelle mie applicazioni, in situazioni abbastanza complesse, ho deciso di mostrarvi alcune prove che potete verificare voi stessi.

Delphi XE ha questo aspetto quando si imposta il ridimensionamento DPI su "Fonts @ 200%" in Windows 7 e Delphi XE2 è ugualmente danneggiato su Windows 7 e 8, ma questi problemi sembrano essere stati risolti a partire da Delphi XE4:

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Si tratta principalmente di controlli VCL standard che si comportano in modo anomalo a DPI elevati. Si noti che la maggior parte delle cose non è stata ridimensionata affatto, quindi gli sviluppatori IDE di Delphi hanno deciso di ignorare la consapevolezza DPI, oltre a disattivare la virtualizzazione DPI. Una scelta così interessante.

Disattiva la virtualizzazione DPI solo se desideri questa nuova fonte aggiuntiva di dolore e scelte difficili. Ti suggerisco di lasciarlo stare. Nota che i controlli comuni di Windows per lo più sembrano funzionare bene. Si noti che il controllo di esplorazione dati di Delphi è un wrapper C # WinForms attorno a un controllo comune standard dell'albero di Windows. Questo è un puro problema tecnico di Microsoft e risolverlo potrebbe richiedere a Embarcadero di riscrivere un puro controllo albero nativo .Net per il proprio esploratore di dati o di scrivere un codice DPI-check-and-modify-properties per modificare le altezze degli elementi nel controllo. Nemmeno microsoft WinForms è in grado di gestire DPI elevati in modo pulito, automatico e senza codice kludge personalizzato.

Aggiornamento: Fatto interessante: sebbene l'IDE delphi non sia "virtualizzato", non utilizza il contenuto manifest mostrato da David per ottenere una "virtualizzazione non DPI". Forse sta usando qualche funzione API in fase di esecuzione.

Aggiornamento 2: in risposta a come avrei supportato DPI 100% / 125%, avrei escogitato un piano in due fasi. La fase 1 consiste nell'inventare il codice per i controlli personalizzati che devono essere corretti per DPI elevati e quindi fare un piano per risolverli o eliminarli gradualmente. La fase 2 consisterebbe nel prendere alcune aree del mio codice che sono progettate come moduli senza gestione del layout e cambiarle in moduli che utilizzano un qualche tipo di gestione del layout in modo che le modifiche DPI o dell'altezza dei caratteri possano funzionare senza ritagli. Ho il sospetto che questo lavoro di layout "inter-controllo" sarebbe molto più complesso nella maggior parte delle applicazioni rispetto al lavoro "intra-controllo".

Aggiornamento: nel 2016, l'ultimo Delphi 10.1 Berlin funziona bene sulla mia workstation a 150 dpi.


5
Quella funzione API sarebbe SetProcessDPIAware.
David Heffernan

2
Eccellente. Grazie per il nuovo factoid. Ti suggerisco di modificare la tua risposta per suggerire quello come un percorso possibile. Potrebbe anche essere che i clienti desiderino configurare tale opzione (disattivarla se non funziona per loro).
Warren P

La schermata iniziale di Delphi utilizza DPI Virtualization, probabilmente perché la chiamata a SetDPIAware avviene dopo che il modulo Splash è già stato reso visibile.
Warren P

6
RAD Studio è un grande mix di controlli VCL standard, controlli personalizzati, .NET WinForms e moduli FireMonkey. Non sorprende che ci siano problemi. Ed è per questo che RAD Studio non è un buon esempio.
Torbins

1
Se hai ragione, è la stessa VCL che ha la testa nella sabbia. Anche Microsoft ha la testa nella sabbia. L'unico framework che io abbia mai usato che fa un lavoro passabile da remoto in questo è COCOA su Mac.
Warren P

63

Le tue impostazioni nel file .dfm verranno ridimensionate correttamente, purché lo Scaledsia True.

Se stai impostando le dimensioni nel codice, devi ridimensionarle Screen.PixelsPerInchdiviso per Form.PixelsPerInch. Usa MulDivper fare questo.

function TMyForm.ScaleDimension(const X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, PixelsPerInch);
end;

Questo è ciò che fa il framework di persistenza del modulo quando lo Scaledè True.

In effetti, puoi creare un argomento convincente per sostituire questa funzione con una versione che codifica in modo rigido un valore di 96 per il denominatore. Ciò ti consente di utilizzare valori di dimensione assoluti e non preoccuparti del cambiamento di significato se ti capita di modificare la scala dei caratteri sulla tua macchina di sviluppo e di salvare nuovamente il file .dfm. La ragione che conta è che la PixelsPerInchproprietà memorizzata nel file .dfm è il valore della macchina su cui il file .dfm è stato salvato l'ultima volta.

const
  SmallFontsPixelsPerInch = 96;

function ScaleFromSmallFontsDimension(const X: Integer): Integer;
begin
  Result := MulDiv(X, Screen.PixelsPerInch, SmallFontsPixelsPerInch);
end;

Quindi, continuando il tema, un'altra cosa da cui diffidare è che se il tuo progetto è sviluppato su più macchine con valori DPI diversi, scoprirai che il ridimensionamento che Delphi usa quando salva i file .dfm si traduce in controlli che vagano su una serie di modifiche . Al mio posto di lavoro, per evitare ciò, abbiamo una politica rigorosa secondo la quale i moduli vengono modificati solo a 96 dpi (ridimensionamento del 100%).

Infatti la mia versione di ScaleFromSmallFontsDimensiontiene conto anche della possibilità che il font del modulo differisca in fase di esecuzione da quello impostato in fase di progettazione. Su macchine XP i moduli della mia domanda utilizzano 8pt Tahoma. Su Vista e versioni successive viene utilizzata l'interfaccia utente Segoe a 9 punti. Ciò fornisce un ulteriore grado di libertà. Il ridimensionamento deve tenerne conto perché si presume che i valori di dimensione assoluti utilizzati nel codice sorgente siano relativi alla linea di base di 8pt Tahoma a 96 dpi.

Se utilizzi immagini o glifi nella tua interfaccia utente, anche questi devono essere ridimensionati. Un esempio comune potrebbero essere i glifi utilizzati nelle barre degli strumenti e nei menu. Ti consigliamo di fornire questi glifi come risorse di icone collegate al tuo eseguibile. Ogni icona dovrebbe contenere un intervallo di dimensioni, quindi in fase di esecuzione scegli la dimensione più appropriata e caricala in un elenco di immagini. Alcuni dettagli su questo argomento possono essere trovati qui: Come faccio a caricare le icone da una risorsa senza soffrire di aliasing?

Un altro trucco utile è definire le dimensioni in unità relative, relative a TextWidtho TextHeight. Quindi, se vuoi che qualcosa abbia una dimensione di circa 10 linee verticali, puoi usare 10*Canvas.TextHeight('Ag'). Questa è una metrica molto approssimativa e pronta perché non consente l'interlinea e così via. Tuttavia, spesso tutto ciò che devi fare è essere in grado di fare in modo che la GUI venga ridimensionata correttamente con PixelsPerInch.

Dovresti anche contrassegnare la tua applicazione come consapevole di DPI elevati . Il modo migliore per farlo è tramite il manifest dell'applicazione. Poiché gli strumenti di compilazione di Delphi non ti consentono di personalizzare il manifest, usi questo ti obbliga a collegare la tua risorsa manifest.

<?xml version='1.0' encoding='UTF-8' standalone='yes'?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <asmv3:application xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
    <asmv3:windowsSettings
         xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">
      <dpiAware>true</dpiAware>
    </asmv3:windowsSettings>
  </asmv3:application>
</assembly>

Lo script della risorsa ha questo aspetto:

1 24 "Manifest.txt"

dove Manifest.txtcontiene il manifesto effettivo. Si sarebbe anche necessario includere la sezione comctl32 v6 e insieme requestedExecutionLevela asInvoker. Quindi colleghi questa risorsa compilata alla tua app e assicurati che Delphi non tenti di fare lo stesso con il suo manifest. Nella moderna Delphi si ottiene ciò impostando l'opzione del progetto Temi di runtime su Nessuno.

Il manifest è il modo giusto per dichiarare che la tua app riconosce un DPI elevato. Se vuoi solo provarlo rapidamente senza interferire con il tuo manifest, chiama SetProcessDPIAware. Fallo come la prima cosa che fai quando la tua app viene eseguita. Preferibilmente in una delle prime sezioni di inizializzazione dell'unità o come prima cosa nel file .dpr.

Se non dichiari che la tua app è a conoscenza di DPI elevati, Vista e versioni successive la renderanno in una modalità legacy per qualsiasi ridimensionamento dei caratteri superiore al 125%. Questo sembra piuttosto terribile. Cerca di evitare di cadere in quella trappola.

Aggiornamento DPI di Windows 8.1 per monitor

A partire da Windows 8.1, ora è disponibile il supporto del sistema operativo per le impostazioni DPI per monitor ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ). Questo è un grosso problema per i dispositivi moderni che potrebbero avere diversi display collegati con funzionalità molto diverse. Potresti avere uno schermo del laptop con DPI molto alto e un proiettore esterno con DPI basso. Supportare uno scenario del genere richiede ancora più lavoro di quanto descritto sopra.


2
Questo non è sempre vero. In effetti, impostare Scaled = true e quindi impostare il grado di attenzione DPI alto può anche causare strane interruzioni nella maggior parte delle applicazioni delphi. Ho passato centinaia di ore cercando di far funzionare le mie app con DPI elevati e ho scoperto che è meglio avere la pixelazione dall'aspetto orribile rispetto ai controlli ritagliati, spostati fuori dallo schermo, barre di scorrimento extra o mancanti su vari controlli, ecc.
Warren P

@ WarrenP Penso che questi problemi siano specifici della tua app. La mia esperienza personale è che la mia app Delphi viene visualizzata e ridimensionata perfettamente anche con un ridimensionamento dei caratteri del 200%.
David Heffernan

2
@ WarrenP Allora cosa? È perfettamente possibile utilizzare Delphi per creare app che si comportano meglio dell'IDE Delphi.
David Heffernan

1
Ho visto molte finestre di dialogo con bordi fissi create con Delphi 5,6,7 e l'impostazione ridimensionata true to fail. Nascondere ok, pulsanti di annullamento ecc. Anche alcune finestre di dialogo in Delphi2006 si pensa siano state morse da questo. Anche la combinazione di componenti Delphi nativi e componenti di Windows produce strani effetti. Sviluppo sempre la GUI in un ridimensionamento dei caratteri del 125% e metto la proprietà scaled su false.
LU RD

2
Roba fantastica. +1 per informazioni fantastiche. La mia opinione (non farlo) è seconda per importanza alla necessità di sapere COME farlo quando vuoi farlo ...
Warren P

42

È anche importante notare che onorare il DPI dell'utente è solo un sottoinsieme del tuo vero lavoro:

onorare la dimensione del carattere dell'utente

Per decenni, Windows ha risolto questo problema con l'idea di eseguire il layout utilizzando le unità di dialogo , anziché i pixel. Una "unità di dialogo" è definita in modo che il carattere medio del font sia

  • 4 unità di dialogo (dlus) di larghezza e
  • 8 unità di dialogo (clus) di altezza

inserisci qui la descrizione dell'immagine

Delphi viene fornito con una nozione (buggy) di Scaled, in cui un modulo cerca di adattarsi automaticamente in base a

  • Impostazioni DPI di Windows dell'utente, versi
  • l'impostazione DPI sulla macchina dello sviluppatore che ha salvato per ultimo il modulo

Ciò non risolve il problema quando l'utente utilizza un carattere diverso da quello con cui hai progettato il modulo, ad esempio:

  • lo sviluppatore ha progettato il modulo con MS Sans Serif 8pt (dove il carattere medio è 6.21px x 13.00px, a 96 dpi)
  • utente che esegue con Tahoma 8pt (dove il carattere medio è 5.94px x 13.00px, a 96 dpi)

    Come nel caso di chiunque sviluppasse un'applicazione per Windows 2000 o Windows XP.

o

  • lo sviluppatore ha progettato il modulo con ** Tahoma 8pt * (dove il carattere medio è 5.94px x 13.00px, a 96 dpi)
  • un utente che esegue con Segoe UI 9pt (dove il carattere medio è 6.67px x 15px, a 96 dpi)

Come buon sviluppatore, onorerai le preferenze dei caratteri dei tuoi utenti. Ciò significa che devi anche ridimensionare tutti i controlli sul modulo in modo che corrispondano alla nuova dimensione del carattere:

  • espande tutto in orizzontale del 12,29% (6,67 / 5,94)
  • allunga tutto verticalmente del 15,38% (15/13)

Scaled non gestirà questo per te.

Peggio quando:

  • ha progettato il modulo su Segoe UI 9pt (Windows Vista, Windows 7, Windows 8 predefinito)
  • l'utente esegue Segoe UI 14pt , (ad esempio la mia preferenza) che è10.52px x 25px

Ora devi ridimensionare tutto

  • orizzontalmente del 57,72%
  • verticalmente del 66,66%

Scaled non gestirà questo per te.


Se sei intelligente, puoi vedere quanto sia irrelavante onorare DPI:

  • modulo progettato con Segoe UI 9pt @ 96dpi (6.67px x 15px)
  • utente che esegue con Segoe UI 9pt a 150 dpi (10,52 px x 25 px)

Non dovresti guardare l'impostazione DPI dell'utente, dovresti guardare la dimensione del carattere . Due utenti in esecuzione

  • Interfaccia utente Segoe 14 pt a 96 dpi (10,52 px x 25 px)
  • Interfaccia utente Segoe 9 pt a 150 dpi (10,52 px x 25 px)

eseguono lo stesso tipo di carattere . DPI è solo una cosa che influisce sulla dimensione del carattere; le preferenze dell'utente sono le altre.

StandardizeFormFont

Clovis ha notato che faccio riferimento a una funzione StandardizeFormFontche corregge il carattere su un modulo e lo ridimensiona alla nuova dimensione del carattere. Non è una funzione standard, ma un intero set di funzioni che svolgono il compito semplice che Borland non ha mai gestito.

function StandardizeFormFont(AForm: TForm): Real;
var
    preferredFontName: string;
    preferredFontHeight: Integer;
begin
    GetUserFontPreference({out}preferredFontName, {out}preferredFontHeight);

    //e.g. "Segoe UI",     
    Result := Toolkit.StandardizeFormFont(AForm, PreferredFontName, PreferredFontHeight);
end;

Windows ha 6 diversi tipi di carattere; non esiste una singola "impostazione dei caratteri" in Windows.
Ma sappiamo per esperienza che i nostri moduli dovrebbero seguire l' impostazione Carattere titolo icona

procedure GetUserFontPreference(out FaceName: string; out PixelHeight: Integer);
var
   font: TFont;
begin
   font := Toolkit.GetIconTitleFont;
   try
      FaceName := font.Name; //e.g. "Segoe UI"

      //Dogfood testing: use a larger font than we're used to; to force us to actually test it    
      if IsDebuggerPresent then
         font.Size := font.Size+1;

      PixelHeight := font.Height; //e.g. -16
   finally
      font.Free;
   end;
end;

Una volta che sappiamo che la dimensione del carattere ci sarà scalare il modulo a , otteniamo altezza carattere corrente del form ( in pixel ), e la scala da tale fattore.

Ad esempio, se sto impostando il modulo su -16e il modulo è attualmente in -11, allora dobbiamo ridimensionare l'intero modulo:

-16 / -11 = 1.45454%

La standardizzazione avviene in due fasi. Per prima cosa ridimensiona il modulo in base al rapporto tra le dimensioni del carattere nuovo e vecchio. Quindi modificare effettivamente i controlli (in modo ricorsivo) per utilizzare il nuovo carattere.

function StandardizeFormFont(AForm: TForm; FontName: string; FontHeight: Integer): Real;
var
    oldHeight: Integer;
begin
    Assert(Assigned(AForm));

    if (AForm.Scaled) then
    begin
        OutputDebugString(PChar('WARNING: StandardizeFormFont: Form "'+GetControlName(AForm)+'" is set to Scaled. Proper form scaling requires VCL scaling to be disabled, unless you implement scaling by overriding the protected ChangeScale() method of the form.'));
    end;

    if (AForm.AutoScroll) then
    begin
        if AForm.WindowState = wsNormal then
        begin
            OutputDebugString(PChar('WARNING: StandardizeFormFont: Form "'+GetControlName(AForm)+'" is set to AutoScroll. Form designed size will be suseptable to changes in Windows form caption height (e.g. 2000 vs XP).'));
                    if IsDebuggerPresent then
                        Windows.DebugBreak; //Some forms would like it (to fix maximizing problem)
        end;
    end;

    if (not AForm.ShowHint) then
    begin
        AForm.ShowHint := True;
        OutputDebugString(PChar('INFORMATION: StandardizeFormFont: Turning on form "'+GetControlName(AForm)+'" hints. (ShowHint := True)'));
                    if IsDebuggerPresent then
                        Windows.DebugBreak; //Some forms would like it (to fix maximizing problem)
    end;

    oldHeight := AForm.Font.Height;

    //Scale the form to the new font size
//  if (FontHeight <> oldHeight) then    For compatibility, it's safer to trigger a call to ChangeScale, since a lot of people will be assuming it always is called
    begin
        ScaleForm(AForm, FontHeight, oldHeight);
    end;

    //Now change all controls to actually use the new font
    Toolkit.StandardizeFont_ControlCore(AForm, g_ForceClearType, FontName, FontHeight,
            AForm.Font.Name, AForm.Font.Size);

    //Return the scaling ratio, so any hard-coded values can be multiplied
    Result := FontHeight / oldHeight;
end;

Ecco il compito di ridimensionare effettivamente un modulo. Funziona attorno ai bug nel Form.ScaleBymetodo di Borland . Per prima cosa deve disabilitare tutti gli ancoraggi sul modulo, quindi eseguire il ridimensionamento, quindi riattivare gli ancoraggi:

TAnchorsArray = array of TAnchors;

procedure ScaleForm(const AForm: TForm; const M, D: Integer);
var
    aAnchorStorage: TAnchorsArray;
    RectBefore, RectAfter: TRect;
    x, y: Integer;
    monitorInfo: TMonitorInfo;
    workArea: TRect;
begin
    if (M = 0) and (D = 0) then
        Exit;

    RectBefore := AForm.BoundsRect;

    SetLength(aAnchorStorage, 0);
    aAnchorStorage := DisableAnchors(AForm);
    try
        AForm.ScaleBy(M, D);
    finally
        EnableAnchors(AForm, aAnchorStorage);
    end;

    RectAfter := AForm.BoundsRect;

    case AForm.Position of
    poScreenCenter, poDesktopCenter, poMainFormCenter, poOwnerFormCenter,
    poDesigned: //i think i really want everything else to also follow the nudging rules...why did i exclude poDesigned
        begin
            //This was only nudging by one quarter the difference, rather than one half the difference
//          x := RectAfter.Left - ((RectAfter.Right-RectBefore.Right) div 2);
//          y := RectAfter.Top - ((RectAfter.Bottom-RectBefore.Bottom) div 2);
            x := RectAfter.Left - ((RectAfter.Right-RectAfter.Left) - (RectBefore.Right-RectBefore.Left)) div 2;
            y := RectAfter.Top - ((RectAfter.Bottom-RectAfter.Top)-(RectBefore.Bottom-RectBefore.Top)) div 2;
        end;
    else
        //poDesigned, poDefault, poDefaultPosOnly, poDefaultSizeOnly:
        x := RectAfter.Left;
        y := RectAfter.Top;
    end;

    if AForm.Monitor <> nil then
    begin
        monitorInfo.cbSize := SizeOf(monitorInfo);
        if GetMonitorInfo(AForm.Monitor.Handle, @monitorInfo) then
            workArea := monitorInfo.rcWork
        else
        begin
            OutputDebugString(PChar(SysErrorMessage(GetLastError)));
            workArea := Rect(AForm.Monitor.Left, AForm.Monitor.Top, AForm.Monitor.Left+AForm.Monitor.Width, AForm.Monitor.Top+AForm.Monitor.Height);
        end;

//      If the form is off the right or bottom of the screen then we need to pull it back
        if RectAfter.Right > workArea.Right then
            x := workArea.Right - (RectAfter.Right-RectAfter.Left); //rightEdge - widthOfForm

        if RectAfter.Bottom > workArea.Bottom then
            y := workArea.Bottom - (RectAfter.Bottom-RectAfter.Top); //bottomEdge - heightOfForm

        x := Max(x, workArea.Left); //don't go beyond left edge
        y := Max(y, workArea.Top); //don't go above top edge
    end
    else
    begin
        x := Max(x, 0); //don't go beyond left edge
        y := Max(y, 0); //don't go above top edge
    end;

    AForm.SetBounds(x, y,
            RectAfter.Right-RectAfter.Left, //Width
            RectAfter.Bottom-RectAfter.Top); //Height
end;

e allora dobbiamo ricorsivamente in realtà usare il nuovo font:

procedure StandardizeFont_ControlCore(AControl: TControl; ForceClearType: Boolean;
        FontName: string; FontSize: Integer;
        ForceFontIfName: string; ForceFontIfSize: Integer);
const
    CLEARTYPE_QUALITY = 5;
var
    i: Integer;
    RunComponent: TComponent;
    AControlFont: TFont;
begin
    if not Assigned(AControl) then
        Exit;

    if (AControl is TStatusBar) then
    begin
        TStatusBar(AControl).UseSystemFont := False; //force...
        TStatusBar(AControl).UseSystemFont := True;  //...it
    end
    else
    begin
        AControlFont := Toolkit.GetControlFont(AControl);

        if not Assigned(AControlFont) then
            Exit;

        StandardizeFont_ControlFontCore(AControlFont, ForceClearType,
                FontName, FontSize,
                ForceFontIfName, ForceFontIfSize);
    end;

{   If a panel has a toolbar on it, the toolbar won't paint properly. So this idea won't work.
    if (not Toolkit.IsRemoteSession) and (AControl is TWinControl) and (not (AControl is TToolBar)) then
        TWinControl(AControl).DoubleBuffered := True;
}

    //Iterate children
    for i := 0 to AControl.ComponentCount-1 do
    begin
        RunComponent := AControl.Components[i];
        if RunComponent is TControl then
            StandardizeFont_ControlCore(
                    TControl(RunComponent), ForceClearType,
                    FontName, FontSize,
                    ForceFontIfName, ForceFontIfSize);
    end;
end;

Con le ancore disabilitate in modo ricorsivo:

function DisableAnchors(ParentControl: TWinControl): TAnchorsArray;
var
    StartingIndex: Integer;
begin
    StartingIndex := 0;
    DisableAnchors_Core(ParentControl, Result, StartingIndex);
end;


procedure DisableAnchors_Core(ParentControl: TWinControl; var aAnchorStorage: TAnchorsArray; var StartingIndex: Integer);
var
    iCounter: integer;
    ChildControl: TControl;
begin
    if (StartingIndex+ParentControl.ControlCount+1) > (Length(aAnchorStorage)) then
        SetLength(aAnchorStorage, StartingIndex+ParentControl.ControlCount+1);

    for iCounter := 0 to ParentControl.ControlCount - 1 do
    begin
        ChildControl := ParentControl.Controls[iCounter];
        aAnchorStorage[StartingIndex] := ChildControl.Anchors;

        //doesn't work for set of stacked top-aligned panels
//      if ([akRight, akBottom ] * ChildControl.Anchors) <> [] then
//          ChildControl.Anchors := [akLeft, akTop];

        if (ChildControl.Anchors) <> [akTop, akLeft] then
            ChildControl.Anchors := [akLeft, akTop];

//      if ([akTop, akBottom] * ChildControl.Anchors) = [akTop, akBottom] then
//          ChildControl.Anchors := ChildControl.Anchors - [akBottom];

        Inc(StartingIndex);
    end;

    //Add children
    for iCounter := 0 to ParentControl.ControlCount - 1 do
    begin
        ChildControl := ParentControl.Controls[iCounter];
        if ChildControl is TWinControl then
            DisableAnchors_Core(TWinControl(ChildControl), aAnchorStorage, StartingIndex);
    end;
end;

E le ancore vengono riattivate ricorsivamente:

procedure EnableAnchors(ParentControl: TWinControl; aAnchorStorage: TAnchorsArray);
var
    StartingIndex: Integer;
begin
    StartingIndex := 0;
    EnableAnchors_Core(ParentControl, aAnchorStorage, StartingIndex);
end;


procedure EnableAnchors_Core(ParentControl: TWinControl; aAnchorStorage: TAnchorsArray; var StartingIndex: Integer);
var
    iCounter: integer;
    ChildControl: TControl;
begin
    for iCounter := 0 to ParentControl.ControlCount - 1 do
    begin
        ChildControl := ParentControl.Controls[iCounter];
        ChildControl.Anchors := aAnchorStorage[StartingIndex];

        Inc(StartingIndex);
    end;

    //Restore children
    for iCounter := 0 to ParentControl.ControlCount - 1 do
    begin
        ChildControl := ParentControl.Controls[iCounter];
        if ChildControl is TWinControl then
            EnableAnchors_Core(TWinControl(ChildControl), aAnchorStorage, StartingIndex);
    end;
end;

Con il lavoro di cambiare effettivamente un carattere dei controlli lasciato a:

procedure StandardizeFont_ControlFontCore(AControlFont: TFont; ForceClearType: Boolean;
        FontName: string; FontSize: Integer;
        ForceFontIfName: string; ForceFontIfSize: Integer);
const
    CLEARTYPE_QUALITY = 5;
var
    CanChangeName: Boolean;
    CanChangeSize: Boolean;
    lf: TLogFont;
begin
    if not Assigned(AControlFont) then
        Exit;

{$IFDEF ForceClearType}
    ForceClearType := True;
{$ELSE}
    if g_ForceClearType then
        ForceClearType := True;
{$ENDIF}

    //Standardize the font if it's currently
    //  "MS Shell Dlg 2" (meaning whoever it was opted into the 'change me' system
    //  "MS Sans Serif" (the Delphi default)
    //  "Tahoma" (when they wanted to match the OS, but "MS Shell Dlg 2" should have been used)
    //  "MS Shell Dlg" (the 9x name)
    CanChangeName :=
            (FontName <> '')
            and
            (AControlFont.Name <> FontName)
            and
            (
                (
                    (ForceFontIfName <> '')
                    and
                    (AControlFont.Name = ForceFontIfName)
                )
                or
                (
                    (ForceFontIfName = '')
                    and
                    (
                        (AControlFont.Name = 'MS Sans Serif') or
                        (AControlFont.Name = 'Tahoma') or
                        (AControlFont.Name = 'MS Shell Dlg 2') or
                        (AControlFont.Name = 'MS Shell Dlg')
                    )
                )
            );

    CanChangeSize :=
            (
                //there is a font size
                (FontSize <> 0)
                and
                (
                    //the font is at it's default size, or we're specifying what it's default size is
                    (AControlFont.Size = 8)
                    or
                    ((ForceFontIfSize <> 0) and (AControlFont.Size = ForceFontIfSize))
                )
                and
                //the font size (or height) is not equal
                (
                    //negative for height (px)
                    ((FontSize < 0) and (AControlFont.Height <> FontSize))
                    or
                    //positive for size (pt)
                    ((FontSize > 0) and (AControlFont.Size <> FontSize))
                )
                and
                //no point in using default font's size if they're not using the face
                (
                    (AControlFont.Name = FontName)
                    or
                    CanChangeName
                )
            );

    if CanChangeName or CanChangeSize or ForceClearType then
    begin
        if GetObject(AControlFont.Handle, SizeOf(TLogFont), @lf) <> 0 then
        begin
            //Change the font attributes and put it back
            if CanChangeName then
                StrPLCopy(Addr(lf.lfFaceName[0]), FontName, LF_FACESIZE);
            if CanChangeSize then
                lf.lfHeight := FontSize;

            if ForceClearType then
                lf.lfQuality := CLEARTYPE_QUALITY;
            AControlFont.Handle := CreateFontIndirect(lf);
        end
        else
        begin
            if CanChangeName then
                AControlFont.Name := FontName;
            if CanChangeSize then
            begin
                if FontSize > 0 then
                    AControlFont.Size := FontSize
                else if FontSize < 0 then
                    AControlFont.Height := FontSize;
            end;
        end;
    end;
end;

È molto più codice di quanto pensavi che sarebbe stato; lo so. La cosa triste è che non c'è nessuno sviluppatore Delphi al mondo, tranne me, che effettivamente rende corrette le proprie applicazioni.

Caro sviluppatore Delphi : imposta il tuo carattere Windows su Segoe UI 14pt e correggi la tua applicazione difettosa

Nota : qualsiasi codice viene rilasciato nel pubblico dominio. Nessuna attribuzione richiesta.


1
Grazie per la risposta, ma cosa suggerisci per il mondo reale? Implementare manualmente un ridimensionamento di tutti i controlli?
LaBracca

3
"La cosa triste è che non c'è nessuno sviluppatore Delphi al mondo, tranne me, che effettivamente rende corrette le proprie applicazioni". Questa è un'affermazione molto arrogante che non è corretta. Dalla mia risposta: infatti la mia versione di ScaleFromSmallFontsDimension tiene conto anche della possibilità che il font del modulo differisca in fase di esecuzione da quello impostato in fase di progettazione. Il ridimensionamento deve tenerne conto perché si presume che i valori di dimensione assoluti utilizzati nel codice sorgente siano relativi alla linea di base di 8pt Tahoma a 96 dpi. La tua è una buona risposta, intendiamoci, +1.
David Heffernan,

1
@ Ian Non io che l'ho detto. Sembra Warren.
David Heffernan

2
È davvero fantastico, Ian. Grazie.
Warren P

2
Di recente mi sono imbattuto in questa domanda e risposta. Ho raccolto tutto il codice di Ian in un'unità di lavoro qui: pastebin.com/dKpfnXLc e l'ho pubblicato su Google+ qui: goo.gl/0ARdq9 Inserendo qui nel caso qualcuno lo trovasse utile.
W.Prins

11

Ecco il mio regalo. Una funzione che può aiutarti con il posizionamento orizzontale degli elementi nei layout della tua GUI. Gratuito per tutti.

function CenterInParent(Place,NumberOfPlaces,ObjectWidth,ParentWidth,CropPercent: Integer): Integer;
  {returns formated centered position of an object relative to parent.
  Place          - P order number of an object beeing centered
  NumberOfPlaces - NOP total number of places available for object beeing centered
  ObjectWidth    - OW width of an object beeing centered
  ParentWidth    - PW width of an parent
  CropPercent    - CP percentage of safe margin on both sides which we want to omit from calculation
  +-----------------------------------------------------+
  |                                                     |
  |        +--------+       +---+      +--------+       |
  |        |        |       |   |      |        |       |
  |        +--------+       +---+      +--------+       |
  |     |              |             |            |     |
  +-----------------------------------------------------+
  |     |<---------------------A----------------->|     |
  |<-C->|<------B----->|<-----B----->|<-----B---->|<-C->|
  |                    |<-D>|
  |<----------E------------>|

  A = PW-C   B = A/NOP  C=(CP*PW)/100  D = (B-OW)/2
  E = C+(P-1)*B+D }

var
  A, B, C, D: Integer;
begin
  C := Trunc((CropPercent*ParentWidth)/100);
  A := ParentWidth - C;
  B := Trunc(A/NumberOfPlaces);
  D := Trunc((B-ObjectWidth)/2);
  Result := C+(Place-1)*B+D;
end;

2
Sono contento che ti piaccia Warren. Sono circa 15 anni quando non c'erano soluzioni disponibili per il problema che dovevo risolvere. E anche oggi ci può essere una situazione in cui può essere applicato. B-)
avra
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.