TAdvToolBar: docked toolbar caption height uses a frozen unit-global DPI scale (Per-Monitor V2)

TAdvToolBar: docked toolbar caption height uses a frozen unit-global DPI scale (Per-Monitor V2)

Environment

  • TMS VCL UI Pack 13.6.5.1
  • RAD Studio / Delphi 13.1, VCL, Win32 and Win64
  • Windows 11 (build 26200)
  • Application manifest: Per-Monitor V2 (<AppDPIAwarenessMode>PerMonitorV2</AppDPIAwarenessMode>)
  • Two monitors: primary 1280x800 at 125% (120 dpi), secondary 1920x1080 at 100% (96 dpi)

Line numbers below refer to AdvToolBar.pas of 13.6.5.1. The surrounding code is unmodified.

Summary

The caption band of a docked TAdvToolBar (the coloured strip that shows the toolbar caption above the buttons) does not follow a DPI change. Its height is computed from ADVToolBarDPI_Scale, a unit-global variable that is set only once, from the DPI of the monitor where the first window was created, and is never refreshed for toolbars hosted in a TAdvDockPanel.

The height of the toolbar itself, on the other hand, is computed from the current monitor. So the two values disagree with each other inside the same component.

Steps to reproduce

  1. Two monitors with different scaling (we use 125% and 100%).
  2. A form with a TAdvDockPanel and a docked TAdvToolBar with ShowCaption = True and CaptionHeight = 27 (designed at 96 dpi).
  3. Start the application on the 125% monitor.
  4. Drag the window to the 100% monitor.

Expected

The caption band scales down together with the toolbar, back to its design height (27 px at 96 dpi).

Actual

The caption band keeps the height computed for 120 dpi. Measured values:

Toolbar height Caption band
Expected at 96 dpi 95 px 27 px
Actual after dragging 120 dpi → 96 dpi 95 px (correct) 36 px (33% too tall)

The extra 9 px overlap the button area at the bottom of the toolbar.

Starting the application directly on the 100% monitor does not show the problem, because then the global is initialised with the right DPI.

Root cause

ADVToolBarDPI_Scale is a unit-global (interface section, around line 425):

var
  ADVToolBarDPI_Scale: single = 1.0;
  ADVToolBarDPI_ScaleSet: boolean = False;

It is initialised only when it has never been set, in TAdvDockPanel.SetBounds (~12268):

if not ADVToolBarDPI_ScaleSet then begin
  ADVToolBarDPI_FormScaled := frm.Scaled;
  if Assigned(frm.Monitor) then
    ADVToolBar_SetDPIScale(frm.Monitor.PixelsPerInch);
  ...

and TAdvCustomToolBar.ChangeScale (~23233) also guards it the same way, so it never refreshes an existing value:

if not ADVToolBarDPI_ScaleSet then
  ADVToolBarDPI_Scale := GetDPIScale(Self, Canvas);

TAdvCustomToolBar.GetCaptionRect (~13664) then uses that frozen value for every non-TAdvPage parent — which is the docked case:

if Assigned(Parent) and (Parent is TAdvPage) then
  CptHeight := CaptionHeight
else
  CptHeight := Round(ADVToolBarDPI_Scale * CaptionHeight);   // <-- frozen global

Two observations that we think make this a design issue rather than a rounding one:

  1. The component contradicts itself. TAdvCustomToolBar.UpdateSize reserves room for the same caption using Round(CaptionHeight * CalculateDPIScale(Self)) (~18109), which does read the current monitor. So the height reserved and the height painted are computed with two different factors and disagree after a DPI change.

  2. TAdvToolBarPager already does the right thing. TAdvToolBarPager.ChangeScale (~39544) recomputes the global unconditionally:

    ADVToolBarDPI_Scale := GetDPIScale(Self, Canvas);
    

    Toolbars hosted in a TAdvDockPanel never get this. The asymmetry looks unintended.

There is also a more general problem with the value being global at all: under Per-Monitor V2, two windows of the same application can be on monitors with different DPI at the same time, and a single global scale cannot be correct for both.

Suggested fix

Use the DPI of the instance instead of the global, which is what the rest of the VCL does under Per-Monitor V2. In GetCaptionRect:

// CaptionHeight comes from the DFM, i.e. expressed at 96 dpi
CptHeight := MulDiv(CaptionHeight, CurrentPPI, USER_DEFAULT_SCREEN_DPI);

We have been running this change since August 2026 and it fixes the reported symptom on both monitors, on startup and while dragging the window between them. GetCaptionRect is the single point that matters: the background, the caption text and GetMyClientRect are all derived from it.

We deliberately left UpdateSize untouched: with the band correct, the room it reserves is equal (at 96 dpi) or 2 px larger (at 120 dpi), which is not noticeable, and changing it would alter the height of every toolbar in the application at high DPI.

One caveat for the TAdvPage branch, in case you apply something similar there: for a toolbar of a TAdvToolBarPager shown in compact mode, Parent is a TCompactWindow, so it falls into the else branch even though its CaptionHeight has already been scaled by TAdvToolBarPager.ChangeScale — that combination would scale twice.

Why the usual workaround is not an option for us

The advice we found in the support center for high-DPI trouble with TAdvToolBar is to set the manifest to System Aware. That would mean giving up Per-Monitor V2 for the whole application: Windows would stretch the window bitmap and everything would look blurry on any monitor other than the one the application started on. Our application is fully Per-Monitor V2 and the rest of the VCL behaves correctly under it.

We traced & solved this issue. The next update will address this.