^ Fix Delphi VCL ScaleBy() TToolBar bug

How to fix Delphi VCL ScaleBy() TToolBar bug

The problem: when using ScaleBy() or the Windows high DPI scaling setting the TToolBars placed on the form are not repositioned and resized.

The solution: first put all TToolBar controls into a TPanel, that is placed at the same position and sized to the same size as the original TToolBar.
Set the TPanel's 'BevelOuter' to 'bvNone', and clear it's caption. Assign the same 'Anchors' as the TToolBar has.
Set the TToolBar that is inside the TPanel to Left = 0 and Top = 0.
This will solve the wrong position.

To fix the icon sizes, the TImageList holding the icons needs to be resized and the icons must be loaded dynamicaly.
Use the following code to resize the TImageList for example in FormCreate():
    ImageListButtonIcons.Width := MulDiv(ImageListButtonIcons.Width, Screen.PixelsPerInch, 96);
    ImageListButtonIcons.Height := MulDiv(ImageListButtonIcons.Height, Screen.PixelsPerInch, 96);
Then call the InitIcons procedure below:
procedure TMainForm.InitIcons;
var
    Icon: TIcon;
begin
    Icon := TIcon.Create;
    try
        Icon.SetSize(256, 256); //* Expecting large icons, at least that large like the scaling requires
        Icon.Handle := LoadIcon(HInstance, PChar('ICON1'));
        ImageListButtonIcons.AddIcon(Icon);
        Icon.Handle := LoadIcon(HInstance, PChar('ICON2'));
        ImageListButtonIcons.AddIcon(Icon);
        //* Load all the icons here
    finally
    	FreeAndNil(Icon);
    end;
end;
You can use XN Resource Editor to create a .res file containing the icons, then add the .res file to the project with:
{$R Icons.res}
If you named the .res file as 'Icons.res'.

Use the following code to automaticaly wrap all TToolBars into a TPanel on the form, call this before calling ScaleBy():
procedure WrapTToolBarsInTPanel(Control: TWinControl; NewScale: Integer; OriginalScale: Integer);
var
    i: Integer;
    NewPanel: TPanel;
    ToolBar: TToolBar;
begin
    for i := Control.ControlCount - 1 downto 0 do begin
        if i >= Control.ControlCount then begin
            Continue;
        end;
        try
            if (Control.Controls[i] is TToolBar)
            AND ((Control.Controls[i] as TToolBar).Tag = 0)
            then begin
                ToolBar := Control.Controls[i] as TToolBar;
                if Assigned(ToolBar.Images)
                AND (ToolBar.Images.Tag = 0)
                then begin
                    ToolBar.Images.Tag := 1;
                    ToolBar.Images.Width := MulDiv(ToolBar.Images.Width, NewScale, OriginalScale);
                    ToolBar.Images.Height := MulDiv(ToolBar.Images.Height, NewScale, OriginalScale);
                end;
                if Assigned(ToolBar.HotImages)
                AND (ToolBar.HotImages.Tag = 0)
                then begin
                    ToolBar.HotImages.Tag := 1;
                    ToolBar.HotImages.Width := MulDiv(ToolBar.HotImages.Width, NewScale, OriginalScale);
                    ToolBar.HotImages.Height := MulDiv(ToolBar.HotImages.Height, NewScale, OriginalScale);
                end;
                NewPanel := TPanel.Create(ToolBar.Parent);
                NewPanel.Name := 'PanelFor' + ToolBar.Name;
                NewPanel.Caption := '';
                NewPanel.Parent := ToolBar.Parent;
                NewPanel.BevelOuter := bvNone;
                NewPanel.Left := ToolBar.Left;
                NewPanel.Top := ToolBar.Top;
                NewPanel.Width := ToolBar.Width;
                NewPanel.Height := ToolBar.Height;
                NewPanel.Anchors := ToolBar.Anchors;
                NewPanel.Align := ToolBar.Align;
                ToolBar.Parent := NewPanel;
                ToolBar.Left := 0;
                ToolBar.Top := 0;
                ToolBar.Anchors := [akLeft, akTop];
                ToolBar.Font.Size := MulDiv(ToolBar.Font.Size, NewScale, OriginalScale);
                ToolBar.Tag := 1;
                SendMessage(ToolBar.Handle, TB_AUTOSIZE, 0, 0);
            end else begin
                if Control.Controls[i] is TWinControl then begin
                    if (Control.Controls[i] as TWinControl).ControlCount > 0 then begin
                        WrapTToolBarsInTPanel(TWinControl(Control.Controls[i]), NewScale, OriginalScale);
                    end;
                end;
            end;
        except
            //*
        end;
    end;
end;

procedure FitTPanelToolBars(Control: TWinControl);
var
    i: Integer;
    Panel: TPanel;
    ToolBar: TToolBar;
begin
    for i := 0 to Control.ControlCount - 1 do begin
        try
            if Control.Controls[i] is TToolBar then begin
                ToolBar := Control.Controls[i] as TToolBar;
                if ToolBar.Parent is TPanel then begin
                    Panel := ToolBar.Parent as TPanel;
                    Panel.Width := ToolBar.Width + 2;
                    Panel.Height := ToolBar.Height + 2;
                end;
            end else begin
                if Control.Controls[i] is TWinControl then begin
                    if (Control.Controls[i] as TWinControl).ControlCount > 0 then begin
                        FitTPanelToolBars(TWinControl(Control.Controls[i]));
                    end;
                end;
            end;
        except
            raise Exception.Create('Error: ' + ToolBar.Name + ' -> ' + ToolBar.Parent.Name);
        end;
    end;
end;
For example in FormCreate(), like:
procedure TForm1.FormCreate(Sender: TObject);
begin
    WrapTToolBarsInTPanel(Self);
    ScaleBy() here...
    FitTPanelToolBars(Self);
end;

Additionaly to fix the TStatusBar panels' widths, use:
    StatusBar.Height := MulDiv(StatusBar.Height, Screen.PixelsPerInch, 96);
    for i := 0 to -1 + StatusBar.Panels.Count do begin
        StatusBar.Panels[i].Width := MulDiv(StatusBar.Panels[i].Width, Screen.PixelsPerInch, 96);
    end;
In the above codes use the needed scale value instead of 'Screen.PixelsPerInch' if using the ScaleBy() function manualy instead.


If you found the above information useful, please check my components.


[Top]