106 votes

Comment faire pour que mon interface graphique se comporte bien lorsque l'échelle des polices de Windows est supérieure à 100 % ?

Lorsque l'on choisit des tailles de police importantes dans le panneau de configuration de Windows (comme 125 % ou 150 %), des problèmes surviennent dans une application VCL, chaque fois que quelque chose a été défini en pixels.

Prenez le TStatusBar.Panel . J'ai réglé sa largeur de manière à ce qu'il contienne exactement une étiquette, mais avec de grandes polices, l'étiquette "déborde". Même problème avec les autres composants.

Certains nouveaux ordinateurs portables de Dell sont déjà livrés avec 125% comme paramètre par défaut, donc si dans le passé ce problème était assez rare maintenant il est vraiment important.

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

62voto

David Heffernan Points 292687

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

Si vous définissez les dimensions dans le code, vous devez les mettre à l'échelle 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 le cadre de persistance des formulaires lorsque Scaled es True .

En fait, vous pouvez avancer un argument convaincant pour remplacer cette fonction par une version qui code en dur une valeur de 96 pour le dénominateur. Cela vous permet d'utiliser des valeurs de dimension absolues et de ne pas craindre que la signification change si vous modifiez l'échelle des polices sur votre machine de développement et réenregistrez le fichier .dfm. La raison pour laquelle cela est important est que le 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;

Si votre projet est développé sur plusieurs machines avec des valeurs de DPI différentes, vous constaterez que la mise à l'échelle utilisée par Delphi lors de l'enregistrement des fichiers .dfm fait que les contrôles se déplacent au cours d'une série d'éditions. Sur mon lieu de travail, pour éviter cela, nous avons une politique stricte selon laquelle les formulaires ne sont jamais édités qu'à 96 dpi (mise à l'échelle de 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 les machines XP, les formulaires de mon application utilisent la police Tahoma de 8 points. Sur Vista et les versions ultérieures, la police Segoe UI de 9 pt est utilisée. Cela offre un degré de liberté supplémentaire. La mise à l'échelle doit en tenir compte car les valeurs absolues des dimensions utilisées dans le code source sont supposées être relatives à la ligne de base de 8pt Tahoma à 96dpi.

Si vous utilisez des images ou des glyphes dans votre interface utilisateur, ils doivent également être mis à l'échelle. Les glyphes utilisés dans les barres d'outils et les menus en sont un exemple courant. Vous devez fournir ces glyphes sous forme de ressources d'icônes liées à votre exécutable. Chaque icône doit contenir une gamme de tailles, puis, au moment de l'exécution, vous choisissez la taille la plus appropriée et la chargez dans une liste d'images. Vous trouverez quelques détails à ce sujet ici : Comment charger des icônes à partir d'une ressource sans souffrir d'aliasing ?

Une autre astuce utile consiste à définir les dimensions en unités relatives, par rapport à TextWidth o TextHeight . Donc, si vous voulez que quelque chose ait une taille d'environ 10 lignes verticales, vous pouvez utiliser 10*Canvas.TextHeight('Ag') . Il s'agit d'une mesure très approximative, car elle ne tient pas compte de l'interligne, etc. Cependant, souvent, tout ce dont vous avez besoin, c'est d'être capable de faire en sorte que l'interface graphique soit correctement mise à l'échelle avec PixelsPerInch .

Vous devez également marquer votre demande comme étant conscient de la haute DPI . Le meilleur moyen d'y parvenir est d'utiliser le manifeste de l'application. Comme les outils de construction de Delphi ne vous permettent pas de personnaliser le manifeste que vous utilisez, vous êtes obligé de lier votre propre ressource 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>

La ressource script ressemble à ceci :

1 24 "Manifest.txt"

Manifest.txt contient le manifeste réel. Vous devrez également inclure la section comctl32 v6 et définir requestedExecutionLevel a asInvoker . Vous liez ensuite cette ressource compilée à votre application et vous vous assurez que Delphi n'essaie pas de faire de même avec son manifeste. Dans les versions modernes de Delphi, vous y parvenez en définissant l'option de projet Runtime Themes sur None.

Le manifeste est le droite pour déclarer que votre application est compatible avec les hautes DPI. Si vous voulez simplement l'essayer rapidement sans toucher à votre manifeste, appelez SetProcessDPIAware . C'est la toute première chose que vous faites lorsque votre application est lancée. De préférence dans l'une des premières sections d'initialisation de l'unité, ou comme première chose dans votre fichier .dpr.

Si vous ne déclarez pas que votre application est compatible avec les hautes DPI, Vista et les versions ultérieures la rendront en mode hérité pour toute mise à l'échelle de police supérieure à 125 %. C'est une situation épouvantable. Essayez d'éviter de tomber dans ce piège.

