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

时间:2011-03-30 14:45:43

标签: delphi menu toolbar ownerdrawn

在用于托管菜单条目的所有者绘制工具栏(设置了MenuItem和Grouped属性的TToolButtons)的上下文中,我想知道是否删除了相应的menuitem。问题是OnAdvancedCustomDrawButton中的State属性不反映该信息。

单击工具按钮时,其Down属性为true,但在上面的特定情况下(MenuItem set和Grouped = True),在菜单被删除后,另一个OnAdvancedCustomDrawButton被触发,但这次将Down设置为false。

这意味着我最终用NOT状态绘制按钮。

查看VCL的来源,似乎有关哪个工具按钮被删除的信息存储在TToolBar的FMenuButton 私有字段中,并且通过执行(TB_SETHOTITEM)通知Windows热状态,但这些都没有提供读取权限......

此外,VCL通过私人FTempMenu执行下拉列表,因此无法访问其句柄。

PS: FWIW如果使用hacky解决方案,唯一可用的私有字段似乎是FButtonMenu,它必须与CustomDraw中的Button.MenuItem进行比较,其他私有字段要么不是设置得足够早(如FMenuButton)或者是具有可变位置的私有变量(如MenuButtonIndex)。但仍然不太令人满意。

3 个答案:

答案 0 :(得分:2)

获取菜单下拉状态是有问题的,使菜单弹出的代码非常复杂,使用了一些消息钩子。它通常不是您想要触摸的代码。幸运的是,工具栏本身使用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的正确常量!这是非常快速获取常量的方法。

  • 转到编译器设置,选中“使用debug 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。尽管如此,这是可行的。

(编辑):您可以使用相同的技术访问任何类的任何私有字段。但在执行此操作之前,您需要考虑许多次,因为私有字段因某种原因而变为私有字段。

答案 1 :(得分:1)

使用类助手。

例如。

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的新属性。

答案 2 :(得分:0)

点击下拉按钮后,表单会发送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样式时,只有下拉箭头部分当菜单启动时按下按钮。事实上,按钮'down'。 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更新按钮时,都需要重新设置样式。

相关问题