Why I Stopped Using TPopupMenu in Delphi FMX (And What I Use Instead)

If you have been building FireMonkey applications for any length of time, you have probably noticed that certain parts of the framework feel like they were never quite finished. Menus are one of those parts. After spending too long debugging a strange visual glitch in our app, I decided to investigate properly — and what I found was a well-documented but still-unfixed bug at the heart of `TPopupMenu`.

The Problem

My application has a tree view where each item shows a “…” button that opens a context menu with several actions. Some of those actions are only relevant in certain states, so I show or hide menu items dynamically by setting their `Visible` property.

After opening and closing the popup a few times, items started appearing in the wrong order. A menu item that should have been third suddenly appeared first. Another item that was supposed to be hidden appeared as a gap at the top of the menu. The layout was unstable and unpredictable.

I found a detailed write-up from 2013 on [Delphi Haven](https://delphihaven.wordpress.com/2013/01/03/making-the-never-ending-sorry-tale-of-fmx-menus-a-bit-happier/) describing the exact same issue. Thirteen years later, it remains unfixed — I verified it still reproduces in Delphi 13.1.

Root Cause

When a `TPopupMenu` is displayed, FMX internally reparents the **visible** items from the `TPopupMenu` component to the actual popup window control. When the popup closes, it reparents them back. The problem is that hidden items (those with `Visible = False`) are left behind during this dance. Since they never got moved and moved back, the next time the popup opens, the index positions are wrong. Hidden items end up at the top of the list, and visible items appear shifted.

The underlying data model is correct — only the visual order gets corrupted. But for users, the menu looks broken.

Interposer solution?

There is a workaround: write an interposer class that overrides the `Popup` method, saves the index and reference of every hidden item before calling `inherited`, then reinserts them at the correct positions afterward. It works, but it adds about 30 lines of fragile bookkeeping that has to fight against the framework on every single popup.

I also discovered two additional problems with `TPopupMenu`:

– It does not work on Android or iOS at all. If you plan to ship a cross-platform app, `TPopupMenu` is simply not an option on mobile.
– There is no `OnPopup` event. In VCL, `TPopupMenu` fires `OnPopup` before the menu appears, giving you a chance to update item visibility. FMX omitted this entirely.

Given all of this, patching `TPopupMenu` felt like the wrong approach. I decided to replace it entirely.

The Final Solution: TPopup + TListBox

Instead of `TPopupMenu`, I build the context menus out of a `TPopup` containing a `TListBox`. Each “menu item” is a `TListBoxItem`. This combination avoids every problem above:

– Showing and hiding items is a simple `Visible` property set — there is no internal reparenting, so order is always stable.
– It works on all platforms, including Android and iOS.
– I have full control over the popup lifecycle.

The mechanics look like this:

– The popup is created with `Height = 0` so that its inline representation (the one sitting in the parent control’s layout tree) does not intercept mouse events when closed. When opened, the height is recalculated from the number of visible items.
– `PreferedDisplayIndex = -1` tells FMX to figure out the correct monitor from the placement target, which matters on multi-monitor setups.
– The `OnChange` event of the `TListBox` is used to detect item selection. It is assigned *after* items are added to prevent spurious fires during construction.
– After a selection, `TThread.ForceQueue` defers the actual action by one message-loop iteration. This is necessary because FMX’s popup teardown interferes with modal dialogs if you try to show one synchronously inside a `OnChange` handler.

Wrapping It Into a Component

Once I had this pattern working in one place, I noticed the same boilerplate appearing in a second tree view item class. That is when I extracted a small `TPopupList` class:

TPopupList = class
public
constructor Create(AParent: TFmxObject; PlacementTarget: TControl; Width: Single = 180);
destructor Destroy; override;
function AddItem(const Text: string): TListBoxItem;
procedure Open;
property OnItemSelected: TProc<TListBoxItem>;
end;

The constructor handles all the setup: creating the `TPopup` and `TListBox`, wiring up the event handlers, and hiding the inline representation. `AddItem` returns a `TListBoxItem` that the caller keeps a reference to for later visibility control. `Open` recalculates the height and shows the popup. `OnItemSelected` is an anonymous method that gets called (via `ForceQueue`) when the user picks an item.

Usage at the call site looks like this:

FMenu := TPopupList.Create(Self, btnMore);
mniSetup := FMenu.AddItem('Setup');
mniDelete := FMenu.AddItem('Delete');
FMenu.OnItemSelected := procedure(Item: TListBoxItem)
begin
if Item = mniSetup then DoSetup
else if Item = mniDelete then DoDelete;
end;

// In the trigger button handler:
procedure TMyItem.btnMoreClick(Sender: TObject);
begin
FMenu.Open;
end;

Showing or hiding items is still just:

mniDelete.Visible := Stuff;

No index management, no fighting the framework.

What I Still Do Not Have

The one feature `TPopupMenu` provides that `TPopup + TListBox` does not is hover highlighting. A standard popup menu lights up each item as the mouse moves over it. Our `TListBox`-based menu does not do this out of the box.

The fix is straightforward: wire up `OnMouseMove` on the `TListBox`, use `ItemByPoint(X, Y)` to find the item under the cursor, and swap its background color. Clear the highlight in `OnMouseLeave`. About fifteen lines of code. I have not done this yet, but since the logic now lives in one place inside `TPopupList`, I only have to write it once.

Keyboard navigation (arrow keys and Enter) is more involved and would require intercepting key events on the `TPopup`. I decided it is not worth the effort for a small inline context menu, but nothing stops you from adding it.

 

For my use case — a context menu with a small number of items that change visibility based on state — `TPopupList` is strictly better. If you need full keyboard navigation and you are targeting Windows only, the standard `TPopupMenu` is still an option (with the interposer-class workaround for the Visible bug). For cross-platform work, `TPopup + TListBox` is the only reliable choice.

 

The component is published (free) in my LightSaber library.

Leave a Comment

Scroll to Top