¿Cómo hago para que mi GUI se comporte bien cuando la escala de fuentes de Windows es superior al 100%?

107

Al elegir tamaños de fuente grandes en el panel de control de Windows (como 125% o 150%), hay problemas en una aplicación VCL, cada vez que se ha configurado algo en píxeles.

Toma el TStatusBar.Panel. He configurado su ancho para que contenga exactamente una etiqueta, ahora con fuentes grandes la etiqueta "desborda". Mismo problema con otros componentes.

Algunas computadoras portátiles nuevas de Dell ya se envían con un 125% como configuración predeterminada, por lo que, aunque en el pasado este problema era bastante raro, ahora es realmente importante.

¿Qué se puede hacer para superar este problema?

LaBracca
fuente

Respuestas:

56

Nota: consulte las otras respuestas, ya que contienen técnicas muy valiosas. Mi respuesta aquí solo proporciona advertencias y advertencias contra la suposición de que el conocimiento de DPI es fácil.

Generalmente evito el escalado con reconocimiento de DPI con TForm.Scaled = True. El conocimiento de DPI solo es importante para mí cuando se vuelve importante para los clientes que me llaman y están dispuestos a pagar por ello. La razón técnica detrás de ese punto de vista es que, si eres consciente de los DPI o no, estás abriendo una ventana a un mundo de dolor. Muchos controles VCL estándar y de terceros no funcionan bien en High DPI. La notable excepción de que las partes de VCL que envuelven los controles comunes de Windows funcionan notablemente bien con un DPI alto. Una gran cantidad de controles personalizados de Delphi VCL integrados y de terceros no funcionan bien, o no funcionan en absoluto, con un DPI alto. Si planea activar TForm.Scaled, asegúrese de probar a 96, 125 y 150 DPI para cada formulario de su proyecto, y cada tercero y control integrado que utilice.

El propio Delphi está escrito en Delphi. Tiene activada la bandera de alta conciencia de DPI, para la mayoría de las formas, aunque incluso tan recientemente como en Delphi XE2, los propios autores de IDE decidieron NO activar esa marca de manifiesto de alta conciencia de DPI. Tenga en cuenta que en Delphi XE4 y versiones posteriores, el indicador de reconocimiento de HIGH DPI está activado y el IDE se ve bien.

Le sugiero que no use TForm.Scaled = true (que es un valor predeterminado en Delphi, por lo que, a menos que lo haya modificado, la mayoría de sus formularios tienen Scaled = true) con las banderas High DPI Aware (como se muestra en las respuestas de David) con Aplicaciones VCL que se crean con el diseñador de formularios integrado delphi.

En el pasado, intenté hacer una muestra mínima del tipo de rotura que puede esperar ver cuando TForm.Scaled es verdadero y cuando el escalado de formularios de Delphi tiene un problema técnico. Estos fallos no siempre y solo son provocados por un valor de DPI distinto de 96. No he podido determinar una lista completa de otras cosas, que incluyen los cambios de tamaño de fuente de Windows XP. Pero dado que la mayoría de estos fallos aparecen solo en mis propias aplicaciones, en situaciones bastante complejas, he decidido mostrarles algunas pruebas que pueden verificar ustedes mismos.

Delphi XE se ve así cuando configura el DPI Scaling en "Fonts @ 200%" en Windows 7, y Delphi XE2 está igualmente roto en Windows 7 y 8, pero estos fallos parecen estar solucionados a partir de Delphi XE4:

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

En su mayoría, estos son controles VCL estándar que se comportan mal a un DPI alto. Tenga en cuenta que la mayoría de las cosas no se han escalado en absoluto, por lo que los desarrolladores de Delphi IDE han decidido ignorar la conciencia de DPI, así como desactivar la virtualización de DPI. Una elección tan interesante.

