Question

J'ai donc un TMenuItem attaché à une TAction sur un TPopupMenu pour un TDBGrid (en réalité une tierce partie, mais vous voyez l'idée). En fonction de la ligne sélectionnée dans la grille, la TAction est activée ou désactivée. Ce que je veux, c'est pouvoir afficher un indice à l'utilisateur expliquant pourquoi l'élément est désactivé.

En ce qui concerne la raison pour laquelle je veux un indice sur un élément de menu désactivé, disons simplement que je suis d'accord avec Joel .

Tous les TMenuItem ont une propriété hint, mais au mieux, je peux dire qu'ils ne sont utilisés que par le gestionnaire d'événements TApplicationEvent.OnHint pour coller l'indicateur dans un TStatusBar ou un autre traitement spécial. J'ai trouvé un article sur la création d'une fenêtre homogène pour un TMenuItems de TMainMenu, mais cela ne fonctionne pas sur TMenuItem d'un TPopupMenu. Cela fonctionne en traitant le message WM_MENUSELECT, qui, autant que je sache, n’est pas envoyé sur un TPopupMenu.

Était-ce utile?

La solution

WM_MENUSELECT est en effet géré pour les éléments de menu dans les menus contextuels également, mais pas par la fenêtre proc du formulaire contenant le menu (popup), mais par une fenêtre auxiliaire invisible créée par Menus.PopupList. Heureusement, vous pouvez (au moins sous Delphi 5) accéder à ce HWND via Menus.PopupList.Window.

Vous pouvez maintenant utiliser la méthode ancienne pour sous-classer une fenêtre, comme décrit par exemple dans cette CodeGear article , pour gérer WM_MENUSELECT également pour les menus contextuels. Le HWND sera valide à partir de la création du premier TPopupMenu avant la destruction du dernier objet TPopupMenu.

Un test rapide avec l'application de démonstration de l'article lié à la question devrait révéler si cela va fonctionner.

Modifier: Cela fonctionne effectivement. J'ai modifié l'exemple lié pour afficher des astuces également pour le menu contextuel. Voici les étapes:

Ajoutez un gestionnaire pour OnDestroy, une variable membre pour l'ancien proc de fenêtre et une méthode pour le nouveau proc de fenêtre au formulaire suivant:

TForm1 = class(TForm)
  ...
  procedure FormCreate(Sender: TObject);
  procedure FormDestroy(Sender: TObject);
  procedure ApplicationEvents1Hint(Sender: TObject);
private
  miHint : TMenuItemHint;
  fOldWndProc: TFarProc;
  procedure WMMenuSelect(var Msg: TWMMenuSelect); message WM_MENUSELECT;
  procedure PopupListWndProc(var AMsg: TMessage);
end;

Modifiez le gestionnaire OnCreate du formulaire afin de sous-classer la fenêtre PopupList masquée et implémentez la restauration appropriée de la procédure de fenêtre dans le gestionnaire OnDestroy:

procedure TForm1.FormCreate(Sender: TObject);
var
  NewWndProc: TFarProc;
begin
  miHint := TMenuItemHint.Create(self);

  NewWndProc := MakeObjectInstance(PopupListWndProc);
  fOldWndProc := TFarProc(SetWindowLong(Menus.PopupList.Window, GWL_WNDPROC,
    integer(NewWndProc)));
end;

procedure TForm1.FormDestroy(Sender: TObject);
var
  NewWndProc: TFarProc;
begin
  NewWndProc := TFarProc(SetWindowLong(Menus.PopupList.Window, GWL_WNDPROC,
    integer(fOldWndProc)));
  FreeObjectInstance(NewWndProc);
end;

Implémentez la proc de fenêtre sous-classée:

procedure TForm1.PopupListWndProc(var AMsg: TMessage);

  function FindItemForCommand(APopupMenu: TPopupMenu;
    const AMenuMsg: TWMMenuSelect): TMenuItem;
  var
    SubMenu: HMENU;
  begin
    Assert(APopupMenu <> nil);
    // menuitem
    Result := APopupMenu.FindItem(AMenuMsg.IDItem, fkCommand);
    if Result = nil then begin
      // submenu
      SubMenu := GetSubMenu(AMenuMsg.Menu, AMenuMsg.IDItem);
      if SubMenu <> 0 then
        Result := APopupMenu.FindItem(SubMenu, fkHandle);
    end;
  end;

var
  Msg: TWMMenuSelect;
  menuItem: TMenuItem;
  MenuIndex: integer;
begin
  AMsg.Result := CallWindowProc(fOldWndProc, Menus.PopupList.Window,
    AMsg.Msg, AMsg.WParam, AMsg.LParam);
  if AMsg.Msg = WM_MENUSELECT then begin
    menuItem := nil;
    Msg := TWMMenuSelect(AMsg);
    if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then begin
      for MenuIndex := 0 to PopupList.Count - 1 do begin
        menuItem := FindItemForCommand(PopupList.Items[MenuIndex], Msg);
        if menuItem <> nil then
          break;
      end;
    end;
    miHint.DoActivateHint(menuItem);
  end;
end;

Ceci est effectué pour tous les menus contextuels d'une boucle, jusqu'à ce que le premier élément ou sous-menu correspondant soit trouvé.

Autres conseils

Je ne suis pas sûr que cela aide, mais j'ai créé ma propre fenêtre d'indications multilignes (pour Delphi7) afin de pouvoir afficher plus d'une ligne de texte. Il est open source et vous pouvez le trouver ici

Il y a du travail à faire pour l'afficher au bon endroit sur l'écran, mais vous en avez le plein contrôle.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top