2

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.

Chau Chee Yang
  • 18,422
  • 16
  • 68
  • 132
  • 3
    I 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
  • 3
    Must 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
  • 1
    Even 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 Answers4

3

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.

Community
  • 1
  • 1
NGLN
  • 43,011
  • 8
  • 105
  • 200
2

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.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
2

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;
NGLN
  • 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
  • 1
    My 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
2

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.

NGLN
  • 43,011
  • 8
  • 105
  • 200
Chau Chee Yang
  • 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