Fixing Delphi XE7 PageControl hidden‑tab index bug

Summary

A bug in Delphi XE7’s Vcl.ComCtrls.pas causes TPageControl.PageIndexFromTabIndex to return an incorrect page index when multiple tabs have TabVisible = False. The routine only skips the first hidden tab, leading to off‑by‑one errors that surface in custom drawing and other internal operations.

Root Cause

  • The original loop iterates from 0 to TabIndex and increments the result for every hidden tab it encounters.
  • When more than one hidden tab appears before the target tab, the function stops after adjusting for the first hidden tab, leaving the result short by the number of additional hidden tabs.
  • The algorithm assumes at most one hidden tab in the range, which is false in real‑world usage.

Why This Happens in Real Systems

  • Dynamic UI: Applications frequently hide tabs based on user permissions, feature flags, or runtime conditions.
  • Custom drawing: OnDrawTab handlers need the correct mapping between visible (tab) and underlying (page) indices.
  • Internal VCL logic: Several VCL methods rely on PageIndexFromTabIndex; an incorrect mapping propagates subtle UI glitches.

Real-World Impact

  • Mismatched event dataOnDrawTab receives the wrong page index, causing drawing artifacts or crashes.
  • Navigation bugs – Programmatic page switches (PageIndex := …) land on the wrong sheet.
  • State loss – Hidden tabs may be mistakenly treated as visible, leading to incorrect persistence of UI state.
  • Hard‑to‑debug – The error appears only when multiple tabs are hidden, a scenario developers may not test.

Example or Code (if necessary and relevant)

function TPageControl.FPageIndexFromTabIndex(TabIndex: Integer): Integer;
var
  I: Integer;
begin
  Result := 0;
  for I := 0 to TabIndex do
    while (not Pages[I + Result].TabVisible) and (I + Result < PageCount) do
      Inc(Result);
  Inc(Result, TabIndex);
end;

How Senior Engineers Fix It

  • Replace the buggy method with the corrected implementation above (or a similar logic that counts all hidden tabs before the target).
  • Add unit tests that cover:
    • No hidden tabs.
    • A single hidden tab.
    • Multiple hidden tabs before, after, and interleaved with visible tabs.
  • Encapsulate the fix in a subclass (TFixedPageControl) to avoid modifying VCL source directly, then use the subclass throughout the project.
  • Review all call sites (OnDrawTab, internal VCL uses) to ensure they reference the fixed version.

Why Juniors Miss It

  • Assume single‑hidden‑tab logic: Junior developers often test only the simplest case and overlook complex UI states.
  • Limited familiarity with VCL internals: The private nature of PageIndexFromTabIndex hides the bug from casual inspection.
  • Focus on “works for now”: Without a regression suite, the off‑by‑one error goes unnoticed until a feature that hides multiple tabs is added.

Key takeaway: Always verify index‑translation functions against edge cases involving multiple hidden elements; a single‑pass adjustment is rarely sufficient in dynamic UI components.

Leave a Comment