Delphi11中未展开菜单时如何显示一级TMainMenu项悬停提示
问题场景
在Windows 10系统下使用Delphi 11 Alexandria开发32位VCL应用程序时,为TMainMenu控件的每个菜单项都设置了Hint属性,设计时界面如下:
核心单元代码如下:
unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.AppEvnts; type TForm1 = class(TForm) MainMenu1: TMainMenu; mFile: TMenuItem; mEdit: TMenuItem; mOpen: TMenuItem; ApplicationEvents1: TApplicationEvents; procedure ApplicationEvents1Hint(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} uses CodeSiteLogging; procedure TForm1.ApplicationEvents1Hint(Sender: TObject); begin CodeSite.Send('TForm1.ApplicationEvents1Hint: Application.Hint', Application.Hint); end; end.
对应DFM窗体设计代码如下:
object Form1: TForm1 Left = 0 Top = 0 Caption = 'Form1' ClientHeight = 366 ClientWidth = 639 Color = clBtnFace Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -15 Font.Name = 'Segoe UI' Font.Style = [] Menu = MainMenu1 Position = poScreenCenter ShowHint = True PixelsPerInch = 120 TextHeight = 20 object MainMenu1: TMainMenu Left = 248 Top = 144 object mFile: TMenuItem Caption = 'File' Hint = 'Click here to open the File menu' object mOpen: TMenuItem Caption = 'Open' Hint = 'Click here to open a File' end end object mEdit: TMenuItem Caption = 'Edit' Hint = 'Click here to open the Edit menu' end end object ApplicationEvents1: TApplicationEvents OnHint = ApplicationEvents1Hint Left = 248 Top = 160 end end
异常表现
鼠标指针悬停在菜单栏未展开的一级菜单项(比如mFile、mEdit)上时,不会触发Application.OnHint事件,无任何提示输出;只有展开对应菜单后,鼠标悬停在下拉列表内的菜单项上,才能正常获取到Application.Hint的值。
需求为:不展开菜单的前提下,鼠标悬停在mFile这类一级TMainMenu菜单项时,可接收到悬停通知并显示对应提示。
解决方案
VCL默认的菜单Hint处理逻辑仅覆盖展开后的下拉菜单项,菜单栏上的顶层菜单项属于窗口非客户区绘制的系统菜单部分,不会走常规的控件Hint触发流程,需要拦截菜单相关Windows消息手动处理,操作步骤如下:
- 在窗体类中声明消息处理方法,拦截
WM_MENUSELECT消息,该消息会在鼠标悬停在任意菜单项(包括未展开的顶层菜单项)时触发 - 从消息参数中解析当前悬停的菜单项索引、对应菜单句柄,匹配到绑定的
TMenuItem实例 - 手动将菜单项的Hint赋值给
Application.Hint,触发原有OnHint事件逻辑即可
完整实现代码如下:
首先在窗体类的私有部分添加消息方法声明:
private procedure WMMenuSelect(var Msg: TWMMenuSelect); message WM_MENUSELECT;
然后实现该消息处理方法:
procedure TForm1.WMMenuSelect(var Msg: TWMMenuSelect); var LMenu: HMENU; LItem: TMenuItem; I: Integer; begin inherited; // 处理菜单关闭、无效选中的场景,清除残留提示 if (Msg.MenuFlag = $FFFF) and (Msg.IDItem = 0) then begin Application.CancelHint; Exit; end; LItem := nil; // 区分顶层菜单栏菜单项和下拉子菜单项 if Msg.MenuFlag and MF_POPUP <> 0 then begin LMenu := Msg.Menu; // 遍历主菜单顶层项,通过子菜单句柄匹配当前悬停的菜单项 for I := 0 to MainMenu1.Items.Count - 1 do begin if GetSubMenu(LMenu, I) = Msg.SubMenu then begin LItem := MainMenu1.Items[I]; Break; end; end; end else begin // 下拉子菜单项直接通过命令ID查找 LItem := MainMenu1.FindItem(Msg.IDItem, fkCommand); end; // 匹配到菜单项且配置了Hint时,触发提示逻辑 if Assigned(LItem) and (LItem.Hint <> '') then begin Application.CancelHint; Application.Hint := LItem.Hint; end else begin Application.CancelHint; end; end;
添加上述代码后无需修改原有ApplicationEvents1Hint的业务逻辑,鼠标悬停在未展开的顶层菜单项上时,即可正常触发Hint事件,输出并显示对应提示内容。
注意:如果使用了VCL样式或自定义菜单栏绘制,需确认
WM_MENUSELECT消息未被自定义逻辑拦截,该方案在原生VCL菜单、Windows 10/11系统下兼容Delphi 10.x及之后的所有版本。
内容的提问来源于stack exchange,提问作者user1580348
相关产品推荐
相关产品推荐

