web-dev-qa-db-fra.com

Comment faire en sorte que l'interface graphique se comporte bien lorsque la mise à l'échelle des polices Windows est supérieure à 100%

Lorsque vous choisissez des tailles de police importantes dans le panneau de configuration Windows (125 ou 150%, par exemple), une application VCL pose des problèmes chaque fois que vous définissez une valeur pixel par pixel.

Prendre la TStatusBar.Panel. J'ai réglé sa largeur de sorte qu'elle contienne exactement une étiquette. Désormais, avec les grosses polices, l'étiquette "déborde". Même problème avec d'autres composants.

Certains nouveaux ordinateurs portables de Dell sont déjà livrés avec un paramètre par défaut de 125%. Par conséquent, ce problème était plutôt rare dans le passé, mais il est vraiment important.

Que peut-on faire pour surmonter ce problème?

105
LaBracca

Remarque: veuillez consulter les autres réponses car elles contiennent des techniques très utiles. Ma réponse ne contient que des mises en garde et des mises en garde contre l'hypothèse que la prise de conscience DPI est facile.

J'évite généralement la mise à l'échelle DPI avec TForm.Scaled = True. La sensibilisation à la DPI n’est importante pour moi que si elle le devient pour les clients qui me téléphonent et qui sont prêts à payer pour cela. La raison technique derrière ce point de vue est que, qu’il soit conscient de l’information ou non, vous ouvrez une fenêtre sur un monde de blessures. De nombreux contrôles VCL standard et tiers ne fonctionnent pas correctement avec une résolution élevée. L'exception notable, à savoir que les composants VCL qui englobent les contrôles communs Windows, fonctionne remarquablement bien à une résolution élevée. Un grand nombre de contrôles personnalisés Delphi VCL intégrés et tiers ne fonctionnent pas bien, ou pas du tout, à un DPI élevé. Si vous envisagez d'activer TForm.Scaled, assurez-vous de tester à 96, 125 et 150 DPI pour chaque formulaire de votre projet, ainsi que pour chaque contrôle tiers et intégré que vous utilisez.

Delphi lui-même est écrit en Delphi. Le drapeau de sensibilisation élevée à DPI est activé pour la plupart des formulaires, bien que, même récemment, dans Delphi XE2, les auteurs de IDE eux-mêmes aient décidé de NE PAS activer ce drapeau. Notez que dans Delphi XE4 et versions ultérieures, l'indicateur de reconnaissance HIGH DPI est activé et le IDE a bonne apparence.

Je suggère que vous n'utilisiez pas TForm.Scaled = true (ce qui est un défaut dans Delphi. Par conséquent, sauf si vous l'avez modifié, la plupart de vos formulaires ont Scaled = true) avec les indicateurs High DPI Aware (comme indiqué dans les réponses de David) avec Applications VCL construites à l'aide du concepteur de formulaires intégré de Delphi.

Dans le passé, j'ai essayé de créer un échantillon minimal du type de casse que vous pouvez vous attendre à voir quand TForm.Scaled est vrai, et lorsque la mise à l'échelle d'un formulaire Delphi présente un problème. Ces problèmes ne sont pas toujours et uniquement déclenchés par une valeur DPI autre que 96. Je n'ai pas pu déterminer une liste complète d'autres éléments, notamment les modifications de la taille de la police Windows XP. Mais comme la plupart de ces problèmes ne figurent que dans mes propres applications, dans des situations assez complexes, j'ai décidé de vous montrer des preuves que vous pouvez vérifier vous-même.

Delphi XE ressemble à ceci lorsque vous définissez la résolution DPI sur "Fonts @ 200%" dans Windows 7 et que Delphi XE2 est également défectueux sous Windows 7 et 8, mais ces problèmes semblent être résolus à partir de Delphi XE4:

enter image description here

enter image description here

Ce sont principalement des contrôles VCL standard qui se comportent mal à une DPI élevée. Notez que la plupart des éléments n'ont pas du tout été redimensionnés. Par conséquent, les développeurs de Delphi IDE ont décidé d'ignorer la prise en compte de la DPI, ainsi que de désactiver la virtualisation DPI. Un tel choix intéressant.

Désactivez la virtualisation DPI uniquement si vous souhaitez cette nouvelle source supplémentaire de problèmes et de choix difficiles. Je vous suggère de le laisser seul. Notez que les contrôles communs Windows semblent généralement fonctionner correctement. Notez que le contrôle Delphi Data-Explorer est un wrapper C # WinForms autour d'un contrôle commun de l'arborescence Windows. C’est un pur problème Microsoft. Pour le corriger, Embarcadero devra peut-être réécrire un contrôle d’arborescence .Net pur pour leur Explorateur de données ou écrire un code DPI-check-and-modify-properties pour modifier la hauteur des éléments dans le contrôle. Même Microsoft WinForms ne peut pas gérer les DPI élevés de manière propre, automatique et sans code kludge personnalisé.

Mise à jour: Fait facto intéressant: Bien que delphi IDE ne semble pas être "virtualisé", il n'utilise pas le contenu du manifeste affiché par David pour réaliser une "virtualisation non DPI". Peut-être utilise-t-il une fonction API au moment de l'exécution.

Mise à jour 2: En réponse à la manière dont je prendrais en charge un DPI à 100%/125%, je proposerais un plan en deux phases. La phase 1 consiste à inventorier mon code pour les contrôles personnalisés à corriger pour une DPI élevée, puis à définir un plan pour les corriger ou les éliminer progressivement. La phase 2 consisterait à prendre certaines zones de mon code conçues comme des formulaires sans gestion de la mise en forme et à les remplacer par des formulaires utilisant une sorte de gestion de la mise en forme afin que les modifications de DPI ou de hauteur de police puissent fonctionner sans découpage. Je suppose que ce travail de mise en page "inter-contrôle" serait beaucoup plus complexe dans la plupart des applications que le travail "intra-contrôle".

Mise à jour: En 2016, la dernière version de Delphi 10.1 Berlin fonctionne bien sur mon poste de travail à 150 dpi.

53
Warren P

Vos paramètres dans le fichier .dfm seront mis à l'échelle correctement, tant que Scaled est True.

Si vous définissez des dimensions dans le code, vous devez les redimensionner par Screen.PixelsPerInch, Divisé par Form.PixelsPerInch. Utilisez MulDiv pour le faire.

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

C'est ce que fait la structure de persistance de formulaire lorsque Scaled est True.

En fait, vous pouvez argumenter de manière pertinente pour remplacer cette fonction par une version codant en dur une valeur de 96 pour le dénominateur. Cela vous permet d'utiliser des valeurs de dimension absolues sans vous soucier du changement de signification si vous modifiez la mise à l'échelle des polices sur votre machine de développement et réenregistrez le fichier .dfm. La raison qui importe est que la propriété PixelsPerInch stockée dans le fichier .dfm est la valeur de la machine sur laquelle le fichier .dfm a été enregistré pour la dernière fois.

const
  SmallFontsPixelsPerInch = 96;

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

Donc, pour continuer sur le thème, il faut également se méfier du fait que si votre projet est développé sur plusieurs machines avec des valeurs DPI différentes, vous constaterez que la mise à l'échelle utilisée par Delphi lors de l'enregistrement de fichiers .dfm entraîne des contrôles errants au fil d'une série de modifications. . Sur mon lieu de travail, pour éviter cela, nous appliquons une règle stricte selon laquelle les formulaires ne sont édités qu’à 96 ppp (mise à l’échelle à 100%).

En fait, ma version de ScaleFromSmallFontsDimension tient également compte de la possibilité que la police du formulaire diffère au moment de l'exécution de celle définie au moment de la conception. Sur XP les formulaires de mon application utilisent 8pt Tahoma. Sous Vista et à partir de 9pt. Segoe UI est utilisé. Ceci fournit un autre degré de liberté. La mise à l'échelle doit en tenir compte, car les valeurs de dimension absolue utilisées dans le code source est supposé être relatif à la ligne de base de 8 pt Tahoma à 96 dpi.

