TAdvToolBar: menu button captions are measured with a scaled font but painted with an unscaled one, so the spacing between main menu items depends on the monitor

Environment

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

Line numbers refer to AdvToolBar.pas of 13.6.8.2. Method names are exact.

Summary

When a TAdvMainMenu is hosted in a TAdvToolBar (ToolBar.Menu := AdvMainMenu), the top level menu items are painted by AdvToolBar.pas as toolbar buttons.

TAdvCustomToolBarButton.AdjustSize, which measures the caption to decide the button width, applies the DPI scale factor to the font unconditionally. But TAdvCustomToolBarButton.DrawGlyphAndCaption, which paints that same caption, applies it under a condition that excludes menu buttons.

So for a menu button the width is computed for a font that is never used to draw it. The button ends up wider than its text by exactly the scale factor, and since that factor is 1.0 at 96 dpi, the apparent spacing between the menu items changes from one monitor to another.

Steps to reproduce

  1. Two monitors with different scaling (we use 125% and 100%).
  2. A form with a TAdvToolBar, a TAdvMainMenu with several top level items (File, Edit, View...), and a TAdvToolBarOfficeStyler whose AdvMenuStyler is assigned.
  3. Assign the menu to the toolbar: ToolBar.Menu := AdvMainMenu.
  4. Compare the horizontal spacing between the menu captions on the 120 dpi monitor and on the 96 dpi one.

Expected

The spacing between menu items looks the same on both monitors. It should depend only on the margins, which scale with the DPI.

Actual

Monitor Font used to MEASURE Font used to PAINT Result
120 dpi height -19 height -15 ~25% of unused width per caption: the items look widely spaced
96 dpi height -15 height -15 no extra width: the items look cramped

The captions themselves are drawn at the correct size in both cases. What is wrong is the width reserved for them.

Root cause

Both routines start the same way, taking the font from the menu styler when the button is a menu button:

if ParentStyler and Assigned(ToolBarStyler) and Assigned(ToolBarStyler.CurrentAdvMenuStyler) then
begin
  if IsMenuButton then
    Canvas.Font.Assign(ToolBarStyler.CurrentAdvMenuStyler.RootItem.font)
  else
    Canvas.Font.Assign(ToolBarStyler.ButtonAppearance.CaptionFont);
end
else
  Canvas.Font.Assign(Font);

and then they disagree on whether to scale it. There are four places in the unit that make this same decision, with four different conditions:

Line Method Condition guarding the scaling
28784 DrawGlyphAndCaption (1st branch) not (Assigned(ToolBarStyler) and Assigned(ToolBarStyler.AdvMenuStyler)) or not IsMenuButton
29295 DrawGlyphAndCaption (2nd branch) the same, plus or (IsMenuButton and not ...RootItem.UseSystemFont)
30082 AdjustSize (1st branch) none — see below
30259 AdjustSize (2nd branch) not (Assigned(ToolBarStyler) and Assigned(ToolBarStyler.AdvMenuStyler)) — no IsMenuButton test

The first branch of AdjustSize (line 30082) is the one that matters for a menu button, and it has no condition at all. What put us on the track is that the condition is already written there, commented out, immediately above it:

//      if not Assigned(ToolBarStyler) or not Assigned(ToolBarStyler.AdvMenuStyler) then
        Canvas.Font.Height := Round(Canvas.Font.Height * scale);

We believe this is a defect rather than a deliberate choice: measuring and painting the same text with different fonts cannot be intentional, and the four conditions above cannot all be correct.

Suggested fix

Make AdjustSize use the same condition the painting code already uses, so that measurement and drawing stay symmetrical. In the first branch (line 30082):

if not (Assigned(ToolBarStyler) and Assigned(ToolBarStyler.AdvMenuStyler)) or not IsMenuButton then
  Canvas.Font.Height := Round(Canvas.Font.Height * scale);

With this change, the spacing between the menu items depends only on the margin and looks identical on both monitors, and no other toolbar button is affected: for a non-menu button the condition evaluates to true, which is the current behaviour.

The second branch of AdjustSize (line 30259) presumably needs the same test. We did not have to touch it for our case, but it is inconsistent with the painting code for the same reason.

We investigated this issue and applied improvements. Next TMS VCL UI Pack update will have these improvements.