Como faço para que minha GUI se comporte bem quando o dimensionamento da fonte do Windows é superior a 100%

107

Ao escolher tamanhos de fonte grandes no painel de controle do Windows (como 125% ou 150%), então há problemas em um aplicativo VCL, toda vez que algo é definido pixelwise.

Pegue o TStatusBar.Panel. Eu configurei sua largura para que contenha exatamente um rótulo, agora com fontes grandes o rótulo "transborda". O mesmo problema com outros componentes.

Alguns novos laptops da Dell já vêm com 125% como configuração padrão, portanto, embora no passado esse problema fosse bastante raro, agora é realmente importante.

O que pode ser feito para superar esse problema?

LaBracca
fonte

Respostas:

56

Nota: consulte as outras respostas, pois contêm técnicas muito valiosas. Minha resposta aqui apenas fornece advertências e avisos contra assumir que o reconhecimento de DPI é fácil.

Eu geralmente evito escalonamento com reconhecimento de DPI com TForm.Scaled = True. A conscientização sobre DPI só é importante para mim quando se torna importante para os clientes que me ligam e estão dispostos a pagar por isso. A razão técnica por trás desse ponto de vista é que a consciência do DPI ou não, você está abrindo uma janela para um mundo de dor. Muitos controles VCL padrão e de terceiros não funcionam bem em Alta DPI. A notável exceção é que as partes VCL que envolvem os controles comuns do Windows funcionam muito bem em DPI alto. Um grande número de controles personalizados Delphi VCL de terceiros e internos não funcionam bem, ou de forma alguma, em alto DPI. Se você planeja ativar o TForm.Scaled, certifique-se de testar a 96, 125 e 150 DPI para cada formulário em seu projeto e cada terceiro e controle integrado que você usa.

O próprio Delphi é escrito em Delphi. Ele tem o sinalizador de conscientização de Alta DPI ativado, para a maioria dos formulários, embora até recentemente, como no Delphi XE2, os próprios autores do IDE tenham decidido NÃO ativar esse sinalizador de manifesto de Reconhecimento de Alto DPI. Observe que no Delphi XE4 e posterior, o sinalizador de reconhecimento de HIGH DPI é ativado e o IDE parece bom.

Eu sugiro que você não use TForm.Scaled = true (que é um padrão no Delphi então a menos que você o modifique, a maioria de seus formulários tem Scaled = true) com os sinalizadores de reconhecimento de alto DPI (como mostrado nas respostas de David) com Aplicativos VCL que são construídos usando o designer de formulário delphi integrado.

Eu tentei no passado fazer uma amostra mínima do tipo de quebra que você pode esperar ver quando TForm.Scaled for verdadeiro e quando o dimensionamento de formulário Delphi apresentar uma falha. Essas falhas nem sempre são acionadas apenas por um valor de DPI diferente de 96. Não consegui determinar uma lista completa de outras coisas, que inclui alterações de tamanho de fonte do Windows XP. Mas como a maioria dessas falhas aparecem apenas em meus próprios aplicativos, em situações bastante complexas, decidi mostrar algumas evidências que vocês mesmos podem verificar.

O Delphi XE se parece com isso quando você configura o DPI Scaling para "Fonts @ 200%" no Windows 7, e o Delphi XE2 apresenta problemas semelhantes no Windows 7 e 8, mas essas falhas parecem ter sido corrigidas no Delphi XE4:

insira a descrição da imagem aqui

insira a descrição da imagem aqui

Esses são principalmente controles VCL padrão que estão se comportando mal em DPI alto. Observe que a maioria das coisas não foi dimensionada, portanto, os desenvolvedores do Delphi IDE decidiram ignorar o reconhecimento de DPI, bem como desligar a virtualização de DPI. Uma escolha tão interessante.

