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.

Était-ce utile?

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. Entrer Integer(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.

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