Comment savoir si le menuitem d'un ttoolbutton est abandonné?
-
14-11-2019 - |
Question
Dans le contexte d'une barre d'outils de dessin propriétaire utilisé pour héberger des entrées de menu (TToolButtons avec leur ensemble de métiutes et de propriétés groupées), je veux savoir si le menuitem correspondant est abandonné. Le problème est que la propriété de l'État dans ONAdvancedCustomDrawbutton ne reflète pas ces informations.
Lorsque le Toolbutton est cliqué, sa propriété Down est vraie, mais dans le cas particulier ci-dessus (MenuItem set et groupés = true), juste après la suppression du menu, un autre onaDvancedCustomDrawbutton est tiré mais cette fois avec Down Set to False.
Cela signifie que je finis par dessiner le bouton avec un état pas en baisse.
En regardant la source du VCL, il semble que les informations sur les baisses de Toolbutton sont stockées dans le FMenubutton du Ttoolbar privé Field, et Windows est informé de l'état chaud par une performance (TB_SetHoTitem), mais aucun de ceux-ci ne fournit d'accès en lecture ...
Le VCL effectue également la liste déroulante via un FTEMPMENU privé, dont la poignée n'est donc pas accessible.
PS: FWIW Si vous utilisez la solution Hacky, le seul champ privé utilisable semble être FButtonMenu qui devra être comparé à votre bouton.Menuetem dans le CustomDraw, les autres Fiels privés ne sont pas réglés assez tôt (comme FMenubutton) ou sont des variables privées ( Comme MenUbuttonIndex) avec un emplacement variable. Toujours pas trop satisfaisant cependant.
La solution
L'obtention du statut de menu est problématique, le code qui fait apparaître le menu est assez compliqué, utilise certains crochets de messages. Ce n'est généralement pas le code que vous voudriez toucher. Heureusement, la barre d'outils elle-même garde une trace de l'état du menu déroulant, en utilisant le FMenuDropped
variable. Malheureusement, cette variable est privé, vous ne pouvez pas y accéder depuis l'extérieur, l'astuce "piratée" ne fonctionne pas. Étant privé, il n'offre pas non plus RTTI!
Il y a deux solutions possibles:
Modifiez le VCL et ajoutez une propriété qui rend FMenudRopped disponible à partir de l'extérieur
Allez sur comctrls.pas, trouvez le TToolBar = class(TToolWindow)
Déclaration, allez à la section publique et ajoutez ceci:
property MenuDropped:Boolean read FMenuDropped;
À partir de votre code, vous pourrez alors vérifier la barre d'outils s'il a un menu supprimé ou non. La partie malheureuse de cela est qu'elle nécessite des modifications au VCL. Jamais une bonne idée, difficile à synchroniser entre plusieurs programmeurs.
Utilisez un hack pour accéder directement au champ FMenudropped, sans changer le VCL
Pour ce faire, vous devez obtenir le décalage du FMenuDropped
champ. Une fois que vous avez obtenu, vous pouvez écrire quelque chose comme ceci:
if PBoolean(Integer(Toolbar1) + 865)^ then
DoStuffIfMenuIsDropped
else
OtherStuffIfMenuIsNotDropped;
La 865
est en fait la bonne constante pour Delphi 2010! Voici un très moyen rapide d'obtenir la constante.
- Accédez aux paramètres du compilateur, vérifiez "Compiler à l'aide de Debug DCU's"
- Ouvrir des comctrls.pas, allez à
procedure TToolButton.Paint
, placez un point de frein là-dedans. - Commencez l'application, prenez un morceau de papier et un stylo. Lorsque le programme s'arrête au point de frein, ouvrez l'inspecteur de débogage. Pour ce faire, placez simplement le curseur sur le nom d'un champ, n'importe quel champ, et frappez Alt+F5. Avec la fenêtre de l'inspecteur de débogage frappé Ctrl+N Pour montrer le générique
Inspect
éditeur qui vous permet d'inspecter quoi que ce soit. EntrerInteger(FToolbar)
. Notez le résultat sur le morceau de papier. - Succès Ctrl+N Encore une fois, cette fois entre
Integer(@FToolBar.FMenuDropped)
. Notez ce deuxième numéro. - La constante dont vous avez besoin est la différence entre la deuxième et la première. C'est ça!
Il y a bien sûr quelques problèmes possibles. Tout d'abord cela dépend du exact Version Delphi que vous utilisez. Si le code doit être compilé sur différentes versions du compilateur Delphi, intelligent $IFDEF
besoin d'être utilisé. Néanmoins, cela est réalisable.
(Éditer): Vous pouvez utiliser cette même technique pour accéder à n'importe quel domaine privé de n'importe quelle classe. Mais tu auras besoin de penser de nombreux fois avant de faire cela, car les champs privés sont rendus privés pour une raison.
Autres conseils
Utilisez une aide de classe.
Par exemple.
TToolBarHelper = class helper for TToolBar
private
function GetMenuDropped: Boolean;
public
property MenuDropped: Boolean read GetMenuDropped;
end;
...
function TToolBarHelper.GetMenuDropped: Boolean;
begin
Result := Self.FMenuDropped;
end;
Maintenant, partout où vous utilisez un ttoolbar, vous pouvez désormais accéder à de nouvelles propriétés appelées menudropped.
Lorsqu'un bouton déroulant est cliqué, le formulaire est envoyé un TBN_DROPDOWN
notification. Cela peut être utilisé pour suivre le bouton qui a lancé un menu:
type
TForm1 = class(TForm)
[...]
private
FButtonArrowDown: TToolButton;
procedure WmNotify(var Msg: TWmNotify); message WM_NOTIFY;
[...]
uses
commctrl;
procedure TForm1.WmNotify(var Msg: TWmNotify);
function FindButton(Bar: TToolBar; Command: Integer): TToolButton;
var
i: Integer;
begin
Result := nil;
for i := 0 to Bar.ButtonCount - 1 do
if Bar.Buttons[i].Index = Command then begin
Result := Bar.Buttons[i];
Break;
end;
end;
begin
if (Msg.NMHdr.code = TBN_DROPDOWN) and
(LongWord(Msg.IDCtrl) = ToolBar1.Handle) then begin
FButtonArrowDown := FindButton(ToolBar1, PNMToolBar(Msg.NMHdr).iItem);
inherited;
FButtonArrowDown := nil;
end else
inherited;
end;
procedure TForm1.ToolBar1AdvancedCustomDrawButton(Sender: TToolBar;
Button: TToolButton; State: TCustomDrawState; Stage: TCustomDrawStage;
var Flags: TTBCustomDrawFlags; var DefaultDraw: Boolean);
var
DroppedDown: Boolean;
begin
DroppedDown := Button = FButtonArrowDown;
[...]
Notez que la variable «Droppeddown» dans «OnAdvancedCustomDrawbutton» n'est pas synchrone avec l'état «en duvet» du bouton, il ne reflète que l'état «en duvet» de la flèche déroulante.
Je crois que c'est la cause du problème dans cette question: lorsqu'une barre d'outils a le TBSTYLE_EX_DRAWDDARROWS
Le style étendu et ses boutons n'ont pas le BTNS_WHOLEDROPDOWN
Style, seule la partie de la flèche déroulante du bouton est déprimée lorsque son menu est lancé. Le bouton, en fait, est ne pas 'vers le bas'. Afaiu, vous voulez dessiner le bouton pressé Toutefois. Malheureusement, le VCL n'expose aucune propriété pour avoir les boutons «Wholedropdown».
Il est possible de définir ce style sur les boutons:
var
ButtonInfo: TTBButtonInfo;
i: Integer;
Rect: TRect;
begin
ButtonInfo.cbSize := SizeOf(ButtonInfo);
ButtonInfo.dwMask := TBIF_STYLE;
for i := 0 to ToolBar1.ButtonCount - 1 do begin
SendMessage(ToolBar1.Handle, TB_GETBUTTONINFO, ToolBar1.Buttons[i].Index,
LPARAM(@ButtonInfo));
ButtonInfo.fsStyle := ButtonInfo.fsStyle or BTNS_WHOLEDROPDOWN;
SendMessage(Toolbar1.Handle, TB_SETBUTTONINFO, ToolBar1.Buttons[i].Index,
LPARAM(@ButtonInfo));
end;
// Tell the VCL the actual positions of the buttons, otherwise the menus
// will launch at wrong offsets due to the separator between button face
// and dropdown arrow being removed.
for i := 0 to ToolBar1.ButtonCount - 1 do begin
SendMessage(ToolBar1.Handle, TB_GETITEMRECT,
ToolBar1.Buttons[i].Index, Longint(@Rect));
ToolBar1.Buttons[i].Left := Rect.Left;
end;
end;
Ensuite, la partie déroulante n'agira pas séparément du bouton, ou plus correctement, il n'y aura pas de partie déroulante distincte, donc l'état vers le bas / appuyé sur un bouton sera défini chaque fois que son menu sera lancé.
Mais en raison de la connaissance du VCL, l'état des boutons posera un problème; Chaque fois que le VCL met à jour les boutons, la réinstallation des styles serait nécessaire.