Desative a virtualização de DPI apenas se desejar essa nova fonte adicional de dor e escolhas difíceis. Eu sugiro que você deixe para lá. Observe que os controles comuns do Windows geralmente parecem funcionar bem. Observe que o controle explorador de dados Delphi é um wrapper C # WinForms em torno de um controle comum de árvore do Windows padrão. Isso é uma falha pura do microsoft, e consertá-la pode exigir que o Embarcadero reescreva um controle de árvore .Net nativo puro para seu explorador de dados ou escreva algum código de verificação e modificação de propriedades de DPI para alterar a altura dos itens no controle. Nem mesmo os WinForms da Microsoft podem lidar com alto DPI de maneira limpa, automática e sem código kludge personalizado.

Atualização: Factóide interessante: Embora o IDE delphi pareça não ser "virtualizado", ele não está usando o conteúdo manifesto mostrado por David para obter "virtualização não-DPI". Talvez esteja usando alguma função da API em tempo de execução.

Atualização 2: em resposta a como eu daria suporte a 100% / 125% DPI, eu proporia um plano de duas fases. A fase 1 é inventariar meu código para controles personalizados que precisam ser corrigidos para alto DPI e, em seguida, fazer um plano para corrigi-los ou eliminá-los. A fase 2 seria pegar algumas áreas do meu código que são projetadas como formulários sem gerenciamento de layout e alterá-las para formulários que usam algum tipo de gerenciamento de layout para que as alterações de altura de fonte ou DPI possam funcionar sem cortes. Suspeito que esse trabalho de layout de "inter-controle" seria muito mais complexo na maioria dos aplicativos do que o trabalho de "intra-controle".

Atualização: Em 2016, o Delphi 10.1 Berlin mais recente está funcionando bem na minha estação de trabalho de 150 dpi.

Warren P
fonte
5
Essa função API seria SetProcessDPIAware.
David Heffernan
2
Excelente. Obrigado pelo novo factóide. Eu sugiro que você modifique sua resposta para sugerir isso como um caminho possível. Pode ser que os clientes até queiram configurar essa opção (desative-a se não funcionar para eles).
Warren P
A tela inicial do Delphi usa DPI Virtualization, provavelmente porque a chamada para SetDPIAware é depois que o formulário Splash já se tornou visível.
Warren P
6
RAD Studio é uma grande mistura de controles VCL padrão, controles personalizados, formulários .NET WinForms e FireMonkey. Não é surpreendente que existam problemas. E é por isso que RAD Studio não é um bom exemplo.
Torbins
1
Se você estiver certo, é o próprio VCL que está com a cabeça na areia. Até a Microsoft está com a cabeça na areia. A única estrutura que eu já usei que faz um trabalho remotamente aceitável é o COCOA no Mac.
Warren P
63

Suas configurações no arquivo .dfm serão aumentadas corretamente, desde que Scaledestejam True.

Se você estiver definindo dimensões em código, será necessário dimensioná-las Screen.PixelsPerInchdividindo por Form.PixelsPerInch. Use MulDivpara fazer isso.

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

Isso é o que a estrutura de persistência de formulário faz quando Scaledé True.

Na verdade, você pode apresentar um argumento convincente para substituir essa função por uma versão que codifica permanentemente um valor de 96 para o denominador. Isso permite que você use valores de dimensão absolutos e não se preocupe com a mudança de significado se acontecer de você alterar a escala da fonte em sua máquina de desenvolvimento e salvar novamente o arquivo .dfm. O motivo que importa é que a PixelsPerInchpropriedade armazenada no arquivo .dfm é o valor da máquina na qual o arquivo .dfm foi salvo pela última vez.

const
  SmallFontsPixelsPerInch = 96;

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

Portanto, continuando o tema, outra coisa a ser cautelosa é que se o seu projeto for desenvolvido em várias máquinas com diferentes valores de DPI, você descobrirá que a escala que o Delphi usa ao salvar arquivos .dfm resulta em controles que vagam por uma série de edições . No meu local de trabalho, para evitar isso, temos uma política rígida de que os formulários só sejam editados a 96dpi (escala 100%).

