Afficher un indice d'aide sur un élément de menu désactivé d'un menu contextuel
-
19-08-2019 - |
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.
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.