Desactive la virtualización de DPI solo si desea esta nueva fuente adicional de dolor y opciones difíciles. Te sugiero que lo dejes solo. Tenga en cuenta que la mayoría de los controles comunes de Windows parecen funcionar bien. Tenga en cuenta que el control del explorador de datos de Delphi es un contenedor de C # WinForms alrededor de un control común de árbol de Windows estándar. Eso es un problema técnico puro de Microsoft, y solucionarlo podría requerir que Embarcadero reescriba un control de árbol .Net nativo puro para su explorador de datos, o que escriba algún código de propiedades de verificación y modificación de DPI para cambiar la altura de los elementos en el control. Ni siquiera microsoft WinForms puede manejar altos DPI de forma limpia, automática y sin código de kludge personalizado.

Actualización: Dato interesante: si bien el IDE de Delphi parece no estar "virtualizado", no está utilizando el contenido del manifiesto mostrado por David para lograr una "virtualización sin DPI". Quizás esté usando alguna función de API en tiempo de ejecución.

Actualización 2: En respuesta a cómo apoyaría el 100% / 125% DPI, se me ocurrió un plan de dos fases. La fase 1 es hacer un inventario de mi código para los controles personalizados que deben arreglarse para un DPI alto y luego hacer un plan para corregirlos o eliminarlos. La fase 2 sería tomar algunas áreas de mi código que están diseñadas como formularios sin administración de diseño y cambiarlas a formularios que usen algún tipo de administración de diseño para que los cambios de DPI o de altura de fuente puedan funcionar sin recortes. Sospecho que este trabajo de diseño de "intercontrol" sería mucho más complejo en la mayoría de las aplicaciones que el trabajo de "intracontrol".

Actualización: En 2016, el último Delphi 10.1 Berlin funciona bien en mi estación de trabajo de 150 ppp.

Warren P
fuente
5
Esa función de API sería SetProcessDPIAware.
David Heffernan
2
Excelente. Gracias por el nuevo factoide. Le sugiero que modifique su respuesta para sugerir eso como una ruta posible. Es posible que los clientes incluso quieran configurar esa opción (apáguela si no les funciona).
Warren P
La pantalla de presentación de Delphi usa virtualización DPI, probablemente porque la llamada a SetDPIAware es después de que el formulario de presentación ya se ha hecho visible.
Warren P
6
RAD Studio es una gran combinación de controles VCL estándar, controles personalizados, formularios .NET WinForms y FireMonkey. No es de extrañar que haya problemas. Y es por eso que RAD Studio no es un buen ejemplo.
Torbins
1
Si tiene razón, es el propio VCL el que tiene la cabeza en la arena. Incluso Microsoft tiene la cabeza en la arena. El único marco que he usado que hace un trabajo remotamente aceptable en esto es COCOA en Mac.
Warren P
63

Su configuración en el archivo .dfm se ampliará correctamente, siempre que lo Scaledesté True.

Si está configurando dimensiones en el código, debe escalarlas Screen.PixelsPerInchdividiendo por Form.PixelsPerInch. Úselo MulDivpara hacer esto.

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

Esto es lo que hace el marco de persistencia de formularios cuando Scaledes True.

De hecho, puede presentar un argumento convincente para reemplazar esta función con una versión que codifica de forma rígida un valor de 96 para el denominador. Esto le permite usar valores de dimensión absolutos y no preocuparse por el cambio de significado si cambia la escala de fuente en su máquina de desarrollo y vuelve a guardar el archivo .dfm. La razón que importa es que la PixelsPerInchpropiedad almacenada en el archivo .dfm es el valor de la máquina en la que se guardó por última vez el archivo .dfm.

const
  SmallFontsPixelsPerInch = 96;

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

Entonces, continuando con el tema, otra cosa de la que debe tener cuidado es que si su proyecto se desarrolla en varias máquinas con diferentes valores de DPI, encontrará que la escala que usa Delphi al guardar archivos .dfm da como resultado controles que se desvían sobre una serie de ediciones. . En mi lugar de trabajo, para evitar esto, tenemos una política estricta de que los formularios solo se editan a 96 ppp (escala del 100%).

