Drop down menu for TButton
Asked Answered
L

2

8

I am trying to simulate a drop down menu for a TButton, as shown below:

procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
  APoint: TPoint;
begin
  APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
  PopupMenu.Popup(APoint.X, APoint.Y);
end;

procedure TForm1.Button1MouseUp(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  if Button = mbLeft then
  begin
    DropMenuDown(Button1, PopupMenu1);
    // ReleaseCapture;
  end;
end;

The problem is that when the menu is dropped down, if I click the button again I would like the menu to close, but instead it drops down again.

I am looking for a solution specifically for generic Delphi TButton not any 3rd Party equivalent.

Lyndonlyndsay answered 15/5, 2012 at 9:28 Comment(0)
A
5

Following our (Vlad & I) discussion, you use a variable to know when the popup was last opened to choose if you display the popupmenu or cancel the mouse event:

unit Unit4;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls;

type
  TForm4 = class(TForm)
    PopupMenu1: TPopupMenu;
    Button1: TButton;
    fgddfg1: TMenuItem;
    fdgdfg1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    cMenuClosed: Cardinal;

  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
  APoint: TPoint;
begin
  APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
  PopupMenu.Popup(APoint.X, APoint.Y);
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
  DropMenuDown(Button1, PopupMenu1);
  cMenuClosed := GetTickCount;
end;

procedure TForm4.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) and not ((cMenuClosed + 100) < GetTickCount) then
  begin
    ReleaseCapture;
  end;
end;

procedure TForm4.FormCreate(Sender: TObject);
begin
  cMenuClosed := 0;
end;

end.
Anjanetteanjela answered 15/5, 2012 at 9:50 Comment(4)
Is the PopupListEx not an overkill here? We know that the menu is closed right after the DropMenuDown line (since the popup is synchronic), or did I missed something?Lyndonlyndsay
if you click on the button... then, you wait n seconds without doing nothing.... and then... you decide to press again the button... before pressing it, as you have done nothing... the popup is still opened? so, if you cMenuClosed := GetTickCount; just after the DropMenuDown(Button1, PopupMenu1); the case I just explain should not work...Anjanetteanjela
What I meant is this: procedure TForm1.Button1Click(Sender: TObject); begin DropMenuDown(Button1, PopupMenu1); cMenuClosed := GetTickCount; end; procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if (Button = mbLeft) and not ((cMenuClosed + 100) < GetTickCount) then begin ReleaseCapture; end; end;Lyndonlyndsay
Your answer gave me the right idea, so I will accept it :) Thank you.Lyndonlyndsay
O
6

After reviewing the solution provided by Whiler & Vlad, and comparing it to the way WinSCP implements the same thing, I'm currently using the following code:

unit ButtonMenus;
interface
uses
  Vcl.Controls, Vcl.Menus;

procedure ButtonMenu(Control: TControl; PopupMenu: TPopupMenu);

implementation

uses
  System.Classes, WinApi.Windows;

var
  LastClose: DWord;
  LastPopupControl: TControl;
  LastPopupMenu: TPopupMenu;

procedure ButtonMenu(Control: TControl; PopupMenu: TPopupMenu);
var
  Pt: TPoint;
begin
  if (Control = LastPopupControl) and (PopupMenu = LastPopupMenu) and (GetTickCount - LastClose < 100) then begin
    LastPopupControl := nil;
    LastPopupMenu := nil;
  end else begin
    PopupMenu.PopupComponent := Control;
    Pt := Control.ClientToScreen(Point(0, Control.ClientHeight));
    PopupMenu.Popup(Pt.X, Pt.Y);
    { Note: PopupMenu.Popup does not return until the menu is closed }
    LastClose := GetTickCount;
    LastPopupControl := Control;
    LastPopupMenu := PopupMenu;
  end;
end;

end.

It has the advantage of not requiring any code changes to the from, apart from calling ButtonMenu() in the onClick handler:

procedure TForm1.Button1Click(Sender: TObject);
begin
  ButtonMenu(Button1, PopupMenu1);
end;
Olaolaf answered 21/5, 2014 at 1:28 Comment(1)
This is the better and more generic solution. See also this answer. +1Vorous
A
5

Following our (Vlad & I) discussion, you use a variable to know when the popup was last opened to choose if you display the popupmenu or cancel the mouse event:

unit Unit4;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.Menus, Vcl.StdCtrls;

type
  TForm4 = class(TForm)
    PopupMenu1: TPopupMenu;
    Button1: TButton;
    fgddfg1: TMenuItem;
    fdgdfg1: TMenuItem;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  private
    { Private declarations }
    cMenuClosed: Cardinal;

  public
    { Public declarations }
  end;

var
  Form4: TForm4;

implementation

{$R *.dfm}

procedure DropMenuDown(Control: TControl; PopupMenu: TPopupMenu);
var
  APoint: TPoint;
begin
  APoint := Control.ClientToScreen(Point(0, Control.ClientHeight));
  PopupMenu.Popup(APoint.X, APoint.Y);
end;

procedure TForm4.Button1Click(Sender: TObject);
begin
  DropMenuDown(Button1, PopupMenu1);
  cMenuClosed := GetTickCount;
end;

procedure TForm4.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  if (Button = mbLeft) and not ((cMenuClosed + 100) < GetTickCount) then
  begin
    ReleaseCapture;
  end;
end;

procedure TForm4.FormCreate(Sender: TObject);
begin
  cMenuClosed := 0;
end;

end.
Anjanetteanjela answered 15/5, 2012 at 9:50 Comment(4)
Is the PopupListEx not an overkill here? We know that the menu is closed right after the DropMenuDown line (since the popup is synchronic), or did I missed something?Lyndonlyndsay
if you click on the button... then, you wait n seconds without doing nothing.... and then... you decide to press again the button... before pressing it, as you have done nothing... the popup is still opened? so, if you cMenuClosed := GetTickCount; just after the DropMenuDown(Button1, PopupMenu1); the case I just explain should not work...Anjanetteanjela
What I meant is this: procedure TForm1.Button1Click(Sender: TObject); begin DropMenuDown(Button1, PopupMenu1); cMenuClosed := GetTickCount; end; procedure TForm1.Button1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if (Button = mbLeft) and not ((cMenuClosed + 100) < GetTickCount) then begin ReleaseCapture; end; end;Lyndonlyndsay
Your answer gave me the right idea, so I will accept it :) Thank you.Lyndonlyndsay

© 2022 - 2024 — McMap. All rights reserved.