Na verdade, minha versão do ScaleFromSmallFontsDimensiontambém permite a possibilidade da fonte do formulário diferir em tempo de execução daquela definida em tempo de design. Em máquinas XP, os formulários do meu aplicativo usam Tahoma 8pt. No Vista e acima, é usada a IU Segoe de 9pt. Isso fornece mais um grau de liberdade. A escala deve levar em conta isso porque os valores de dimensão absoluta usados ​​no código-fonte são considerados relativos à linha de base de Tahoma de 8pt a 96dpi.

Se você usar imagens ou glifos em sua IU, eles também precisam ser redimensionados. Um exemplo comum seriam os glifos usados ​​em barras de ferramentas e menus. Você vai querer fornecer esses glifos como recursos de ícone vinculados ao seu executável. Cada ícone deve conter uma variedade de tamanhos e, em tempo de execução, você escolhe o tamanho mais apropriado e carrega-o em uma lista de imagens. Alguns detalhes sobre esse tópico podem ser encontrados aqui: Como carrego ícones de um recurso sem sofrer alias?

Outro truque útil é definir dimensões em unidades relativas, em relação a TextWidthou TextHeight. Então, se você quiser algo com cerca de 10 linhas verticais de tamanho, você pode usar 10*Canvas.TextHeight('Ag'). Esta é uma métrica muito aproximada e pronta porque não permite espaçamento entre linhas e assim por diante. No entanto, geralmente tudo o que você precisa fazer é organizar a escala correta da GUI PixelsPerInch.

Você também deve marcar seu aplicativo como tendo alto DPI . A melhor maneira de fazer isso é por meio do manifesto do aplicativo. Como as ferramentas de construção do Delphi não permitem que você personalize o manifesto que você usa, isso força você a vincular seu próprio recurso de manifesto.

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

O script de recurso tem a seguinte aparência:

1 24 "Manifest.txt"

onde Manifest.txtcontém o manifesto real. Você também precisaria incluir a seção comctl32 v6 e definir requestedExecutionLevelcomo asInvoker. Em seguida, vincule esse recurso compilado ao seu aplicativo e certifique-se de que o Delphi não tente fazer o mesmo com seu manifesto. No Delphi moderno, você consegue isso definindo a opção de projeto Runtime Themes como Nenhum.

O manifesto é a maneira certa de declarar que seu aplicativo reconhece alto DPI. Se você quiser apenas experimentar rapidamente, sem mexer no seu manifesto, ligue SetProcessDPIAware. Faça isso como a primeira coisa a fazer quando seu aplicativo é executado. De preferência, em uma das primeiras seções de inicialização da unidade, ou como a primeira coisa em seu arquivo .dpr.

Se você não declarar que seu aplicativo tem reconhecimento de DPI alto, o Vista e superior irão renderizá-lo em um modo legado para qualquer escala de fonte acima de 125%. Isso parece muito terrível. Tente evitar cair nessa armadilha.

Atualização de DPI do Windows 8.1 por monitor

A partir do Windows 8.1, agora há suporte do sistema operacional para configurações de DPI por monitor ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ). Este é um grande problema para dispositivos modernos, que podem ter monitores diferentes conectados com recursos muito diferentes. Você pode ter uma tela de laptop com DPI muito alto e um projetor externo com DPI baixo. Apoiar tal cenário exige ainda mais trabalho do que o descrito acima.

