We use mouse left click to trigger actions in menu items of TPopupMenu. How to trigger different action on mouse middle click in these menu items? In other word, mouse left and middle click on TPopupmenu's menu items are both different action.
-
3I think that the reason this is hard (and the reason nobody else has responded yet) is because it's a very non-standard and counter-intuitive thing to do... How will your users know that they need to press the middle button on the menu item? How will they know __which__ menu items a middle press works on? – Nat Aug 06 '11 at 04:55
-
3Must it be the middle button? Not all mice have three buttons. (I've seen laptops for sale at the Microsoft store with only *one* button, like Macs, which means even the right button requires using a special modifier key.) Maybe you could [use the right button](http://stackoverflow.com/questions/3788184/how-to-detect-tmenuitem-right-click) instead of the middle one. That's supported directly by the [popup-menu API](http://msdn.microsoft.com/en-us/library/ms647610.aspx). – Rob Kennedy Aug 06 '11 at 06:31
-
1Even if the mouse has a middle button few people know that it can be clicked. Most only know about scrolling. What's more the middle mouse button when it is pressed has a pre-defined meaning which is to start a scroll loop. – David Heffernan Aug 06 '11 at 08:08
-
If the middle click is not a suitable choice, how about using some key combination with mouse click like Ctrl-Click, to trigger another action? The TPopupMenu doesn't have any event related to customized click. – Chau Chee Yang Aug 06 '11 at 09:14
4 Answers
The global Menus.PopupList
variable keeps track of all PopupMenus and handles all massages send to them. You can override this PopupList with your own instance, as follows:
type
TMyPopupList = class(TPopupList)
private
FMenuItem: TMenuItem;
protected
procedure WndProc(var Message: TMessage); override;
end;
{ TMyPopupList }
procedure TMyPopupList.WndProc(var Message: TMessage);
var
FindKind: TFindItemKind;
I: Integer;
Item: Integer;
Action: TBasicAction;
Menu: TMenu;
begin
case Message.Msg of
WM_MENUSELECT:
with TWMMenuSelect(Message) do
begin
FindKind := fkCommand;
if MenuFlag and MF_POPUP <> 0 then
FindKind := fkHandle;
for I := 0 to Count - 1 do
begin
if FindKind = fkHandle then
begin
if Menu <> 0 then
Item := GetSubMenu(Menu, IDItem)
else
Item := -1;
end
else
Item := IDItem;
FMenuItem := TPopupMenu(Items[I]).FindItem(Item, FindKind);
if FMenuItem <> nil then
Break;
end;
end;
WM_MBUTTONUP:
if FMenuItem <> nil then
begin
GetMenuItemSecondAction(FMenuItem, Action);
Menu := FMenuItem.GetParentMenu;
if Action <> nil then
begin
Menu := FMenuItem.GetParentMenu;
SendMessage(Menu.WindowHandle, WM_IME_KEYDOWN, VK_ESCAPE, 0);
Action.Execute;
Exit;
end;
end;
end;
inherited WndProc(Message);
end;
initialization
PopupList.Free;
PopupList := TMyPopupList.Create;
The GetMenuItemSecondAction
routine you have to write yourself. Maybe this answer provides some help about adding your own actions to a component.
Note that the code under WM_MENUSELECT
is simply copied from Menus.TPopupList.WndProc
. You could also retrieve the MenuItem in the WM_MBUTTONUP
handling by using MenuItemFromPoint
.
But as the many comments have already said: think twice (or more) before implementing this UI functionality.
You are not notified of such an event. If you were there would be an entry for middle mouse button click in the list of menu notifications.
So perhaps you could use some sort of hack behind the back of the menu system if you really want to do this. However, as discussed in the comments, there are good reasons for thinking that your proposed UI may not be very appropriate.

- 601,492
- 42
- 1,072
- 1,490
If the middle click is not a suitable choice, how about using some key combination with mouse click like Ctrl-Click, to trigger another action? The TPopupMenu doesn't have any event related to customized click.
That is preferred over a middle mouse button click.
And then it is much simpler. Just check in your action execute handler if the CTRL button is pressed:
procedure TForm1.Action1Execute(Sender: TObject);
begin
if (GetKeyState(VK_CONTROL) and $8000 = 0) then
// process normal click
else
// process ctrl click
end;

- 43,011
- 8
- 105
- 200
-
Yeah, this is more intuitive but it still requires some explanation though. The long hint text of the action can be sufficient. – NGLN Aug 06 '11 at 10:25
-
1My intention is use left mouse click to open a new form if the form isn't exist and middle mouse click to open a new form regardless if the form exist. The middle click is to mimic Windows 7 task bar buttons where using middle click to open a new instance of program. – Chau Chee Yang Aug 07 '11 at 00:52
I try to combine 2 answers from author NGLN and come out with the following.
Define a new class inherit from TPopupList:
TMyPopupList = class(TPopupList)
protected
procedure WndProc(var Message: TMessage); override;
end;
procedure TMyPopupList.WndProc(var Message: TMessage);
var H: HWND;
begin
case Message.Msg of
WM_MBUTTONDOWN: begin
H := FindWindow(PChar('#32768'), nil);
SendMessage(H, WM_IME_KEYDOWN, VK_RETURN, 0);
end;
end;
inherited WndProc(Message);
end;
initialization
PopupList.Free;
PopupList := TMyPopupList.Create;
end.
The Item1Click is an OnClick event handler of TMenuItem that perform based on mouse click:
procedure TForm1.Item1Click(Sender: TObject);
begin
if (GetKeyState(VK_MBUTTON) and $80 > 0) then
Caption := 'Middle Click'
else
Caption := 'Normal Click';
end;
Note: #32768 is the default window class name for a pop-up menu, see MSDN documentation.

- 43,011
- 8
- 105
- 200

- 18,422
- 16
- 68
- 132
-
+1 For using the mainform's title bar as test output medium. And the use of the default window class name for the popup menu is also well found! – NGLN Aug 09 '11 at 17:50