2011. január 29., szombat

How to check if a menu selection has been dispatched by the TPopupList


Problem/Question/Abstract:

I am creating a dynamic popup menu, fill it and call Popup to show the menu. After that method returns I am done with the menu, so I free it (see comment of Peter below). The problems is the OnClick events never get called. I found the problem but don't see how to get around it. When the menu is created it is added to the PopupList which has created a hidden window whose responsibility it is to dispatch the wm_Command message sent by all popup menus. The problem is that the wm_Command is happening after the Popup menu method returns and after I have freed the menu and it has been removed from the PopupList.

How do I get around this? I don't see a mechanism to check if the menu selection has been dispatched by the PopupList before freeing the menu item. I guess I could make the Popup menu a field of my class and free it in the OnClick Events but I won't be able to free the menu if no menu item is selected. I don't like this solution since the only place I need the popup is in one method of my class so I want to keep it a local variable.

Answer:

You go badly wrong when you think you're done. When Popup returns you are not done with the menu, you have just shown it and the user can now make a menu selection or close the menu by clicking elsewhere or hittin ESC. Only after that has happended are you truely "done" with the menu, if you destroy the VCl wrapper earlier the windows menu may visually persist but you have destroyed the link between it and your code.

In D5 there is a solution to your problem. Add this unit to your project (no further code is needed) and the active form will get the custom messages declared in the units interface. You could destroy the popup menu instance when you see CM_EXITMENULOOP. This solution does not work in earlier versions of Delphi which did not expose the Popuplist to the outside world. In these versions the only solution would be to install a WH_CALLWNDPROC hook (thread specific) when the menu is popped up and remove it again when it gets the WM_EXITMENULOOP message.

unit ExPopupList;

interface

uses
  Controls;

const
  CM_MENUCLOSED = CM_BASE - 1;
  CM_ENTERMENULOOP = CM_BASE - 2;
  CM_EXITMENULOOP = CM_BASE - 3;

implementation

uses Messages, Forms, Menus;

type
  TExPopupList = class(TPopupList)
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

  { TExPopupList }

procedure TExPopupList.WndProc(var Message: TMessage);
  procedure Send(msg: Integer);
  begin
    if Assigned(Screen.Activeform) then
      Screen.ActiveForm.Perform(msg, Message.wparam, Message.lparam);
  end;
begin
  case message.Msg of
    WM_ENTERMENULOOP:
      Send(CM_ENTERMENULOOP);
    WM_EXITMENULOOP:
      Send(CM_EXITMENULOOP);
    WM_MENUSELECT:
      with TWMMenuSelect(Message) do
        if (Menuflag = $FFFF) and (Menu = 0) then
          Send(CM_MENUCLOSED);
  end;
  inherited;
end;

initialization
  PopupList.Free;
  PopupList := TExPopupList.Create;
  {Note: will be freed by Finalization section of Menus unit}
end.

Nincsenek megjegyzések:

Megjegyzés küldése