David Heffernan
fonte
2
Isso não é sempre verdade. Na verdade, definir Scaled = true e, em seguida, definir High DPI ciente também pode causar algumas interrupções estranhas na maioria das aplicações Delphi. Passei centenas de horas tentando fazer meus aplicativos funcionarem em alta DPI e descobri que é melhor ter a pixelização de aparência horrível do que controles cortados, movidos para fora da tela, barras de rolagem extras ou ausentes em vários controles, etc.
Warren P
@ WarrenP Acho que esses problemas são específicos do seu aplicativo. Minha experiência pessoal é que meu aplicativo Delphi exibe e dimensiona perfeitamente, mesmo com o dimensionamento de fonte de 200%.
David Heffernan
2
@WarrenP E daí? É perfeitamente possível usar Delphi para construir aplicativos que se comportam melhor do que o IDE Delphi.
David Heffernan
1
Tenho visto muitos diálogos com bordas fixas criados com Delphi 5,6,7 e a configuração em escala true to fail. Escondendo ok, cancelando botões etc. Até mesmo alguns diálogos no Delphi2006 ele acha que foi mordido por isso. Misturar componentes nativos do Delphi e componentes do Windows também produz efeitos estranhos. Eu sempre desenvolvo a GUI em uma escala de fonte de 125% e coloco a propriedade scaled em false.
LU RD
2
Coisas boas. 1 para informações fantásticas. Minha opinião (não faça isso) é a segunda em importância para a necessidade de saber COMO fazer quando quiser fazer isso ...
Warren P
42

Também é importante observar que honrar o DPI do usuário é apenas um subconjunto de seu trabalho real:

honrando o tamanho da fonte do usuário

Por décadas, o Windows resolveu esse problema com a noção de execução de layout usando Dialog Units , em vez de pixels. Uma "unidade de diálogo" é definida para que o caractere médio da fonte seja

  • 4 unidades de diálogo (dlus) de largura e
  • 8 unidades de diálogo (clus) de altura

insira a descrição da imagem aqui

Delphi vem com uma noção (buggy) de Scaled, onde um formulário tenta se ajustar automaticamente com base no

  • Configurações de DPI do Windows do usuário, versos
  • a configuração de DPI na máquina do desenvolvedor que salvou o formulário pela última vez

Isso não resolve o problema quando o usuário usa uma fonte diferente daquela com a qual você projetou o formulário, por exemplo:

  • o desenvolvedor projetou o formulário com MS Sans Serif 8pt (onde o caractere médio é 6.21px x 13.00px, em 96dpi)
  • usuário executando com Tahoma 8pt (onde o caractere médio é 5.94px x 13.00px, em 96dpi)

    Como era o caso de qualquer pessoa que desenvolvesse um aplicativo para Windows 2000 ou Windows XP.

ou

  • o desenvolvedor projetou o formulário com ** Tahoma 8pt * (onde o caractere médio é 5.94px x 13.00px, em 96dpi)
  • um usuário executando com Segoe UI 9pt (em que o caractere médio é 6.67px x 15px, em 96dpi)

Como um bom desenvolvedor, você respeitará as preferências de fonte do usuário. Isso significa que você também precisa dimensionar todos os controles em seu formulário para corresponder ao novo tamanho da fonte:

  • expandir tudo horizontalmente em 12,29% (6,67 / 5,94)
  • esticar tudo verticalmente em 15,38% (15/13)

Scaled não vai cuidar disso para você.

Piora quando:

  • projetou seu formulário no Segoe UI 9pt (o padrão do Windows Vista, Windows 7, Windows 8)
  • o usuário está executando o Segoe UI 14pt (por exemplo, minha preferência), que é10.52px x 25px

Agora você tem que dimensionar tudo

  • horizontalmente em 57,72%
  • verticalmente em 66,66%

Scaled não vai cuidar disso para você.


Se você for inteligente, verá como honrar o DPI é irrelevante:

  • formulário desenvolvido com Segoe UI 9pt @ 96dpi (6,67px x 15px)
  • usuário executando com Segoe UI 9pt @ 150dpi (10.52px x 25px)

Você não deve olhar para a configuração de DPI do usuário, deve olhar para o tamanho da fonte . Dois usuários executando

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

estão executando a mesma fonte . DPI é apenas uma coisa que afeta o tamanho da fonte; as preferências do usuário são as outras.

