问题描述
我想要按钮上方的弹出菜单:
Delphi包裹Win32菜单系统的方式似乎排除了基础Win32 API提供的不在模式中的每种模式或标志。那天VCL作者的大脑。 TPM_BOTTOMALIGN
就是一个这样的例子,可以将其传递到 TrackPopupMenu
中,但是Delphi包装器似乎不显示此内容。只有在库存的VCL中这是不可能的,但是通过不恰当地使用私有和受保护的方法,就不可能(至少在我看来是不可能的)在运行时准确地进行操作,或者通过重写来进行操作。 VCL组件TPopupMenu的设计也不是很好,因为它应该有一个名为 PrepareForTrackPopupMenu
的虚拟方法,除了调用 TrackPopupMenu
或 TrackPopupMenuEx
,然后允许某人重写实际调用该Win32方法的方法。但这为时已晚。也许Delphi XE5可以正确完成Win32 API的基本覆盖。
我尝试过的方法:
方法A:使用METRICS或Fonts:
准确确定弹出菜单的高度,以便在调用popupmenu.Popup(x,y)之前可以减去Y值。结果:必须处理Windows主题的所有变体,并做出我似乎无法确定的假设。似乎不太可能在现实世界中产生良好的结果。以下是基本字体指标方法的示例:
height:= aPopupMenu.items.count *(abs(font.height) + 6)+ 34;
您可以考虑隐藏项目,并且对于具有单个主题模式设置的Windows版本实际上,您可能会像那样接近,但并非完全正确。
方法B:让Windows做到:
尝试传递 TPM_BOTTOMALIGN
最终到达Win32 API调用 TrackPopupMenu
。
到目前为止,如果我修改VCL menus.pas,我想可以做到。我在此项目中使用Delphi 2007。我对这个想法并不满意。
这是我正在尝试的那种代码:
过程TMyForm.ButtonClick(Sender:TObject);
var
pt:TPoint;
popupMenuHeightEstimate:Integer;
开始
// las,如何准确地执行此操作,如何处理主题以及OnMeasureItem事件
//在运行时进行更改。
popupMenuHeightEstimate:= PopupMenuHeight(BookingsPopupMenu);
点X:= 0;
pt.Y:= -1 * popupMenuHeightEstimate;
pt:= aButton.ClientToScreen(pt); //为我做数学。
aPopupMenu.popup(pt.X,pt.Y);
结尾;
或者我想这样做:
点X:= 0;
pt.Y:= 0;
pt:= aButton.ClientToScreen(pt); //为我做数学。
aPopupMenu.popupEx(pt.X,pt.Y,TPM_BOTTOMALIGN);
当然,VCL中没有popupEx。没有比VCL家伙在1995年添加的
标志(版本1.0中的
)传递更多的
标志到 TrackPopupMenu
的方式了。
注意:我认为在显示菜单之前估算高度是不可能的,因此我们应该通过 TrackPopupMenu $ c $解决问题
更新:直接调用 TrackPopupMenu
不起作用,因为其余的VCL方法 TPopupMenu.Popup(x,y)
中的步骤对于我的应用程序绘制其菜单并使其看起来正确是必需的,但是无法调用它们没有邪恶的欺骗,因为它们是私有方法。修改VCL是一个令人毛骨悚然的提议,我也不希望这样。
有点黑,但是可能会解决它。
为覆盖弹出窗口的TPopupMenu声明一个拦截器类:
type
TPopupMenu = class(Vcl.Menus.TPopupMenu)
public
过程Popup(X,Y:Integer);覆盖
结尾;
过程TPopupMenu.Popup(X,Y:Integer);
const
标志:Word的数组[布尔值,TPopupAlignment] =
((TPM_LEFTALIGN,TPM_RIGHTALIGN,TPM_CENTERALIGN),
(TPM_RIGHTALIGN,TPM_LEFTALIGN,TPM_CENTERALIGN));
按钮:Word的array [TTrackButton] =(TPM_RIGHTBUTTON,TPM_LEFTBUTTON);
var
AFlags:整数;
开始
PostMessage(PopupList.Window,WM_CANCELMODE,0,0);
继承了;
AFlags:=标志[UseRightToLeftAlignment,Alignment]或
Buttons [TrackButton]或
TPM_BOTTOMALIGN或
(Byte(MenuAnimation)shl 10);
TrackPopupMenu(Items.Handle,AFlags,X,Y,0 {reserved},PopupList.Window,nil);
结尾;
技巧是将取消消息发布到菜单窗口中,以取消继承的TrackPopupMenu调用。
I want a popup menu above a button:
Delphi wraps the Win32 menu system in a way that seems to preclude every mode or flag that the underlying Win32 API provides that was not in the VCL author's brain on that day. One such example appears to be the TPM_BOTTOMALIGN
which can be passed into TrackPopupMenu
but, the Delphi wrapper appears to render this not only impossible in the stock VCL, but by injudicious use of private and protected methods, is impossible (at least seems to me to be impossible) to do either accurately at runtime, or by overrides. The VCL component TPopupMenu is not very well designed either, as it should have had a virtual method called PrepareForTrackPopupMenu
that did everything other than the call to TrackPopupMenu
or TrackPopupMenuEx
, and then allow someone to override a method that actually invokes that Win32 method. But that's too late now. Maybe Delphi XE5 will have this basic coverage of the Win32 API done right.
Approaches I have tried:
Approach A: Use METRICS or Fonts:
Accurately determine height of a popup menu so I can subtract the Y value before calling popupmenu.Popup(x,y). Results: Would have to handle all variants of Windows theming, and make assumptions that I seem unable to be sure about. Seems unlikely to result in good results in the real world. Here's an example of a basic font metrics approach:
height := aPopupMenu.items.count * (abs(font.height) + 6) + 34;
You can take into account hidden items, and for a single version of windows with a single theme mode setting in effect, you might get close like that, but not exactly right.
Approach B: Let Windows Do It:
Try to pass in TPM_BOTTOMALIGN
to eventually reach Win32 API call TrackPopupMenu
.
So far, i think I can do it, if I modify the VCL menus.pas.. I am using Delphi 2007 in this project. I am not all that happy about that idea though.
Here is the kind of code I am trying:
procedure TMyForm.ButtonClick(Sender: TObject);
var
pt:TPoint;
popupMenuHeightEstimate:Integer;
begin
// alas, how to do this accurately, what with themes, and the OnMeasureItem event
// changing things at runtime.
popupMenuHeightEstimate := PopupMenuHeight(BookingsPopupMenu);
pt.X := 0;
pt.Y := -1*popupMenuHeightEstimate;
pt := aButton.ClientToScreen(pt); // do the math for me.
aPopupMenu.popup( pt.X, pt.Y );
end;
Alternatively I wanted to do this:
pt.X := 0;
pt.Y := 0;
pt := aButton.ClientToScreen(pt); // do the math for me.
aPopupMenu.popupEx( pt.X, pt.Y, TPM_BOTTOMALIGN);
Of course, popupEx is not there in the VCL. Nor any way to pass in moreflags to TrackPopupMenu
than those that the VCL guys added probably in 1995,in version 1.0.
Note: I believe the problem of estimating the height before showing the menu is impossible, thus we should be actually having the problem solved by TrackPopupMenu
not by estimating the height.
Update: Calling TrackPopupMenu
directly does not work, because the rest of the steps in the VCL method TPopupMenu.Popup(x,y)
are necessary to invoke for my application to paint its menu and have it look correct, however it is impossible to invoke them without evil trickery because they are private methods. Modifying the VCL is a hellish proposition and I don't wish to entertain that either.
A little bit hacky, but it might solve it.
Declare an interceptor class for TPopupMenu overriding Popup:
type
TPopupMenu = class(Vcl.Menus.TPopupMenu)
public
procedure Popup(X, Y: Integer); override;
end;
procedure TPopupMenu.Popup(X, Y: Integer);
const
Flags: array[Boolean, TPopupAlignment] of Word =
((TPM_LEFTALIGN, TPM_RIGHTALIGN, TPM_CENTERALIGN),
(TPM_RIGHTALIGN, TPM_LEFTALIGN, TPM_CENTERALIGN));
Buttons: array[TTrackButton] of Word = (TPM_RIGHTBUTTON, TPM_LEFTBUTTON);
var
AFlags: Integer;
begin
PostMessage(PopupList.Window, WM_CANCELMODE, 0, 0);
inherited;
AFlags := Flags[UseRightToLeftAlignment, Alignment] or
Buttons[TrackButton] or
TPM_BOTTOMALIGN or
(Byte(MenuAnimation) shl 10);
TrackPopupMenu(Items.Handle, AFlags, X, Y, 0 { reserved }, PopupList.Window, nil);
end;
The trick is to post a cancel message to the menu window which cancels the inherited TrackPopupMenu call.
这篇关于您如何排列TPopupMenu,以使其准确地定位在按钮上方?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!