Si vous utilisez des images ou des glyphes dans votre interface utilisateur, ceux-ci doivent également être redimensionnés. Un exemple courant serait les glyphes utilisés dans les barres d’outils et les menus. Vous voudrez fournir ces glyphes sous forme de ressources d'icônes liées à votre exécutable. Chaque icône doit contenir une gamme de tailles. Au moment de l'exécution, vous devez choisir la taille la plus appropriée et la charger dans une liste d'images. Quelques détails sur ce sujet peuvent être trouvés ici: Comment charger des icônes à partir d’une ressource sans aliasing?

Une autre astuce utile consiste à définir des dimensions en unités relatives, relatives à TextWidth ou TextHeight. Donc, si vous voulez que quelque chose soit autour de 10 lignes verticales, vous pouvez utiliser 10*Canvas.TextHeight('Ag'). C'est une métrique très approximative car elle ne permet pas l'interligne, etc. Cependant, tout ce que vous avez à faire est souvent capable de vous assurer que l'interface graphique évolue correctement avec PixelsPerInch.

Vous devez également marquer votre application comme étant prise en charge élevée . La meilleure façon de procéder consiste à utiliser le manifeste d'application. Comme les outils de construction de Delphi ne vous permettent pas de personnaliser le manifeste que vous utilisez, cela vous oblige à lier votre propre ressource de manifeste.

<?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>

Le script de ressource ressemble à ceci:

1 24 "Manifest.txt"

Manifest.txt contient le manifeste réel. Vous devez également inclure la section comctl32 v6 et définir requestedExecutionLevel sur asInvoker. Vous liez ensuite cette ressource compilée à votre application et assurez-vous que Delphi n'essaie pas de faire la même chose avec son manifeste. Dans Delphi moderne, vous y parvenez en définissant l'option de projet Thèmes d'exécution à Aucune.

Le manifeste est le moyen right de déclarer votre application très sensible à la DPI. Si vous souhaitez simplement l'essayer rapidement sans modifier votre manifeste, appelez SetProcessDPIAware . Faites-le dès la première action de votre application. De préférence dans l'une des premières sections d'initialisation de l'unité ou en tant que première chose dans votre fichier .dpr.

Si vous ne déclarez pas que votre application prend en charge une résolution DPI élevée, Vista et versions ultérieures la rendent dans un mode hérité pour toute police dont l'échelle est supérieure à 125%. Cela semble assez terrible. Essayez d'éviter de tomber dans ce piège.

Windows 8.1 par mise à jour DPI du moniteur

Depuis Windows 8.1, le système d'exploitation prend désormais en charge les paramètres DPI par moniteur ( http://msdn.Microsoft.com/en-ca/magazine/dn574798.aspx ). Ceci est un gros problème pour les appareils modernes qui peuvent avoir différents écrans associés avec des capacités très différentes. Vous avez peut-être un écran d'ordinateur portable à très haute résolution et un projecteur externe à faible résolution. Soutenir un tel scénario prend encore plus de travail que décrit ci-dessus.

61
David Heffernan

Il est également important de noter que le respect de la DPI de l'utilisateur n'est qu'un sous-ensemble de votre travail réel:

respecter la taille de la police de l'utilisateur

Pendant des décennies, Windows a résolu ce problème en effectuant une mise en page à l'aide de Dialog Units, plutôt que de pixels. Une "unité de dialogue" est définie pour que le caractère moyen de la police soit

  • 4 unités de dialogue (dlus) de large, et
  • 8 unités de dialogue (clus) de haut

enter image description here

Delphi est livré avec une notion (buggy) de Scaled, où un formulaire tente de s’ajuster automatiquement en fonction de la

  • Paramètres DPI Windows de l'utilisateur, versets
  • le paramètre DPI sur la machine du développeur qui a sauvegardé le formulaire pour la dernière fois

Cela ne résout pas le problème lorsque l'utilisateur utilise une police différente de celle avec laquelle vous avez conçu le formulaire, par exemple:

  • le développeur a conçu le formulaire avec MS Sans Serif 8pt (où le caractère moyen est 6.21px x 13.00px, à 96 dpi).
  • utilisateur fonctionnant avec Tahoma 8pt (où le caractère moyen est 5.94px x 13.00px, à 96 dpi)

    Comme ce fut le cas pour toute personne développant une application pour Windows 2000 ou Windows XP.

ou

  • le développeur a conçu le formulaire avec ** Tahoma 8pt * (où le caractère moyen est 5.94px x 13.00px, à 96 dpi).
  • un utilisateur fonctionnant avec Segoe UI 9pt (où le caractère moyen est 6.67px x 15px, à 96 dpi)

En tant que bon développeur, vous allez respecter les préférences de police de votre utilisateur. Cela signifie que vous devez également adapter tous les contrôles de votre formulaire à la nouvelle taille de police:

  • tout agrandir horizontalement de 12,29% (6,67/5,94)
  • étirer tout verticalement de 15,38% (15/13)

Scaled ne gérera pas cela pour vous.

Il y a pire quand:

  • conçu votre formulaire à Segoe UI 9pt (Windows Vista, Windows 7, Windows 8 par défaut)
  • l'utilisateur est en cours d'exécution Segoe UI 14pt, (par exemple, ma préférence) qui correspond à 10.52px x 25px

Maintenant, il faut tout mettre à l'échelle

  • horizontalement de 57,72%
  • verticalement de 66,66%

Scaled ne gérera pas cela pour vous.


Si vous êtes malin, vous pouvez voir à quel point le respect de DPI est irrévocable:

  • formulaire conçu avec Segoe UI 9pt @ 96 dpi (6,67px x 15px)
  • utilisateur fonctionnant avec Segoe UI 9pt @ 150dpi (10.52px x 25px)

Vous ne devriez pas regarder le paramètre DPI de l'utilisateur, mais plutôt sa taille de police . Deux utilisateurs en cours d'exécution

  • Segoe UI 14pt @ 96 dpi (10.52px x 25px)
  • Segoe UI 9pt @ 150dpi (10.52px x 25px)

utilisent la même police . DPI est juste un chose qui affecte la taille de la police; les préférences de l'utilisateur sont l'autre.

StandardizeFormFont

Clovis a remarqué que je référence une fonction StandardizeFormFont qui corrige la police dans un formulaire et la redimensionne à la nouvelle taille de police. Ce n'est pas une fonction standard, mais tout un ensemble de fonctions qui accomplissent la tâche simple que Borland n'a jamais effectuée.

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 a 6 polices différentes; il n'y a pas de "paramètre de police" unique dans Windows.
Mais nous savons par expérience que nos formulaires doivent suivre le réglage Icon Title Font

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;

Une fois que nous connaissons la taille de la police, nous redimensionnons le formulaire to, nous obtenons la hauteur de police actuelle du formulaire (en pixels) et le redimensionnons en fonction de ce facteur.

Par exemple, si je mets le formulaire à -16, et que le formulaire est actuellement à -11, nous devons redimensionner le forme entière par:

-16 / -11 = 1.45454%

La normalisation se déroule en deux phases. Commencez par redimensionner la forme en fonction du nouveau: tailles de police anciennes. Ensuite, changez les contrôles (de manière récursive) pour utiliser la nouvelle police.

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;

Voici le travail de mise à l'échelle réelle d'un formulaire. Cela fonctionne autour des bugs de la propre méthode Form.ScaleBy De Borland. Il doit d'abord désactiver toutes les ancres du formulaire, puis effectuer la mise à l'échelle, puis réactiver les ancres:

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;

et puis nous devons effectivement récursivement tiliser la nouvelle police:

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;

Les ancres étant désactivées de manière récursive:

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;

Et les ancres étant réactivées de manière récursive:

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;

Avec le travail de changer une police de contrôles laissée à:

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;

C'est beaucoup plus de code que vous ne le pensiez; je sais. Ce qui est triste, c’est qu’il n’ya pas de développeur Delphi sur la Terre, à l’exception de moi, qui corrige leurs applications.

Cher développeur Delphi: Définissez votre police Windows sur Segoe UI 14pt, et corrigez votre application buggy

Note: Tout code est publié dans le domaine public. Aucune attribution requise.

41
Ian Boyd

Voici mon cadeau. Une fonction qui peut vous aider avec le positionnement horizontal des éléments dans vos présentations graphiques. Gratuit pour tous.

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;
11
avra