Mise à jour du DPI de Windows 8.1 par moniteur

Depuis Windows 8.1, le système d'exploitation prend en charge les paramètres DPI par moniteur ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ). Il s'agit d'un problème majeur pour les appareils modernes qui peuvent être équipés de différents écrans aux capacités très différentes. Vous pouvez avoir un écran d'ordinateur portable à très haute DPI, et un projecteur externe à faible DPI. La prise en charge d'un tel scénario nécessite encore plus de travail que celui décrit ci-dessus.

54voto

Warren P Points 23750

Note : Veuillez consulter les autres réponses car elles contiennent des techniques très utiles. Ma réponse ne fait que fournir des mises en garde et avertir qu'il ne faut pas croire que la sensibilisation à l'IAP est facile.

J'évite généralement la mise à l'échelle en fonction de l'IAP avec les éléments suivants TForm.Scaled = True . La sensibilisation au DPI n'est importante pour moi que lorsqu'elle devient importante pour les clients qui m'appellent et qui sont prêts à payer pour cela. La raison technique qui sous-tend ce point de vue est que, sensibilisation à la DPI ou non, vous ouvrez une fenêtre sur un monde de souffrance. De nombreux contrôles VCL standard et tiers ne fonctionnent pas bien en haute DPI. L'exception notable est que les parties de la VCL qui enveloppent les contrôles communs de Windows fonctionnent remarquablement bien en haute DPI. Un grand nombre de contrôles VCL tiers et intégrés à Delphi ne fonctionnent pas bien, voire pas du tout, en haute résolution. Si vous prévoyez d'activer TForm.Scaled, assurez-vous de tester à 96, 125 et 150 DPI chaque formulaire de votre projet et chaque contrôle tiers ou intégré que vous utilisez.

Delphi lui-même est écrit en Delphi. Le drapeau de sensibilisation à l'indice DPI élevé est activé pour la plupart des formulaires, bien que les auteurs de l'EDI aient eux-mêmes décidé de ne PAS activer ce drapeau manifeste de sensibilisation à l'indice DPI élevé, même récemment dans Delphi XE2. Notez que dans Delphi XE4 et les versions ultérieures, l'indicateur de sensibilisation à la technologie High DPI est activé et l'IDE se présente bien.

Je vous suggère de ne pas utiliser TForm.Scaled=true (qui est une valeur par défaut dans Delphi, donc à moins que vous ne l'ayez modifié, la plupart de vos formulaires ont Scaled=true) avec les drapeaux High DPI Aware (comme indiqué dans les réponses de David) avec les applications VCL qui sont construites à l'aide du concepteur de formulaires intégré de Delphi.

J'ai essayé dans le passé de faire un échantillon minimal du type de casse que vous pouvez vous attendre à voir lorsque TForm.Scaled est vrai, et lorsque la mise à l'échelle du formulaire Delphi a un pépin. Ces problèmes ne sont pas toujours et uniquement déclenchés par une valeur DPI autre que 96. Je n'ai pas été en mesure de déterminer une liste complète d'autres choses, qui inclut les changements de taille de police de Windows XP. Mais comme la plupart de ces problèmes n'apparaissent 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êmes.

Delphi XE ressemble à cela lorsque vous définissez l'échelle DPI sur "Fonts @ 200%" sous Windows 7, et Delphi XE2 présente le même problème sous Windows 7 et 8, mais ces problèmes semblent avoir été résolus à partir de Delphi XE4 :

enter image description here

enter image description here

Il s'agit principalement de contrôles VCL standard qui se comportent mal à des DPI élevés. Notez que la plupart des éléments n'ont pas été mis à l'échelle du tout, de sorte que les développeurs de l'EDI Delphi ont décidé d'ignorer la sensibilisation à la DPI et de désactiver la virtualisation de la DPI. Un choix intéressant.

Désactivez la virtualisation du DPI uniquement si vous voulez cette nouvelle source supplémentaire de douleur et de choix difficiles. Je vous suggère de ne pas y toucher. Notez que les contrôles communs de Windows semblent fonctionner correctement. Notez que le contrôle Delphi data-explorer est une enveloppe C# WinForms autour d'un contrôle commun standard Windows Tree. C'est un problème purement Microsoft, et pour le résoudre, Embarcadero devra soit réécrire un contrôle d'arbre purement natif .Net pour son explorateur de données, soit écrire un code de vérification de l'IPA et de modification des propriétés pour changer la hauteur des éléments dans le contrôle. Même Microsoft WinForms n'est pas en mesure de gérer les DPI élevés de manière propre, automatique et sans code de détournement personnalisé.

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

Mise à jour 2 : En réponse à la question de savoir comment je supporterais un DPI de 100%/125%, je proposerais un plan en deux phases. La phase 1 consiste à inventorier mon code pour trouver les contrôles personnalisés qui doivent être corrigés pour une DPI élevée, puis à établir un plan pour les corriger ou les supprimer progressivement. La phase 2 consisterait à prendre certaines zones de mon code qui sont conçues comme des formulaires sans gestion de la mise en page et à les transformer en formulaires qui utilisent une certaine forme de gestion de la mise en page afin que les changements de DPI ou de hauteur de police puissent fonctionner sans coupure. Je soupçonne 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, le dernier Delphi 10.1 Berlin fonctionne bien sur mon poste de travail à 150 ppp.

41voto

Ian Boyd Points 50743

Il est également important de noter que le respect du DPI de l'utilisateur ne représente qu'une partie de votre véritable travail :

respecter la taille de la police de l'utilisateur

Depuis des décennies, Windows a résolu ce problème avec la notion de mise en page performante utilisant Unités de dialogue plutôt que des pixels. A "unité de dialogue" est définie de manière à ce que la police caractère moyen est

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

enter image description here

Delphi est livré avec une notion (boguée) de Scaled où un formulaire tente de s'ajuster automatiquement en fonction de l'âge de l'utilisateur.

  • paramètres DPI de Windows de l'utilisateur, versets
  • le paramètre DPI sur la machine du développeur qui a enregistré le formulaire en dernier lieu

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 personnage moyen est 6.21px x 13.00px à 96dpi)
  • utilisateur fonctionnant avec Tahoma 8pt (où le personnage moyen est 5.94px x 13.00px à 96dpi)

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

