继续本主题:
我已经使用任何 TControl
为DropDown memu编写了一个通用代码,但由于某种原因,TPanel
无法按预期工作:
var
TickCountMenuClosed: Cardinal = 0;
LastPopupControl: TControl;
type
TDropDownMenuHandler = class
public
class procedure MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
end;
TControlAccess = class(TControl);
class procedure TDropDownMenuHandler.MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if LastPopupControl <> Sender then Exit;
if (Button = mbLeft) and not ((TickCountMenuClosed + 100) < GetTickCount) then
begin
if GetCapture <> 0 then SendMessage(GetCapture, WM_CANCELMODE, 0, 0);
ReleaseCapture;
// SetCapture(0);
if Sender is TGraphicControl then Abort;
end;
end;
procedure RegisterControlDropMenu(Control: TControl; PopupMenu: TPopupMenu);
begin
TControlAccess(Control).OnMouseDown := TDropDownMenuHandler.MouseDown;
end;
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
begin
LastPopupControl := Control;
RegisterControlDropMenu(Control, PopupMenu);
APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.PopupComponent := Control;
PopupMenu.Popup(APoint.X, APoint.Y);
TickCountMenuClosed := GetTickCount;
end;
据我所知,这适用于TButton
和TSpeedButton
以及任何TGraphicControl
(例如TImage
或TSpeedButton
等)。< / p>
但是TPanel
procedure TForm1.Button1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1);
end;
procedure TForm1.Panel1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1); // Does not work!
end;
procedure TForm1.SpeedButton1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1);
end;
procedure TForm1.Image1Click(Sender: TObject);
begin
DropMenuDown(Sender as TControl, PopupMenu1);
end;
在TPanel
事件中,ReleaseCapture;
似乎不尊重Abort
,甚至不尊重TDropDownMenuHandler.MouseDown
。我可以做些什么来使用TPanel
和其他控件?我错过了什么?
答案 0 :(得分:6)
并非TPanel
不尊重ReleaseCapture
,而是捕获根本不相关。弹出菜单启动并激活后会发生这种情况,再次单击控件:
[csClicked]
内设置一个标记。当然,我没有追查一个有效的例子,所以我无法告诉ReleaseCapture
何时以及如何有用。无论如何,它在这里都无济于事。
我提出的解决方案与当前设计略有不同。
我们想要的是第二次点击以不导致点击。看到代码的这一部分:
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
begin
...
PopupMenu.PopupComponent := Control;
PopupMenu.Popup(APoint.X, APoint.Y);
TickCountMenuClosed := GetTickCount;
end;
第二次点击实际上是关闭菜单的内容,然后再次通过相同的处理程序启动它。这是导致PopupMenu.Popup
调用返回的原因。所以我们在这里可以看出,单击鼠标按钮(左键或双击),但VCL尚未处理。这意味着消息仍然在队列中。
使用这种方法删除注册机制(鼠标向下处理程序黑客攻击),它不需要,并且类本身就是结果,而且是全局的。
procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
APoint: TPoint;
Msg: TMsg;
Wnd: HWND;
ARect: TRect;
begin
APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
PopupMenu.PopupComponent := Control;
PopupMenu.Popup(APoint.X, APoint.Y);
if (Control is TWinControl) then
Wnd := TWinControl(Control).Handle
else
Wnd := Control.Parent.Handle;
if PeekMessage(Msg, Wnd, WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, PM_NOREMOVE) then begin
ARect.TopLeft := Control.ClientOrigin;
ARect.Right := ARect.Left + Control.Width;
ARect.Bottom := ARect.Top + Control.Height;
if PtInRect(ARect, Msg.pt) then
PeekMessage(Msg, Wnd, WM_LBUTTONDOWN, WM_LBUTTONDBLCLK, PM_REMOVE);
end;
end;
此外,这并不取决于处理时间。
答案 1 :(得分:1)
如果我理解正确,那么要求是:
意识到,暂时忽略要求1的实现,要求2自动发生:当您在PopupMenu外部单击时,PopupMenu将关闭。结论是第一个的实现不应该干扰第二个。
可能的解决方案:
TPopupMenu.Popup
在PopupMenu关闭之前不会返回的事实。OnClick
事件期间:
OnMouseDown
事件被分配给自定义处理程序OnClick
事件期间),OnMouseDown
事件处理程序注意:可能已经存在OnMouseDown
事件设置并且已经消失了!
TCustomButton
通过响应Windows发送CN_COMMAND
消息来处理点击事件。这是一个特定的Windows BUTTON
系统类控件特性。通过取消鼠标捕获模式,不会发送此消息。因此,第二次单击时不会触发Control的OnClick
事件。
TPanel
通过将csClickEvents
样式添加到其ControlStyle
属性来处理点击事件。这是一个特定的VCL特性。通过中止执行,将停止由WM_LBUTTONDOWN
消息引起的后续代码。但是,OnClick
的{{1}}事件会在其TPanel
消息处理程序的某处触发,因此WM_LBUTTONUP
事件仍会被触发。
在davea's answer上使用your other question,如果PopupMenu关闭的保存时间在最后100毫秒内,他就什么都不做。