In a 32-bit VCL Application in Windows 10 in Delphi 11 Alexandria, I have a TMainMenu
with a hint on each menu item:
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.
This is the 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
When I hover the mouse pointer over the "File" menu item, there is NO Application hint! Only after OPENING the File menu, I do get an Application.Hint
when hovering the mouse pointer over the "File" menu item.
So how can I get notified when hovering the mouse pointer over the mFile
menu item without opening the menu?
CodePudding user response:
If you only want to react on mouse cursor movements (and not on keyboard input) then process the WM_NCMOUSEMOVE
message:
interface
TfrmMain= class( TForm )
mnuMain: TMainMenu; // The menu, containing at least one top item
protected
procedure WmNcMouseMove(var vMsg: TWMNCMouseMove); message WM_NCMOUSEMOVE;
end;
implementation
procedure TfrmMain.WmNcMouseMove(var vMsg: TWMNCMouseMove);
var
iItem: Integer;
vR: TRect;
oItem: TMenuItem;
begin
inherited;
// Only react to menu related mouse cursor moves
if vMsg.HitTest= HTMENU then begin
oItem:= nil; // Not found yet
for iItem:= 0 to self.mnuMain.Items.Count- 1 do begin // All topmost items
if GetMenuItemRect( self.Handle, self.mnuMain.Handle, iItem, vR ) then begin
// Also checking Y is needed, as a menu can have more than 1 line.
// Consider sizing your window width to the minimum to see this effect.
if (vMsg.XCursor>= vR.Left)
and(vMsg.XCursor<= vR.Right)
and(vMsg.YCursor>= vR.Top)
and(vMsg.YCursor<= vR.Bottom) then begin
oItem:= self.mnuMain.Items[iItem]; // Found the item under the mouse cursor
break;
end;
end else break; // Makes no sense to continue on any error
end;
// Now get the .hint or otherwise display empty text
if oItem<> nil then self.Caption:= oItem.Hint else self.Caption:= '';
end;
end;
- Successfully tested with Delphi 7 on Windows 7 x64 with themes disabled (Windows 95 look), 1 monitor only and the menu having 1 or even 2 lines (very short window width): hovering the mouse cursor over the menu items without clicking them will display the correct hint.
- Maybe a desktop spanning multiple monitors needs additional work.
- Covering keyboard input (Alt or F10) is already done with your code (
TApplicationEvents.OnHint
). - Discovered
GetMenuItemRect()
in Delphi: Menu Hint bug
andWM_NCMOUSEMOVE
in Not receiving WM_NCHitTest on title bar.
CodePudding user response:
I changed the logic of your idea which is great!
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, Vcl.ComCtrls;
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
mFile: TMenuItem;
mEdit: TMenuItem;
mOpen: TMenuItem;
StatusBar1: TStatusBar;
ApplicationEvents1: TApplicationEvents;
procedure ApplicationEvents1Hint(Sender: TObject);
procedure FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
procedure WmNcMouseMove(var vMsg: TWMNCMouseMove); message WM_NCMOUSEMOVE;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
uses
CodeSiteLogging;
procedure TForm1.ApplicationEvents1Hint(Sender: TObject);
begin
StatusBar1.SimpleText := Application.Hint;
end;
procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
StatusBar1.SimpleText := '';
end;
procedure TForm1.WmNcMouseMove(var vMsg: TWMNCMouseMove);
var
iItem: Integer;
vR: TRect;
oItem: TMenuItem;
begin
inherited;
if vMsg.HitTest = HTMENU then
begin
oItem := nil;
for iItem := 0 to Self.MainMenu1.Items.Count - 1 do
begin
if GetMenuItemRect(Self.Handle, Self.MainMenu1.Handle, iItem, vR) then
begin
if (vMsg.XCursor >= vR.Left) and (vMsg.XCursor <= vR.Right) and (vMsg.YCursor >= vR.Top) and (vMsg.YCursor <= vR.Bottom) then
begin
oItem := Self.MainMenu1.Items[iItem];
BREAK;
end;
end
else
BREAK;
end;
if Assigned(oItem) then
StatusBar1.SimpleText := oItem.Hint;
end;
end;
end.
What do you think?