o

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

En tant que bon développeur, vous allez respecter les préférences de votre utilisateur en matière de polices. Cela signifie que vous devez également mettre à l'échelle tous les contrôles de votre formulaire pour qu'ils correspondent à la nouvelle taille de la police :

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

Scaled ne s'occupera pas de ça pour vous.

C'est pire quand :

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

Maintenant, vous devez tout mettre à l'échelle

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

Scaled ne s'occupera pas de ça pour vous.


Si vous êtes intelligent, vous pouvez voir comment honorer le DPI est sans importance :

  • formulaire conçu avec Segoe UI 9pt @ 96dpi (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 son taille de la police . Deux utilisateurs exécutant

  • Segoe UI 14pt @ 96dpi (10,52px x 25px)
  • Segoe UI 9pt @ 150dpi (10,52px x 25px)

utilisent la même police . Le DPI est juste un Les préférences de l'utilisateur sont l'autre élément qui influe sur la taille de la police.

StandardizeFormFont

Clovis a remarqué que je faisais référence à une fonction StandardizeFormFont qui fixe la police sur un formulaire, et la met à l'échelle de la nouvelle taille de la police. Il ne s'agit pas d'une fonction standard, mais d'un ensemble de fonctions qui accomplissent une tâche simple que Borland n'a jamais géré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 dispose de 6 polices différentes ; il n'existe pas de "paramètre de police" unique dans Windows.
Mais nous savons d'expérience que nos formulaires doivent suivre les Police du titre de l'icône réglage

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 allons mettre le formulaire à l'échelle. à nous obtenons la hauteur de la police actuelle du formulaire ( en pixels ), et augmentez l'échelle de ce facteur.

Par exemple, si je règle le formulaire sur -16 et le formulaire est actuellement à -11 alors nous devons mettre à l'échelle le formulaire entier par :

-16 / -11 = 1.45454%

La normalisation se déroule en deux phases. Tout d'abord, le formulaire est mis à l'échelle en fonction du rapport entre les anciennes et les nouvelles tailles de police. Ensuite, il faut modifier 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 d'un formulaire. Il contourne les bogues dans le propre système de Borland. Form.ScaleBy méthode. Elle doit d'abord désactiver toutes les ancres du formulaire, puis effectuer la mise à l'échelle, et enfin 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 ensuite, nous devons récursivement et réellement utiliser 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 sont 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;

Il ne reste plus qu'à changer la police de contrôle :

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 le sais. Ce qui est triste, c'est qu'il n'y a aucun développeur Delphi sur terre, à part moi, qui rend réellement ses applications correctes.

Cher développeur Delphi : Réglez votre police Windows sur Segoe UI 14pt et corrigez votre application défectueuse

Note : Tout code est libéré dans le domaine public. Aucune attribution n'est requise.

11voto

avra Points 2794

Voici mon cadeau. Une fonction qui peut vous aider avec le positionnement horizontal des éléments dans vos mises en page GUI. 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;

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X