Détecter la fermeture d’un TPopupMenu

Le composant TPopupMenu possède peu d’évènements. Seulement OnChange et OnPopup sont disponibles. Dans certains cas, il pourrait être pratique de détecter la fermeture d’un menu contextuel. Malheureusement, cet évènement n’existe pas, mais il est par contre possible de le coder.

Lorsqu’un menu contextuel se ferme, l’évènement WM_MENUSELECT est envoyé avec certains paramètres. Le seul problème c’est que ce n’est pas la fenêtre qui reçoit l’évènement. C’est la classe TPopupList qui a cette responsabilité. Dans Menus.hpp on retrouve cette ligne de code:

extern PACKAGE TPopupList* PopupList;

L’objet PopupList est créé à l’initialisation, il va donc falloir capturer les messages qu’il reçoit pour appeler une méthode lors de la fermeture du menu.

Voici la liste des méthodes et attributs à ajouter a votre fichier .h:

private:	// User declarations
    void __fastcall PopupWndProc(Messages::TMessage &Message);

    WNDPROC OldPopupProc;
    TFNWndProc FPopupProcInst;
    TPopupMenu *FLastOpenPopupMenu;
protected:
    void __fastcall DoPopupExit(TPopupMenu *PopupMenu);

La première étape est de rediriger les messages vers la méthode PopupWndProc. Avant de faire cela on va d’abord enregistrer l’ancienne adresse de la procédure de fenêtre dans OldPopupProc. Voici le code à ajouter à votre constructeur:

    FPopupProcInst = MakeObjectInstance(PopupWndProc);
    OldPopupProc = (WNDPROC)SetWindowLongPtr(PopupList->Window, GWL_WNDPROC,
        (LONG_PTR)FPopupProcInst);

Dans le destructeur on va restaurer l’appel vers la méthode WndProc originale et libérer la mémoire allouée par la fonction MakeObjectInstance:

    if(FPopupProcInst != NULL)
    {
        SetWindowLong(PopupList->Window, GWL_WNDPROC, (LONG)OldPopupProc);
        FreeObjectInstance(FPopupProcInst);
    }

À la réception du message WM_MENUSELECT on regarde si MenuFlag est à 0xFFFF et Menu à NULL. Si c’est le cas, alors le menu est fermé et on appelle DoPopupExit. Cette méthode a pour paramètre un pointeur vers le TPopupMenu qui vient de se fermer. La variable FLastOpenPopupMenu contient cette information. On lui affecte une valeur à la réception du message WM_INITMENUPOPUP qui est appelé lorsqu’un TPopupMenu est sur le point d’être actif. À la place de ce message, l’évènement OnPopup aurait pu être utilisé, mais je l’ai fait de cette manière pour rendre le code plus général. Les lignes 25 et 26 servent à appeler la méthode WndProc originale.

void __fastcall TForm1::PopupWndProc(Messages::TMessage &Message)
{
    if(Message.Msg == WM_MENUSELECT)
    {
        TWMMenuSelect *MenuSelect = (TWMMenuSelect *)&Message;
        if(MenuSelect->MenuFlag == 0xFFFF && MenuSelect->Menu == NULL)
        {
            DoPopupExit(FLastOpenPopupMenu);
        }
    }
    else if(Message.Msg == WM_INITMENUPOPUP)
    {
        TWMInitMenuPopup *InitMenuPopup = (TWMInitMenuPopup *)&Message;
        const int ListCount = PopupList->Count;
        for(int i = 0; i < ListCount; ++i)
        {
            TPopupMenu *LPopupMenu = (TPopupMenu *)PopupList->Items[i];
            if(LPopupMenu->Handle == InitMenuPopup->MenuPopup)
            {
                FLastOpenPopupMenu = LPopupMenu;
                break;
            }
        }
    }
    Message.Result = CallWindowProc(OldPopupProc, PopupList->Window,
        Message.Msg, Message.WParam, Message.LParam);
}

La seule chose qui manque est de mettre votre code à l’intérieur de cette méthode.

void __fastcall TForm1::DoPopupExit(TPopupMenu *PopupMenu)
{
}

Avec le code plus haut, il serait très facile de faire un composant que l’on pourrait utiliser dans plusieurs projets qui nécessitent d’être notifiés lors de la fermeture d’un menu contextuel. L’ajout d’une propriété OnPopupExit serait simple à implémenter.

J’espère que cet article vous sera pratique.