如何知道TToolButton的MenuItem是否被删除?

在用于托管菜单条目的所有者绘制工具栏(具有其MenuItem和Grouped属性集的TToolButtons)的上下文中,我想知道是否删除了相应的menuitem。问题是OnAdvancedCustomDrawButton中的State属性不反映该信息。 单击工具按钮时,其Down属性为true,但在上面的特定情况下(MenuItem set和Grouped = True),在菜单被删除后,另一个OnAdvancedCustomDrawButton被触发,但这次将Down设置为false。 这意味着我最终绘制了一个NOT down状态的按钮。 查看VCL的来源,似乎有关删除哪个工具按钮的信息存储在TToolBar的FMenuButton专用字段中,并且通过Perform(TB_SETHOTITEM)通知Windows热状态,但这些都不提供读访问权限。 .. 此外,VCL通过私有FTempMenu执行下拉列表,因此无法访问其句柄。 PS:FWIW如果使用hacky解决方案,唯一可用的私有字段似乎是FButtonMenu,它必须与CustomDraw中的Button.MenuItem进行比较,其他私有fiel要么没有尽早设置(如FMenuButton),要么是私有的具有可变位置的变量(如MenuButtonIndex)。尽管如此仍然不太令人满意。     
已邀请:
获取菜单下拉状态是有问题的,使菜单弹出的代码非常复杂,使用了一些消息钩子。它通常不是您想要触摸的代码。幸运的是,工具栏本身使用
FMenuDropped
变量跟踪下拉菜单状态。不幸的是,变量是私有的,你不能从外部访问它,“黑客”技巧不起作用。私有它也不提供RTTI! 有两种可能的解决方案: 修改VCL并添加一个使FMenuDropped可从外部使用的属性 转到ComCtrls.pas,找到
TToolBar = class(TToolWindow)
声明,转到公共部分并添加:
property MenuDropped:Boolean read FMenuDropped;
从您的代码中,您可以检查工具栏是否有下拉菜单。不幸的是,它需要修改VCL。从来没有一个好主意,很难在几个程序员之间同步。 使用hack直接访问FMenuDropped字段,而无需更改VCL 为此,您需要获得
FMenuDropped
字段的偏移量。一旦你得到它,你可以写这样的东西:
if PBoolean(Integer(Toolbar1) + 865)^ then
   DoStuffIfMenuIsDropped
else
   OtherStuffIfMenuIsNotDropped;
865
实际上是Delphi 2010的正确常量!这是获得常数的一种非常快捷的方法。 转到编译器设置,检查“使用调试DCU编译” 打开ComCtrls.pas,转到
procedure TToolButton.Paint
,在那里放置制动点。 启动应用程序,拿一张纸和一支笔。当程序在制动点停止时打开Debug Inspector。为此,只需将光标放在字段名称,任何字段上,然后按Alt + F5。使用Debug Inspector窗口按Ctrl + N以显示允许您检查任何内容的通用
Inspect
编辑器。输入
Integer(FToolbar)
。注意纸上的结果。 再次按Ctrl + N,此时输入
Integer(@FToolBar.FMenuDropped)
。注意第二个数字。 你需要的常数是第二个和第一个之间的差异。而已! 当然有一些可能的问题。首先,这取决于您正在使用的确切Delphi版本。如果需要在不同版本的Delphi编译器上编译代码,则需要使用聪明的
$IFDEF
。尽管如此,这是可行的。 (编辑):您可以使用相同的技术访问任何类的任何私有字段。但是在执行此操作之前,您需要多次思考,因为私有字段因某种原因而变为私有字段。     
使用类助手。 例如。
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;
现在,无论您使用TToolBar,现在都可以访问名为MenuDropped的新属性。     
单击下拉按钮时,表单将发送
TBN_DROPDOWN
通知。这可用于跟踪启动菜单的按钮:
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;
  [...]
 
请注意,'OnAdvancedCustomDrawButton'中的'DroppedDown'变量与按钮的'Down'状态不同步,它只反映下拉箭头的'down'状态。 我相信这是问题的原因:当工具栏具有
TBSTYLE_EX_DRAWDDARROWS
扩展样式且其按钮没有
BTNS_WHOLEDROPDOWN
样式时,只有按钮的下拉箭头部分在其菜单启动时被按下。事实上,按钮不是“向下”。 AFAIU,你想要按下按钮即使这样。不幸的是,VCL没有公开任何属性以使按钮'wholedropdown'。 可以在按钮上设置此样式:
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;
然后下拉部分将不会与按钮分开操作,或者更准确地说,没有单独的下拉部分,因此每当其菜单启动时,将设置按钮的向下/按下状态。 但是由于VCL没有意识到按钮的状态会造成一个问题;每当VCL更新按钮时,都需要重新设置样式。     

要回复问题请先登录注册