StandardizeFormFont

Clovis notou que faço referência a uma função StandardizeFormFontque corrige a fonte em um formulário e o dimensiona para o novo tamanho de fonte. Não é uma função padrão, mas todo um conjunto de funções que realizam a tarefa simples que a Borland nunca realizou.

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;

O Windows possui 6 fontes diferentes; não há uma única "configuração de fonte" no Windows.
Mas sabemos por experiência que nossos formulários devem seguir a configuração da fonte do título do ícone

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;

Uma vez que sabemos o tamanho da fonte que irá dimensionar a forma de , obtemos a altura da fonte atual do formulário ( em pixels ), e escalar por esse fator.

Por exemplo, se estou configurando o formulário para -16e o formulário está atualmente em -11, precisamos dimensionar todo o formulário:

-16 / -11 = 1.45454%

A padronização acontece em duas fases. Primeiro dimensione o formulário de acordo com a proporção dos tamanhos de fonte novos: antigos. Em seguida, altere os controles (recursivamente) para usar a nova fonte.

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;

Aqui está o trabalho de realmente dimensionar um formulário. Ele contorna bugs no próprio Form.ScaleBymétodo da Borland . Primeiro, ele deve desativar todas as âncoras no formulário, em seguida, realizar o dimensionamento e, em seguida, reativar as âncoras:

TAnchorsArray = array of TAnchors;

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

    RectBefore := AForm.BoundsRect;

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

    RectAfter := AForm.BoundsRect;

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

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

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

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

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

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

e então temos que usar recursivamente a nova fonte:

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;

Com as âncoras sendo desativadas recursivamente:

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


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

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

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

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

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

        Inc(StartingIndex);
    end;

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

E as âncoras sendo reativadas recursivamente:

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;

Com o trabalho de realmente alterar uma fonte de controle, deixou para:

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;

Isso é muito mais código do que você pensou que seria; eu sei. O triste é que não há desenvolvedor Delphi na terra, exceto eu, que realmente faz seus aplicativos corretos.

Caro desenvolvedor Delphi : Defina sua fonte do Windows para Segoe UI 14pt e corrija seu aplicativo com erros

Nota : Qualquer código é lançado em domínio público. Nenhuma atribuição necessária.

Ian Boyd
fonte
1
Obrigado pela resposta, mas o que você sugere para o mundo real? Implementar um redimensionamento de todos os controles manualmente?
LaBracca
3
"O triste é que não há desenvolvedor Delphi na terra, exceto eu, que realmente torna seus aplicativos corretos." Essa é uma afirmação muito arrogante que está incorreta. Da minha resposta: Na verdade, minha versão de ScaleFromSmallFontsDimension também permite a possibilidade de a fonte do formulário diferir em tempo de execução daquela definida em tempo de design. A escala deve levar em conta isso porque os valores de dimensão absoluta usados ​​no código-fonte são considerados relativos à linha de base de Tahoma de 8pt a 96dpi. Sua é uma boa resposta, lembre-se, +1.
David Heffernan
1
@Ian Não sou eu que disse isso. Parece Warren.
David Heffernan
2
Isso é incrível, Ian. Obrigado.
Warren P
2
Recentemente encontrei esta pergunta e resposta. Coletei todo o código de Ian em uma unidade de trabalho aqui: pastebin.com/dKpfnXLc e postei sobre ele no Google+ aqui: goo.gl/0ARdq9 Postando aqui caso alguém ache isso útil.
W.Prins,
11

Aqui está meu presente. Uma função que pode ajudá-lo com o posicionamento horizontal de elementos em seus layouts de GUI. Gratuito para todos.

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
fonte
2
Estou feliz que você goste Warren. Tinha cerca de 15 anos quando não havia soluções disponíveis para o problema que tinha de resolver. E ainda hoje pode haver uma situação em que pode ser aplicado. B-)
abril de