De hecho, mi versión de ScaleFromSmallFontsDimensiontambién tiene en cuenta la posibilidad de que la fuente del formulario difiera en tiempo de ejecución de la configurada en tiempo de diseño. En las máquinas XP, los formularios de mi solicitud utilizan 8pt Tahoma. En Vista y versiones posteriores, se usa Segoe UI de 9 puntos. Esto proporciona otro grado de libertad. La escala debe tener en cuenta esto porque se supone que los valores de dimensión absolutos utilizados en el código fuente son relativos a la línea de base de Tahoma de 8 puntos a 96 ppp.

Si usa imágenes o glifos en su interfaz de usuario, estos también deben escalar. Un ejemplo común serían los glifos que se usan en barras de herramientas y menús. Querrá proporcionar estos glifos como recursos de iconos vinculados a su ejecutable. Cada ícono debe contener un rango de tamaños y luego, en tiempo de ejecución, usted elige el tamaño más apropiado y lo carga en una lista de imágenes. Algunos detalles sobre ese tema se pueden encontrar aquí: ¿Cómo cargo íconos desde un recurso sin sufrir alias?

Otro truco útil es definir las dimensiones en unidades relativas, relativas a TextWidtho TextHeight. Por lo tanto, si desea que algo tenga un tamaño de alrededor de 10 líneas verticales, puede usar 10*Canvas.TextHeight('Ag'). Esta es una métrica muy aproximada y lista porque no permite el espaciado de línea, etc. Sin embargo, a menudo todo lo que necesita hacer es poder arreglar que la GUI se adapte correctamente PixelsPerInch.

También debe marcar su aplicación como de alta resolución de DPI . La mejor forma de hacerlo es a través del manifiesto de la aplicación. Dado que las herramientas de compilación de Delphi no le permiten personalizar el manifiesto que usa, esto lo obliga a vincular su propio recurso de manifiesto.

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

El script de recursos se ve así:

1 24 "Manifest.txt"

donde Manifest.txtcontiene el manifiesto real. También necesitaría incluir la sección comctl32 v6 y establecerlo requestedExecutionLevelen asInvoker. Luego, vincula este recurso compilado a su aplicación y se asegura de que Delphi no intente hacer lo mismo con su manifiesto. En el Delphi moderno, esto se logra estableciendo la opción de proyecto Runtime Themes en None.

El manifiesto es la forma correcta de declarar que su aplicación tiene un alto nivel de DPI. Si solo desea probarlo rápidamente sin alterar su manifiesto, llame SetProcessDPIAware. Hágalo como lo primero que hace cuando se ejecuta su aplicación. Preferiblemente en una de las primeras secciones de inicialización de la unidad, o como lo primero en su archivo .dpr.

Si no declara que su aplicación tiene un alto nivel de DPI, Vista y versiones posteriores la renderizarán en un modo heredado para cualquier escala de fuente superior al 125%. Esto parece bastante espantoso. Trate de evitar caer en esa trampa.

Actualización de DPI por monitor de Windows 8.1

