Friday, April 10, 2009

How to Display Menu Item Hints In Delphi

When a mouse is over a component (a TButton, for example) if ShowHint property is True and there is some text in the Hint property, the hint / tooltip window will be displayed for the component.

Hints for Menu Items?

By (Windows) design, even if you set the value for the Hint property to a Menu item, the popup hint will not get displayed.
However, the Windows Start Menu items do display hints, and the Favorites menu in the Internet Explorer also displays menu item hints.

It is quite common to use the OnHint event of the global Application variable, in Delphi applications, to display menu item (long) hints in a status bar.

Windows do not expose the messages needed to support a traditional OnMouseEnter event. However, the WM_MENUSELECT message is sent when the user selects a menu item.

The WM_MENUSELECT implementation of the TCustomForm (ancestor of the TForm) sets the menu item hint into Application.Hint that can be used in the Application.OnHint event.

If you want to add menu item popup hints (tooltips) to your Delphi application menus you *only* need to handle the WM_MenuSelect message properly.

The TMenuItemHint class - popup hints for menu items!

Since you cannot rely on the Application.ActivateHint method to display the hint window for menu items (as menu handling is completely done by Windows), to get the hint window displayed you must create your own version of the hint window - by deriving a new class from the THintWindow.

Here's how to create a TMenuItemHint class - a hint widow that actually gets displayed for menu items!

Note: download the full source to explore it more easily.

First, you need to handle the WM_MENUSELECT Windows message:

TForm1 = class(TForm)
procedure WMMenuSelect(var Msg: TWMMenuSelect) ; message WM_MENUSELECT;
procedure TForm1.WMMenuSelect(var Msg: TWMMenuSelect) ;
menuItem : TMenuItem;
hSubMenu : HMENU;
inherited; // from TCustomForm (so that Application.Hint is assigned)

menuItem := nil;
if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then
if Msg.MenuFlag and MF_POPUP = MF_POPUP then
hSubMenu := GetSubMenu(Msg.Menu, Msg.IDItem) ;
menuItem := Self.Menu.FindItem(hSubMenu, fkHandle) ;
menuItem := Self.Menu.FindItem(Msg.IDItem, fkCommand) ;

miHint.DoActivateHint(menuItem) ;
end; (*WMMenuSelect*)

Quick info: the WM_MENUSELECT message is sent to a menu's owner window (Form1 !) when the user selects (not clicks!) a menu item. Using the FindItem method of the TMenu class, you can get the menu item currently selected. Parameters of the FindItem function relate to the properties of the message received. Once we know what menu item the mouse is over, we call the DoActivateHint method of the TMenuItemHint class. Note: the miHint variable is defined as "var miHint : TMenuItemHint" and is created in the Form's OnCreate event handler.

Now, what's left is the implementation of the TMenuItemHint class.

Here's the interface part:

TMenuItemHint = class(THintWindow)
activeMenuItem : TMenuItem;
showTimer : TTimer;
hideTimer : TTimer;
procedure HideTime(Sender : TObject) ;
procedure ShowTime(Sender : TObject) ;
constructor Create(AOwner : TComponent) ; override;
procedure DoActivateHint(menuItem : TMenuItem) ;
destructor Destroy; override;
You can find the full implementation in the sample project.

Basically, the DoActivateHint function calls the ActivateHint method of the THintWindow using the TMenuItem's Hint property (if it is assigned).
The showTimer is used to ensure that the HintPause (of the Application) elapses before the hint is displayed. The hideTimer uses Application.HintHidePause to hide the hint window after a specified interval.

When would you use Menu Item Hints?

While some might say that it is not a good design to display hints for menu items, there are situations where actually displaying menu item hints is much better than using a status bar.

No comments: