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

107

Lorsque vous choisissez de grandes tailles de police dans le panneau de configuration de Windows (comme 125% ou 150%), il y a des problèmes dans une application VCL, chaque fois que quelque chose a été défini par pixel.

Prendre la TStatusBar.Panel . J'ai réglé sa largeur pour qu'elle contienne exactement une étiquette, maintenant 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% de paramètres par défaut, alors que dans le passé, ce problème était assez rare maintenant, il est vraiment important.

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

LaBracca
la source

Réponses:

56

Remarque: veuillez consulter les autres réponses car elles contiennent des techniques très utiles. Ma réponse ici ne fournit que des mises en garde et des mises en garde contre le fait de supposer que la conscience DPI est facile.

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

Delphi lui-même est écrit en Delphi. Il a le drapeau de sensibilisation High DPI activé, pour la plupart des formulaires, bien que même aussi récemment que dans Delphi XE2, les auteurs de l'EDI eux-mêmes ont décidé de NE PAS activer ce drapeau de manifeste High DPI Awareness. Notez que dans Delphi XE4 et versions ultérieures, l'indicateur de prise de conscience HIGH DPI est activé et l'EDI a l'air bien.

Je 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 indicateurs High DPI Aware (comme indiqué dans les réponses de David) avec Applications VCL créées à l'aide du concepteur de formulaires Delphi intégré.

J'ai essayé dans le passé de faire un échantillon minimal du type de rupture que vous pouvez vous attendre à voir lorsque TForm.Scaled est vrai et lorsque la mise à l'échelle du formulaire Delphi a un problème. Ces problèmes ne sont pas toujours et uniquement déclenchés par une valeur PPP autre que 96. J'ai été incapable de déterminer une liste complète d'autres choses, y compris les changements de taille de police 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 pourrez vérifier vous-même.

Delphi XE ressemble à ceci lorsque vous définissez la mise à l'échelle DPI sur "Polices @ 200%" dans Windows 7, et Delphi XE2 est également cassé sous Windows 7 et 8, mais ces problèmes semblent être corrigés à partir de Delphi XE4:

entrez la description de l'image ici

entrez la description de l'image ici

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

Désactivez la virtualisation DPI uniquement si vous souhaitez cette nouvelle source supplémentaire de douleur et de choix difficiles. Je vous suggère de le laisser tranquille. Notez que les contrôles communs de Windows semblent généralement fonctionner correctement. Notez que le contrôle de l'explorateur de données Delphi est un wrapper C # WinForms autour d'un contrôle commun d'arborescence Windows standard. C'est un pur problème de microsoft, et sa résolution peut nécessiter qu'Embarcadero réécrive un contrôle d'arborescence .Net natif pur pour son explorateur de données, ou qu'il écrive du code DPI-check-and-modify-properties pour changer la hauteur des éléments dans le contrôle. Même Microsoft WinForms ne peut pas gérer un DPI élevé de manière propre, automatique et sans code kludge personnalisé.

Mise à jour: Fait intéressant: bien que l'EDI delphi ne semble pas être «virtualisé», il n'utilise pas le contenu manifeste montré par David pour réaliser une «virtualisation sans DPI». Peut-être utilise-t-il une fonction API lors de l'exécution.

Mise à jour 2: En réponse à la façon dont je prendrais en charge 100% / 125% DPI, je proposerais un plan en deux phases. La phase 1 consiste à inventorier mon code pour les contrôles personnalisés qui doivent être corrigés pour un DPI élevé, puis à élaborer un plan pour les corriger ou les éliminer 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 changer en formulaires qui utilisent une sorte de gestion de la mise en page afin que les changements de DPI ou de hauteur de police puissent fonctionner sans découpage. 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 dpi.

Warren P
la source
5
Cette fonction API serait SetProcessDPIAware.
David Heffernan
2
Excellent. Merci pour le nouveau factoid. Je vous suggère de modifier votre réponse pour suggérer cela comme une voie possible. Il se peut que les clients souhaitent même configurer cette option (désactivez-la si cela ne fonctionne pas pour eux).
Warren P
L'écran de démarrage de Delphi utilise la virtualisation DPI, probablement parce que l'appel à SetDPIAware se fait après que le formulaire Splash a déjà été rendu visible.
Warren P
6
RAD Studio est un grand mélange de contrôles VCL standard, de contrôles personnalisés, de formulaires .NET WinForms et FireMonkey. Il n'est pas surprenant qu'il y ait des problèmes. Et c'est pourquoi RAD Studio n'est pas un bon exemple.
Torbins
1
Si vous avez raison, c'est la VCL elle-même qui a la tête dans le sable. Même Microsoft a la tête dans le sable. Le seul cadre que j'ai jamais utilisé et qui fasse un travail à distance possible est COCOA sur Mac.
Warren P
63

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

