delphidelphi-10.3-rio

How to Detect a TPopupMenu's OnClose (OnPopDown) Event in Delphi applications


I can write some code inside TPopupMenu's OnPopUp event. But I also need another event for OnPopDown. Is there any way to do it using Delphi 10.3.3?


Solution

  • There are various options you can try.

    Approach 1

    In the simpler case when you have a particular control whose context menu you need to track, you can handle its WM_CONTEXTMENU message manually:

      protected
        procedure WMContextMenu(var Message: TWMContextMenu);
          message WM_CONTEXTMENU;
    

    where (for example)

    procedure TForm1.WMContextMenu(var Message: TWMContextMenu);
    begin
      if
        Assigned(PopupMenu)
          and
        (ClientRect.Contains(ScreenToClient(Message.Pos)) or (Message.Pos = Point(-1, -1)))
      then
      begin
        Windows.Beep(200, 500); // pre-popup code
        if (Message.XPos = -1) and (Message.YPos = -1) then // Menu key or Shift+F10
          with ClientToScreen(Point(0, 0)) do
            PopupMenu.Popup(X, Y)
        else
          PopupMenu.Popup(Message.XPos, Message.YPos);
        Windows.Beep(400, 500); // post-popup code
      end
      else
        inherited;
    end;
    

    The test ClientRect.Contains(ScreenToClient(Message.Pos)) is necessary so that you don't "overwrite" the scrollbar's own context menu. Also, you need to consider the case when the context menu is opened using the keyboard (e.g. menu key or Shift+F10).

    Approach 2

    If this is not enough for you, you could create your own TPopupMenu child class and override its Popup method, which is virtual. Add a DoPopdown method and call it at the end (following the design of the DoPopup method).

    To quickly test this approach, you can use an interposer class:

    type
      TPopupMenu = class(Vcl.Menus.TPopupMenu)
        procedure Popup(X, Y: Integer); override;
      end;
    

    implemented as

    { TPopupMenu }
    
    procedure TPopupMenu.Popup(X, Y: Integer);
    begin
      inherited;
      Windows.Beep(400, 500); // post-popup code
    end;
    

    But of course it is nicer to create a true descendant class (TPopupMenuEx, perhaps?) that you register in the IDE. Add an FOnPopdown: TNotifyEvent private field, a DoPopdown protected function, and an OnPopdown published property. This precisely mimics the OnPopup mechanism.

    Needless to say, this approach also works with a TTrayIcon's menu.