3

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

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

这意味着我最终绘制了一个非按下状态的按钮。

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

VCL 还通过私有 FTempMenu 执行下拉菜单,因此无法访问其句柄。

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

4

3 回答 3

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 的正确常数!这是获取常量的一种非常快速的方法。

  • 转到编译器设置,选中“使用调试 DCU 进行编译”
  • 打开 ComCtrls.pas,转到procedure TToolButton.Paint,在其中放置一个刹车点。
  • 启动应用程序,拿一张纸和一支笔。当程序在刹车点停止时,打开调试检查器。为此,只需将光标放在字段名称、任何字段上,然后点击Alt+ F5。使用 Debug Inspector 窗口点击Ctrl+以显示允许您检查任何内容N的通用编辑器。Inspect输入Integer(FToolbar)。记下纸上的结果。
  • 再次点击Ctrl+ N,这次输入Integer(@FToolBar.FMenuDropped)。请注意第二个数字。
  • 您需要的常数是第二个和第一个之间的差异。就是这样!

当然也有一些可能的问题。首先,这取决于您使用的确切Delphi 版本。如果代码需要在不同版本的Delphi编译器上编译,$IFDEF就需要使用clever。尽管如此,这是可行的。

(编辑):您可以使用相同的技术来访问任何类的任何 Private 字段。但是在这样做之前你需要考虑很多次,因为私有字段被设为私有是有原因的。

于 2011-03-30T15:54:37.967 回答
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 的新属性。

于 2012-04-02T04:20:43.117 回答
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”变量与按钮的“向下”状态不同步,它仅反映下拉箭头的“向下”状态。

我相信,这就是这个问题中问题的原因:当工具栏具有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 更新按钮时,都需要重新设置样式。

于 2011-03-30T16:27:01.320 回答