Continue of this topic:
I have wrote a generic code for DropDown memu with any TControl
, but for some reason it dose not work as expected with 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;
This works well with TButton
and with TSpeedButton
and with any TGraphicControl
(like TImage
or TSpeedButton
etc) as far as I can tell.
BUT does not work as expected with 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;
Seems like TPanel
is not respecting ReleaseCapture;
and not even Abort
in the event TDropDownMenuHandler.MouseDown
. What can I do to make this work with TPanel
and other controls? What am I missing?
Requirements
If I understand you correctly, then the requirements are:
Realize that, disregarding the implementation of requirement 1 for the moment, requirement 2 happens automatically: when you click outside a PopupMenu, the PopupMenu will close. This concludes to that the implementation of the first should not interfere with the second.
Possible solutions:
TPopupMenu.Popup
will not return until the PopupMenu is closed.The current implementation
OnClick
event of a Control:OnMouseDown
event of the control is assigned to a custom handler,OnClick
event),OnMouseDown
event handler is called,Note: a possibly already
OnMouseDown
event setting is not saved and gone!Why this works for a Button
A
TCustomButton
handles click events by responding to a by Windows sendCN_COMMAND
message. That is a specific WindowsBUTTON
sytem class control characteristic. By canceling the mouse capture mode, this message is not send. Thus the Control'sOnClick
event is not fired on the second click.Why this doesn't work for a Panel
A
TPanel
handles click events by adding thecsClickEvents
style to itsControlStyle
property. This is a specific VCL characteristic. By aborting execution, subsequent code due to theWM_LBUTTONDOWN
message is stopped. However, theOnClick
event of aTPanel
is fired somewhere down itsWM_LBUTTONUP
message handler, thus theOnClick
event is still fired.Solution for both
Use davea's answer on your other question wherein he simply does nothing if the saved time of the PopupMenu's closing was within the last 100 milliseconds.
It's not that
TPanel
is not respectingReleaseCapture
, it is that the capture is not relevant at all. This is what happens after the popup menu is launched and active, and the control is clicked once again:[csClicked]
.Granted I didn't trace a working example so I can't tell when and how
ReleaseCapture
is helpful. In any case, it can't help here.The solution I'd propose is a little different than the current design.
What we want is a second click to not to cause a click. See this part of the code:
The second click is in fact what closes the menu, before launching it again through the same handler. It is what causes the
PopupMenu.Popup
call to return. So what we can tell here is that the mouse button is clicked (either a left button or a double click), but not yet processed by the VCL. That means the message is yet in the queue.Remove the registration mechanism (mouse down handler hacking) with this approach, it is not needed, and the class itself as a result, and the globals.
Additionally this doesn't depend on processing timing.