Si vous définissez des dimensions dans le code, vous devez les mettre à l'échelle en les Screen.PixelsPerInchdivisant par Form.PixelsPerInch. Utilisez MulDivpour faire cela.

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

C'est ce que fait le framework de persistance de formulaire quand Scaledest True.

En fait, vous pouvez faire 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 vous soucier du changement de signification si vous modifiez la mise à l'échelle de la police sur votre machine de développement et enregistrez à nouveau le fichier .dfm. La raison qui compte est que la PixelsPerInchpropriété 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, en poursuivant le thème, une autre chose à laquelle il faut se méfier est 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 des fichiers .dfm entraîne des contrôles errant sur une série de modifications. . Sur mon lieu de travail, pour éviter cela, nous avons une politique stricte selon laquelle les formulaires ne sont édités qu'à 96 dpi (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 les machines XP, les formulaires de ma candidature utilisent Tahoma 8pt. Sur Vista et les versions ultérieures, l'interface utilisateur Segoe 9pt est utilisée. Cela offre encore 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 sont supposées être relatives à la ligne de base de Tahoma 8pt à 96 dpi.

Si vous utilisez des images ou des glyphes dans votre interface utilisateur, ceux-ci doivent également être mis à l'échelle. Un exemple courant serait les glyphes utilisés sur 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, puis au moment de l'exécution, vous choisissez la taille la plus appropriée et la chargez 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 souffrir d'alias?

Une autre astuce utile consiste à définir les dimensions en unités relatives, par rapport à TextWidthou TextHeight. Donc, si vous voulez que quelque chose soit d'environ 10 lignes verticales, vous pouvez utiliser10*Canvas.TextHeight('Ag') . Il s'agit d'une métrique très approximative car elle ne permet pas d'espacement des lignes, etc. Cependant, il suffit souvent de pouvoir faire en sorte que l'interface graphique évolue correctement avec PixelsPerInch.

Vous devez également marquer votre application comme étant à haute résolution . La meilleure façon de procéder consiste à utiliser le manifeste de l'application. Puisque 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 ressources ressemble à ceci:

1 24 "Manifest.txt"

Manifest.txtcontient le manifeste réel. Vous devrez également inclure la section comctl32 v6 et définir requestedExecutionLevelsurasInvoker . Vous liez ensuite cette ressource compilée à votre application et vous assurez que Delphi n'essaye pas de faire de même avec son manifeste. Dans Delphi moderne, vous y parvenez en définissant l'option de projet Runtime Themes sur Aucun.

Le manifeste est le bon moyen de déclarer que votre application est consciente du DPI élevé. Si vous voulez juste l'essayer rapidement sans déranger votre manifeste, appelezSetProcessDPIAware . Faites-le comme la toute première chose que vous faites lorsque votre application s'exécute. 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 le DPI élevé, Vista et les versions ultérieures la rendront dans un mode hérité pour toute mise à l'échelle de police supérieure à 125%. Cela semble assez affreux. Essayez d'éviter de tomber dans ce piège.

Mise à jour DPI de Windows 8.1 par moniteur

À partir de 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 ). C'est un gros problème pour les appareils modernes qui peuvent avoir différents écrans connectés avec des capacités très différentes. Vous pouvez avoir un écran d'ordinateur portable très élevé et un projecteur externe à faible DPI. Soutenir un tel scénario demande encore plus de travail que décrit ci-dessus.

David Heffernan
la source
2
Ce n'est pas toujours vrai. En fait, la définition de Scaled = true, puis la définition de la prise en compte du DPI élevé peuvent également provoquer une rupture étrange dans la plupart des applications Delphi. J'ai passé des centaines d'heures à essayer de faire fonctionner mes applications en haute résolution et j'ai constaté qu'il était préférable d'avoir la pixellisation affreuse que les contrôles rognés, déplacés hors de l'écran, barres de défilement supplémentaires ou manquantes sur divers contrôles, etc.
Warren P
@WarrenP Je pense que ces problèmes sont particuliers à votre application. Mon expérience personnelle est que mon application Delphi s'affiche et s'adapte parfaitement, même à une échelle de police de 200%.
David Heffernan
2
@WarrenP Et alors? Il est parfaitement possible d'utiliser Delphi pour créer des applications qui se comportent mieux que l'EDI Delphi.
David Heffernan
1
J'ai vu beaucoup de dialogues avec des bordures fixes créées avec Delphi 5,6,7 et le paramètre mis à l'échelle true pour échouer. Cacher ok, annuler les boutons, etc. Même certains dialogues dans Delphi2006, il pense avoir été mordu par cela. Le mélange de composants Delphi natifs et de composants Windows donne également des effets étranges. Je développe toujours l'interface graphique avec une mise à l'échelle de la police de 125% et je mets la propriété scaled sur false.
LU RD
2
Super truc. +1 pour des informations fantastiques. Mon opinion (ne le faites pas) est la deuxième en importance après la nécessité de savoir COMMENT le faire quand vous voulez faire cela ...
Warren P
42

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

