在用于托管菜单条目的所有者绘制工具栏(设置了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)。但仍然不太令人满意。
答案 0 :(得分:2)
获取菜单下拉状态是有问题的,使菜单弹出的代码非常复杂,使用了一些消息钩子。它通常不是您想要触摸的代码。幸运的是,工具栏本身使用FMenuDropped
变量跟踪下拉菜单状态。不幸的是,变量是私有,你无法从外部访问它,“黑客”技巧不起作用。作为私人,它也不提供RTTI!
有两种可能的解决方案:
转到ComCtrls.pas,找到TToolBar = class(TToolWindow)
声明,转到公开部分并添加:
property MenuDropped:Boolean read FMenuDropped;
从代码中,您可以检查工具栏是否有下拉菜单。不幸的是,它需要修改VCL。从来没有一个好主意,很难在几个程序员之间同步。
要执行此操作,您需要获取FMenuDropped
字段的偏移量。一旦你得到它,你可以写这样的东西:
if PBoolean(Integer(Toolbar1) + 865)^ then
DoStuffIfMenuIsDropped
else
OtherStuffIfMenuIsNotDropped;
865
实际上是Delphi 2010的正确常量!这是非常快速获取常量的方法。
procedure TToolButton.Paint
,在那里放置制动点。Inspect
编辑器。输入Integer(FToolbar)
。注意纸上的结果。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更新按钮时,都需要重新设置样式。