Link to home
Start Free TrialLog in
Avatar of zoltan082098
zoltan082098

asked on

need right button down event for tmenuitem

TMenuitem has neither message handling WM_RBUTTONDOWN nor event like oncontextpopup or something similar. However, I want to activate a pop-up menu if the user clicks the menu item by the right mouse button (e.g. for setting a shortcut for the menuitem in question or perform other customisation tasks).
If menuitem would have been descendant of tcontrol the problem could have solved as simple as it's possible: either overriding wndproc or creating a message method:
 
procedure rightclick(var message: TMessage); message WM_RBUTTONDOWN;

Tmenuitem is derived only from tcomponent so it has no wndproc method. The problem is that the message method creating (see above) doesn't work too.
Why?

Has anybody a bright idea how can I capture the right button click for menu items? Notice that I want to use something descendant from TMenuItem to keep compatibility with Delphi's menu-related objects. Building menus only at runtime is acceptable.
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

hello zoltan, a Window's menu is not handled by the system like a system Window, there is no Message WndProc for a menu, all of that operational funtionality (mouse and keyboard messages) is done by the system. I have used API menus and for the most part using menu API on Delphi menus confuses Delphi as to the state of a menu or menu item, , , or has unusual effects. If you want to have a Delphi menu change menu item properties, it may be easiest and safer to add a menu item that says - Change menu Item properties - and show a dialog box to get the item and changes for that menu item. Also, while a menu is displayed your app is modal (non responsive) all the time the menu is displayed. and a menu does not respond to a right click. You may could do a mouse hook in a DLL, but you would still have the modal problem.
Sorry, I was thinking of something else, I was dead wrong about the app being modal, so here is some code to get the right click on a menu. You need to have a TTimer added with the Interval set at about 100. I use the Journal hook to get the right mouse click. There is a TPopupMenu on the form also. -


 private
    { Private declarations }
    Track: Boolean;
    procedure MenuSel(var Msg : TMessage); message WM_MENUSELECT {WM_INITMENUPOPUP};
    procedure MenuExitLoop(var Msg : TMessage); message WM_EXITMENULOOP;


var
  Form1: TForm1;
  JHook: THandle;
  SelIndex, SelIndex2: Word;
 


function JourProc(Code, wParam: Integer; var EventStrut: TEVENTMSG): Integer; stdcall;
begin
{this is the JournalRecordProc}
Result := CallNextHookEx(JHook, Code, wParam, Longint(@EventStrut));
{the CallNextHookEX is not really needed for journal hook since it it not
really in a hook chain, but it's standard for a Hook}
if (Code < 0) or (Code = HC_SYSMODALON) then Exit;
{you should cancel operation if you get HC_SYSMODALON}

if Code = HC_ACTION then
  begin
  if (EventStrut.message = WM_RBUTTONUP) then
    begin
    {the menu Items I allowed this on were on the first submenu with SelIndex
    of 2 through 4}
    if (Form1.SelIndex < 2) or (Form1.SelIndex > 4) then Exit;
    mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTDOWN, Form1.Left+50, Form1.Top+6, 0, 0);
    mouse_event(MOUSEEVENTF_ABSOLUTE or MOUSEEVENTF_LEFTUP, Form1.Left+50, Form1.Top+6, 0, 0);
    Form1.SelIndex2 := Form1.SelIndex;
    Form1.Timer1.Enabled := True;
    end;
  end;
end;


procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
{make sure you UN hook it if the app closes}
UnhookWindowsHookEx(JHook);
end;

procedure TForm1.MenuExitLoop(var Msg : TMessage);
begin
Track := False;
UnhookWindowsHookEx(JHook);
JHook := 0;
Label2.Caption := 'Menu Exit Loop';
end;

procedure TForm1.MenuSel(var Msg : TMessage);
begin
SelIndex := LOWORD(Msg.wParam);
Label3.Caption := 'Menu sel '+IntToStr(LOWORD(Msg.wParam));
end;