respecter la taille de la police de l'utilisateur

Pendant des décennies, Windows a résolu ce problème avec la notion de mise en page en utilisant des unités de dialogue , plutôt que des 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

entrez la description de l'image ici

Delphi est livré avec une notion (boguée) de Scaled, où un formulaire essaie de s'ajuster automatiquement en fonction du

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

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 est5.94px x 13.00px , à 96 dpi)

    Comme 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 mettre à l'échelle tous les contrôles de votre formulaire pour qu'ils correspondent à la nouvelle taille de police:

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

Scaled ne gérera pas cela pour vous.

Cela empire quand:

  • conçu votre formulaire sur Segoe UI 9pt (Windows Vista, Windows 7, Windows 8 par défaut)
  • l'utilisateur exécute Segoe UI 14pt , (par exemple, ma préférence) qui est10.52px x 25px

Maintenant, vous devez tout mettre à l'échelle

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

Scaled ne gérera pas cela pour vous.


Si vous êtes intelligent, vous pouvez voir à quel point honorer DPI n'est pas pertinent:

  • 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, vous devriez regarder sa taille de police . Deux utilisateurs en cours d'exécution

  • Interface utilisateur Segoe 14pt à 96 dpi (10,52 px x 25 px)
  • Interface utilisateur Segoe 9pt à 150 dpi (10,52 px x 25 px)

exécutent la même police . Le DPI n'est qu'une chose qui affecte la taille de la police; les préférences de l'utilisateur sont les autres.

StandardizeFormFont

Clovis a remarqué que je fais référence à une fonction StandardizeFormFontqui corrige la police sur un formulaire et l'adapte à la nouvelle taille de police. Ce n'est pas une fonction standard, mais un ensemble complet de fonctions qui accomplissent la tâche simple que Borland n'a jamais traité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 "réglage de police" unique dans Windows.
Mais nous savons par expérience que nos formulaires doivent suivre le paramètre Police du titre de l' icône

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

Par exemple, si je configure le formulaire sur -16et que le formulaire est actuellement à -11, nous devons mettre à l'échelle le formulaire entier en:

-16 / -11 = 1.45454%

La normalisation se déroule en deux phases. Mettez d'abord le formulaire à l'échelle en fonction du rapport entre la nouvelle et l'ancienne taille de police. Modifiez ensuite les contrôles (récursivement) 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. Cela fonctionne autour des bogues dans la propre Form.ScaleByméthode 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 nous devons récursive 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 é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 réellement une police de contrôle à gauche pour:

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 connais. Le plus triste, c'est qu'il n'y a pas de développeur Delphi sur terre, à part moi, qui corrige réellement leurs applications.

Cher développeur Delphi , définissez votre police Windows sur Segoe UI 14pt et corrigez votre application boguée

Remarque : tout code est publié dans le domaine public. Aucune attribution requise.

Ian Boyd
la source
1
Merci pour la réponse, mais que suggérez-vous pour le monde réel? Implémenter manuellement un redimensionnement de tous les contrôles?
LaBracca
3
"Le plus triste, c'est qu'il n'y a pas de développeur Delphi sur terre, à part moi, qui corrige réellement leurs applications." C'est une déclaration très arrogante qui est incorrecte. D'après ma réponse: 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. La mise à l'échelle doit en tenir compte car les valeurs de dimension absolue utilisées dans le code source sont supposées être relatives à la ligne de base de Tahoma 8pt à 96 dpi. Votre réponse est une bonne réponse à vous, +1.
David Heffernan
1
@Ian Pas moi qui ai dit ça. Sonne comme Warren.
David Heffernan
2
C'est assez génial, Ian. Merci.
Warren P le
2
Récemment rencontré cette question et réponse. J'ai rassemblé tout le code d'Ian dans une unité de travail ici: pastebin.com/dKpfnXLc et posté sur Google+ ici: goo.gl/0ARdq9 Publier ici au cas où quelqu'un trouverait cela utile.
W.Prins
11

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;
avra
la source
2
Je suis content que tu aimes ça Warren. Il y a environ 15 ans quand il n'y avait aucune solution disponible pour le problème que je devais résoudre. Et même aujourd'hui, il peut y avoir une situation où cela peut être appliqué. B-)
avra