如何知道 TToolButton 的 MenuItem 是否被下拉?

发布于 2024-10-27 11:15:07 字数 674 浏览 10 评论 0原文

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

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

这意味着我最终会绘制处于“未按下”状态的按钮。

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

此外,VCL 通过私有 FTempMenu 执行下拉菜单,因此其句柄不可访问。

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

In the context of an owner-draw toolbar used to host menu entries (TToolButtons with their MenuItem and Grouped properties set), I want to know if the corresponding menuitem is dropped. The problem is that the State property in the OnAdvancedCustomDrawButton doesn't reflect that information.

When the toolbutton is clicked, its Down property is true, but in the particular case above (MenuItem set and Grouped=True), just after the menu is dropped, another OnAdvancedCustomDrawButton is fired but this time with Down set to false.

This means that I end up drawing the button with a NOT down state.

Looking at the source of the VCL, it seems the information about which toolbutton is dropped is stored in the TToolBar's FMenuButton private field, and Windows is notified of the hot state by a Perform(TB_SETHOTITEM), however neither of these provide read-access...

Also the VCL performs the dropdown via a private FTempMenu, whose handle is thus not accessible.

PS: FWIW if using the hacky solution, the only private field usable seems to be FButtonMenu which will have to be compared against your Button.MenuItem in the CustomDraw, the others private fiels are either not set early enough (like FMenuButton) or are private variables (like MenuButtonIndex) with a variable location. Still not too satisfying though.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

南七夏 2024-11-03 11:15:07

获取菜单下拉状态是有问题的,弹出菜单的代码非常复杂,使用了一些消息挂钩。它通常不是您想要触及的代码。幸运的是,工具栏本身使用 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,转到程序 TToolButton.Paint,在其中放置一个制动点。
  • 启动应用程序,拿一张纸和一支笔。当程序在制动点停止时打开调试检查器。为此,只需将光标放在字段名称(任何字段)上,然后按 Alt+F5。在“调试检查器”窗口中,按 Ctrl+N 显示通用的 Inspect 编辑器,该编辑器允许您检查任何内容。输入整数(FToolbar)。将结果记在纸上。
  • 再次按 Ctrl+N,这次输入 Integer(@FToolBar.FMenuDropped)。请注意第二个数字。
  • 您需要的常数是第二个和第一个之间的差。 就是这样!

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

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

Getting the menu-dropped-down status is problematic, the code that makes the menu pop up is pretty convoluted, makes use of some message hooks. It's generally not the code you'd want to touch. Fortunately the Toolbar itself keeps track of the drop-down menu status, using the FMenuDropped variable. Unfortunately that variable is private, you can't access it from outside, the "hacked" trick doesn't work. Being private it also doesn't offer RTTI!

There are two possible solutions:

Modify the VCL and add a property that makes FMenuDropped available from outside

Go to ComCtrls.pas, find the TToolBar = class(TToolWindow) declaration, go to the public section and add this:

property MenuDropped:Boolean read FMenuDropped;

From your code you'll then be able to check the toolbar if it has a dropped down menu or not. The unfortunate part of this is that it requires modifications to the VCL. Never a good idea, difficult to synchronize amongst several programmers.

Use a hack to access the FMenuDropped field directly, without changing the VCL

To do this you need to get the offset of the FMenuDropped field. Once you get that you can write something like this:

if PBoolean(Integer(Toolbar1) + 865)^ then
   DoStuffIfMenuIsDropped
else
   OtherStuffIfMenuIsNotDropped;

The 865 is actually the correct constant for Delphi 2010! Here's a very quick way of getting the constant.

  • Go to compiler settings, check "compile using debug DCU's"
  • Open ComCtrls.pas, go to procedure TToolButton.Paint, place a brakepoint in there.
  • Start the application, take a piece of paper and a pen. When the program stops at the brakepoint open up Debug Inspector. To do that simply place the cursor on the name of a field, any field, and hit Alt+F5. With the Debug Inspector window hit Ctrl+N to show the generic Inspect editor that allows you to inspect anything. Enter Integer(FToolbar). Note the result on the piece of paper.
  • Hit Ctrl+N again, this time enter Integer(@FToolBar.FMenuDropped). Note this second number.
  • The constant you need is the difference between the second and the first. That's it!

There are of course some possible problems. First of all this depends on the exact Delphi version you're using. If the code needs to be compiled on different versions of the Delphi compiler, clever $IFDEF need to be used. None the less this is workable.

(Edit): You can use this same technique to access any Private field of any class. But you'll need to think many times before doing this, because private fields are made private for a reason.

阪姬 2024-11-03 11:15:07

使用班级助手。

例如。

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

Use a class helper.

For example.

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;

Now, anywhere you use a TToolBar, you can now access new property called MenuDropped.

太阳哥哥 2024-11-03 11:15:07

单击下拉按钮时,表单将发送 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 更新按钮时,就需要重新设置样式。

When a dropdown button is clicked, the form is sent a TBN_DROPDOWN notification. This can be used to track the button that launched a 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;
  [...]
 

Note that the 'DroppedDown' variable in 'OnAdvancedCustomDrawButton' is not synchronous with the 'Down' state of the button, it only reflects the 'down' state of the dropdown-arrow.

This, I believe, is the cause of the problem in this question: when a toolbar has the TBSTYLE_EX_DRAWDDARROWS extended style and its buttons do not have the BTNS_WHOLEDROPDOWN style, only the dropdown-arrow part of the button is depressed when its menu is launched. The button, in fact, is not 'down'. AFAIU, you want to draw the button pressed even so. Unfortunately the VCL does not expose any property to have the buttons 'wholedropdown'.

It is possible to set this style on the buttons:

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;

Then the dropdown part will not act separately from the button, or more correctly, there won't be a separate dropdown part, hence the down/pressed state of a button will be set whenever its menu is launched.

But due to the VCL being unaware of the state of the buttons will pose one problem; whenever the VCL updates the buttons, re-setting the styles would be necessary.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文