A partir de Windows 8.1, ahora hay soporte del sistema operativo para la configuración de DPI por monitor ( http://msdn.microsoft.com/en-ca/magazine/dn574798.aspx ). Este es un gran problema para los dispositivos modernos que pueden tener diferentes pantallas conectadas con capacidades muy diferentes. Es posible que tenga una pantalla de computadora portátil de DPI muy alto y un proyector externo de DPI bajo. Apoyar tal escenario requiere aún más trabajo que el descrito anteriormente.

David Heffernan
fuente
2
Eso no siempre es cierto. De hecho, configurar Scaled = true y luego configurar High DPI también puede causar algunas fallas extrañas en la mayoría de las aplicaciones delphi. He pasado cientos de horas tratando de que mis aplicaciones funcionen con un DPI alto y he descubierto que es mejor tener una pixelación de aspecto horrible que los controles recortados, movidos fuera de la pantalla, barras de desplazamiento extra o faltantes en varios controles, etc.
Warren P
@WarrenP Creo que esos problemas son específicos de su aplicación. Mi experiencia personal es que mi aplicación Delphi se muestra y escala perfectamente incluso con una escala de fuente del 200%.
David Heffernan
2
@WarrenP ¿Y qué? Es perfectamente posible utilizar Delphi para crear aplicaciones que se comporten mejor que el IDE de Delphi.
David Heffernan
1
He visto muchos diálogos con bordes fijos creados con Delphi 5,6,7 y la configuración escalada es verdadera para fallar. Esconder ok, cancelar botones, etc. Incluso algunos diálogos en Delphi2006 creo que fue mordido por esto. La combinación de componentes nativos de Delphi y componentes de Windows también produce efectos extraños. Siempre desarrollo la GUI en una escala de fuente del 125% y pongo la propiedad escalada en falso.
LU RD
2
Buena cosa. +1 para obtener información fantástica. Mi opinión (no lo hagas) es la segunda en importancia a la necesidad de saber CÓMO hacerlo cuando quieras hacer esto ...
Warren P
42

También es importante tener en cuenta que respetar el DPI del usuario es solo un subconjunto de su trabajo real:

respetando el tamaño de fuente del usuario

Durante décadas, Windows ha resuelto este problema con la noción de realizar el diseño utilizando unidades de diálogo , en lugar de píxeles. Se define una "unidad de diálogo" de modo que el carácter medio de la fuente sea

  • 4 unidades de diálogo (dlus) de ancho y
  • 8 unidades de diálogo (grupo) alto

ingrese la descripción de la imagen aquí

Delphi se envía con una noción (con errores) de Scaled, donde un formulario intenta ajustarse automáticamente en función de la

  • Configuración de Windows DPI del usuario, versos
  • la configuración de DPI en la máquina del desarrollador que guardó el formulario por última vez

Eso no resuelve el problema cuando el usuario usa una fuente diferente a la que diseñó el formulario, por ejemplo:

  • El desarrollador diseñó el formulario con MS Sans Serif 8pt (donde el carácter promedio es 6.21px x 13.00px, a 96 ppp)
  • usuario que ejecuta Tahoma 8pt (donde el carácter promedio es 5.94px x 13.00px, a 96 ppp)

    Como fue el caso de cualquiera que desarrolle una aplicación para Windows 2000 o Windows XP.

o

  • el desarrollador diseñó el formulario con ** Tahoma 8pt * (donde el carácter promedio es 5.94px x 13.00px, a 96 ppp)
  • un usuario que ejecuta Segoe UI 9pt (donde el carácter promedio es 6.67px x 15px, a 96 ppp)

Como buen desarrollador, respetarás las preferencias de fuente de tu usuario. Esto significa que también necesita escalar todos los controles en su formulario para que coincidan con el nuevo tamaño de fuente:

  • expandir todo horizontalmente en un 12,29% (6,67 / 5,94)
  • estirar todo verticalmente en un 15,38% (15/13)

Scaled no se encargará de esto por ti.

Empeora cuando:

  • diseñó su formulario en Segoe UI 9pt (Windows Vista, Windows 7, Windows 8 predeterminado)
  • el usuario está ejecutando Segoe UI 14pt , (por ejemplo, mi preferencia) que es10.52px x 25px

Ahora tienes que escalar todo

  • horizontalmente en un 57,72%
  • verticalmente en un 66,66%

Scaled no se encargará de esto por ti.


Si eres inteligente, puedes ver lo irrelevante que es respetar los DPI:

  • formulario diseñado con Segoe UI 9pt @ 96dpi (6.67px x 15px)
  • usuario que ejecuta Segoe UI 9pt @ 150dpi (10.52px x 25px)

No debería mirar la configuración de DPI del usuario, debería mirar su tamaño de fuente . Dos usuarios corriendo

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

están ejecutando la misma fuente . DPI es solo una cosa que afecta el tamaño de fuente; las preferencias del usuario son las otras.

StandardizeFormFont

Clovis notó que hago referencia a una función StandardizeFormFontque fija la fuente en un formulario y la escala al nuevo tamaño de fuente. No es una función estándar, sino un conjunto completo de funciones que realizan la tarea simple que Borland nunca manejó.

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 tiene 6 fuentes diferentes; no existe una única "configuración de fuente" en Windows.
Pero sabemos por experiencia que nuestros formularios deben seguir la configuración de Fuente de título de icono

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

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

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

Una vez que sabemos el tamaño de fuente vamos a escalar la forma a , obtenemos altura de la fuente actual de la forma ( en píxeles ), y la escala por ese factor.

Por ejemplo, si estoy configurando el formulario en -16, y el formulario está actualmente en -11, entonces necesitamos escalar todo el formulario de la siguiente manera:

-16 / -11 = 1.45454%

La estandarización ocurre en dos fases. Primero escale el formulario por la proporción de los tamaños de fuente nuevo: antiguo. Luego, cambie los controles (recursivamente) para usar la nueva fuente.

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;

Aquí está el trabajo de escalar un formulario. Funciona con errores en el propio Form.ScaleBymétodo de Borland . Primero tiene que deshabilitar todos los anclajes en el formulario, luego realizar el escalado y luego volver a habilitar los anclajes:

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;

y luego tenemos que usar recursivamente la nueva fuente:

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

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

        if not Assigned(AControlFont) then
            Exit;

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

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

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

Con los anclajes desactivados de forma recursiva:

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;

Y los anclajes se vuelven a habilitar de forma recursiva:

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


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

        Inc(StartingIndex);
    end;

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

Con el trabajo de cambiar la fuente de los controles a la izquierda:

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;

Eso es mucho más código de lo que pensaba que iba a ser; Lo sé. Lo triste es que no hay ningún desarrollador de Delphi en la tierra, excepto yo, que realmente hace que sus aplicaciones sean correctas.

Estimado desarrollador de Delphi : configure su fuente de Windows en Segoe UI 14pt y corrija su aplicación defectuosa

Nota : Cualquier código se libera al dominio público. No se requiere atribución.

Ian Boyd
fuente
1
Gracias por la respuesta, pero ¿qué sugieres para el mundo real? ¿Implementar un cambio de tamaño de todos los controles manualmente?
LaBracca
3
"Lo triste es que no hay ningún desarrollador de Delphi en la tierra, excepto yo, que realmente hace que sus aplicaciones sean correctas". Esa es una declaración muy arrogante que es incorrecta. De mi respuesta: de hecho, mi versión de ScaleFromSmallFontsDimension también tiene en cuenta la posibilidad de que la fuente del formulario difiera en tiempo de ejecución de la establecida en tiempo de diseño. La escala debe tener en cuenta esto porque se supone que los valores de dimensión absolutos utilizados en el código fuente son relativos a la línea de base de Tahoma de 8 puntos a 96 ppp.La tuya es una buena respuesta, claro, +1.
David Heffernan
1
@Ian No soy yo el que dijo eso. Suena como Warren.
David Heffernan
2
Esto es bastante asombroso, Ian. Gracias.
Warren P
2
Recientemente encontré esta pregunta y respuesta. He recopilado todo el código de Ian en una unidad de trabajo aquí: pastebin.com/dKpfnXLc y lo publiqué en Google+ aquí: goo.gl/0ARdq9 Publicando aquí en caso de que alguien lo encuentre útil.
W.Prins
11

Aquí está mi regalo. Una función que puede ayudarlo con el posicionamiento horizontal de elementos en sus diseños de GUI. Gratuita 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
fuente
2
Me alegro de que te guste, Warren. Tiene unos 15 años cuando no había soluciones disponibles para el problema que tenía que resolver. E incluso hoy en día puede haber una situación en la que se pueda aplicar. B-)
avra