procedure TForm1.File1Click(Sender: TObject);
begin
{this is the Main menu first item (usually File) which is the
sub menu I want to do right clicks on, if you want other sub menus then
assign their OnClick method in the object inspector to this procedure}
Label1.Caption := 'Menu Init File';
if Track then Exit;
JHook := SetWindowsHookEx(WH_JOURNALRECORD , @JourProc, hInstance, 0);
{SetWindowsHookEx starts the Hook}
if JHook > 0 then
  Track := True;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
{I had to add a timer because the Popup menu will not show untill the
main menu has compleatly cleared}
Timer1.Enabled := False;
PopupMenu1.Items[0].Caption := 'New Caption for Item '+IntToStr(SelIndex2);
PopupMenu1.Popup(Left+150,Top+100);
end;


 - - - - - - - - - - - - - - - - - - -
this seems to work for me
ASKER CERTIFIED SOLUTION
Avatar of Member_2_248744
Member_2_248744
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
sorry about the double post, you will need to change the line


 if (Form1.SelIndex < 2) or (Form1.SelIndex > 4) then Exit;


to


 if (SelIndex < 2) or (SelIndex > 4) then Exit;
listening
Avatar of zoltan082098
zoltan082098

ASKER

Yes,
The solution proposed seems to work fine. Of course, I've made some enhancements:
1. I've used WH_MOUSE directly in SetWindowsHookEx insead of WH_JOURNALRECORD.
2. I've added a submenu instead of waiting for main menu to disappear, than showing a popupmenu.

There are still two problems with this solution:
1. Adding a submenu dinamically to selected menu item puts the menu to redraw itself which causes flickering. (This happens because the little black triangle must be drawn on the selected menu to show that the menu item has submenus. Because one single menuitem has changed, Windows redraws the whole menu. I don'n need this triangle at all but I think Windows draws it if necessary even the menuitem is an owner draw one. I cannot have the submenu in question at design time even with visible = false [in order to show it only when user right-clicks the menu item] because the triangle will be there all the time -- that's totally inacceptable.)

2. I cannot understand why Delphi(6) indents shortcut keys on left instead of aligning them on right? It's a Bullshit, even the Delphi 6 application's main menu has its shortcuts aligned on right! Can I solve it without owner-drawing the items?

You already have got an 'A' from me, but please try to answer these two questions too...
Thanks,
Zoltan
the WH_JOURNALRECORD hook takes alot less perfomance hit than the WH_MOUSE hook (or any other hook) because it is not in a hook chain, that's why I used it. As to redrawing the displayed menu, I do not know and have never seen any reference for doing anything to control or alter the painting of a sub-menu. However, it seems that there should not be any flickering unless the menu is drawn more than once in rapid sequence. I would check your code for when you add the Menu Item to the menu, be sure that you do not alter the menu or menu Item After you add it to the menu, do all modifications, BEFORE the menu item is added. In oter words do a -
 
NewItem := TMenuItem.Create(FileMenu);
  try
    NewItem.Caption := 'New Item';
{do all menu modifications here be fore you insert it};
    FileMenu.Delete(4);
    FileMenu.Insert(4, NewItem);
  except
    NewItem.Free;
    raise; { reraise the exception }
  end;

I was not aware that delphi 6 did this, did you look for a property to adjust this?
The solving is simple as it's possible: forget about any addmenu function or similar: we must create the submenu first, than use setmenuinfo for the meniutem in question with the lpmii.hsubmenu = the submenu handle. No flickering at all!
The only problem remained is the wrong indenting of the shortcut keys. I cannod find any property regarding of this problem...
Be sure to test your menu if you use any API menu functions like setmenuinfo to add a sub menu, I have had many weird things happen doing API to Delphi menus. I Think I would stick to Delphi menu methods, may be use Assign

NewItem := TMenuItem.Create(FileMenu);
 try
   NewItem.Assign(FileMenu.Item[4];
{do all menu modifications here for NewItem};
   FileMenu.Item[4].Assign(NewItem);
 finally
   NewItem.Free;
 end;

- - - - - - -
I'm sorry, I can not help you with the Delphi 6 menu alignment, it seems like that would not be something delphi would do.