Creating a popup menu at runtime - delphi

I'm trying to simply create a popup menu (or context menu), add some items to it, and show it at the mouse location. All the examples I have found are doing this using the designer. I'm doing this from a DLL plugin, so there is no form/designer. The user will click a button from the main application which calls the execute procedure below. I just want something similar to a right click menu to appear.
My code obviously doesn't work, but I was hoping for an example of creating a popup menu during runtime instead of design time.
procedure TPlugIn.Execute(AParameters : WideString);
var
pnt: TPoint;
PopupMenu1: TPopupMenu;
PopupMenuItem : TMenuItem;
begin
GetCursorPos(pnt);
PopupMenuItem.Caption := 'MenuItem1';
PopupMenu1.Items.Add(PopupMenuItem);
PopupMenuItem.Caption := 'MenuItem2';
PopupMenu1.Items.Add(PopupMenuItem);
PopupMenu1.Popup(pnt.X, pnt.Y);
end;

You have to actually create instances of a class in Delphi before you can use them. The following code creates a popup menu, adds a few items to it (including an event handler for the click), and assigns it to the form. Note that you have to declare (and write) the HandlePopupItemClick event yourself like I've done).
In the interface section (add Menus to the uses clause):
type
TForm1 = class(TForm)
// Double-click the OnCreate in the Object Inspector Events tab.
// It will add this item.
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
// Add the next two lines yourself, then use Ctrl+C to
// generate the empty HandlePopupItem handler
FPopup: TPopupMenu;
procedure HandlePopupItem(Sender: TObject);
public
{ Public declarations }
end;
implementation
// The Object Inspector will generate the basic code for this; add the
// parts it doesn't add for you.
procedure TForm1.FormCreate(Sender: TObject);
var
Item: TMenuItem;
i: Integer;
begin
FPopup := TPopupMenu.Create(Self);
FPopup.AutoHotkeys := maManual;
for i := 0 to 5 do
begin
Item := TMenuItem.Create(FPopup);
Item.Caption := 'Item ' + IntToStr(i);
Item.OnClick := HandlePopupItem;
FPopup.Items.Add(Item);
end;
Self.PopupMenu := FPopup;
end;
// The Ctrl+C I described will generate the basic code for this;
// add the line between begin and end that it doesn't.
procedure TForm1.HandlePopupItem(Sender: TObject);
begin
ShowMessage(TMenuItem(Sender).Caption);
end;
Now I'll leave it to you to figure out how to do the rest (create and show it at a specific position).

Related

Firemonkey - TPopUp memory issue

I am facing a strange issue. I have set of buttons in a panel and I want to show tooltip for each button. For that I am using TPopUp, but whenever mouse enter, I can observe that memory is increasing for the application. But if I comment the mouse enter and mouse leave events then memory doesn't increase. Did I miss something?
Whenever the mouse enters the button, I can see 0.3MB increase in my task manager.
TfrmEncode = class(TForm)
pnlTop: TPanel;
btnSaveToJSON: TButton;
procedure FormCreate(Sender: TObject);
procedure btnSaveToJSONMouseEnter(Sender: TObject);
procedure btnSaveToJSONMouseLeave(Sender: TObject);
private
{ Private declarations }
pop : TPopup;
cb : TColorBox;
labelText: TLabel;
public
{ Public declarations }
end;
implementation
{$R *.fmx}
procedure TfrmEncode.btnSaveToJSONMouseEnter(Sender: TObject);
begin
Pop.IsOpen := True;
end;
procedure TfrmEncode.btnSaveToJSONMouseLeave(Sender: TObject);
begin
Pop.IsOpen := False;
end;
procedure TfrmEncode.FormCreate(Sender: TObject);
begin
try
pop := TPopup.Create(self);
pop.Parent:= self;
pop.Width:=200;
cb := TColorBox.Create(pop);
cb.Align := TAlignLayout.Client;
cb.Color := TAlphaColors.White;
pop.AddObject(cb);
labelText := TLabel.Create(pop);
labelText.Align :=TAlignLayout.alClient;
labelText.Parent := pop;
labelText.Text := 'This is the hint This is the hint This is the hint This is the hint This is the hint This is the hint This is the hint This is the hint This is the hint This is the hint';
pop.AddObject(labelText);
pop.PlacementTarget := btnSaveToJSON;
pop.Placement:=TPlacement.BottomCenter;
finally
end;
end;
procedure TfrmEncode.FormDestroy(Sender: TObject);
begin
FreeAndNil(pop);
end;
There is a bug in TPopup control. Reported as RSP-21438
TPopup internally creates new TCustomPopupForm every time popup is open. However, that form does not get released when popup is closed (as it should) but only when popup control itself is destroyed.
There are few workarounds
1. Create new TPopup control on open and free it on close
2. Fix FMX.Controls and FMX.Forms
Error can be fixed in implementation section of the above units. That means you can copy FMX.Controls and FMX.Forms into your project folder and Delphi will use those fixed units instead of default ones.
Fix following code:
FMX.Controls - change constructor parameter from False to True - it means popup form will be automatically released on close.
function TPopup.CreatePopupForm: TFmxObject;
...
NewForm := TCustomPopupForm.Create(Self, NewStyle, PlacementTarget, True);
FMX.Forms - assign AutoFree parameter to field.
constructor TCustomPopupForm.Create(AOwner: TComponent; AStyleBook: TStyleBook = nil; APlacementTarget: TControl = nil;
AutoFree: Boolean = True);
var
NewStyleBook: TStyleBook;
begin
FAutoFree := AutoFree;
....

cxgrid popup menu paste value of popupmenuitem

IN TMS string grid I used to use this to paste the caption of the popup menu into the grid's cell :
var
s:string;
begin
s:=(Sender as TmenuItem).Caption;
s:=stringReplace(s,'&','',[rfReplaceAll]);
with AdvStringGrid1 do
Cells[Col,Row]:=s;
I never used this before in a cxGrid so I am totally new to this. I have linked cxGridpopUpMenu1 to my grid,added a classic PopUpMenu so it gets used by the cxGridpopUpMenu1,added some items in the popup menu and thats it. popup menu fires on right click in the grid ok, but how do you paste the value of the menuitem into the cell??
+ Is there a way to assign popopmenu to a particular column ?
I'd do it like this:
procedure TForm1.MenuItem1Click(Sender: TObject);
var
s: string;
begin
Assert(Sender is TMenuItem);
s := StripHotKey(TMenuItem(Sender).Caption);
cxGrid1TableView1.DataController.Edit;
cxGrid1TableView1.Controller.FocusedColumn.EditValue := s;
end;
This can be done combining two event handlers:
The OnPopUp handler of your TcxGridPopupMenu.
An OnClick handler for all your popup menu items.
The idea is to use the OnPopup to store a reference to the item (column) and record clicked, while the OnClick would apply the value to the cell.
Code is as following:
//in private section of your form
fItem: TcxCustomGridTableItem;
fRec: TcxCustomGridRecord;
procedure TForm1.cxGridPopupMenu1Popup(ASenderMenu: TComponent;
AHitTest: TcxCustomGridHitTest; X, Y: Integer; var AllowPopup: Boolean);
begin
if AHitTest is TcxGridRecordCellHitTest then
begin
fItem := TcxGridRecordCellHitTest(AHitTest).Item;
fRec := TcxGridRecordCellHitTest(AHitTest).GridRecord;
end;
end;
procedure TForm1.MenuItem1Click(Sender: TObject);
var
s : string;
begin
s := (sender as tmenuItem).Caption;
gridView.DataController.Values[frec.Index, fitem.Index] := StripHotKey(s);
end;
As #DavidHeffernan suggested, notice the use of StripHotKey that removes the accelerator character mark from the menu caption.

Refer to an object instance and free it

If I create multiple TButton objects with this routine:
procedure CreateButton;
begin
Btn := TButton.Create(nil);
end;
Then, how can I refer to a specific object instance to free it using another method like:
procedure FreeButton;
begin
Btn[0].Free; //???
end;
Of course, this does not compile, but I think the question is clear: How do I declare Btn? And how do I free multiple instances?
It doesn't make much sense to create a TButton anywhere that isn't part of a form (which your code does).
With that being said, in order to refer to it later to free it, you need to store a reference to it somewhere.
Since you're referring to "multiple buttons" and using array code in your delete routine, I think you're probably wanting to track an array of buttons. Here's an example of doing just that:
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject); // Add via Object Inspector Events tab
private
{ Private declarations }
// Add these yourself
BtnArray: array of TButton;
procedure CreateButtons(const NumBtns: Integer);
procedure DeleteBtn(BtnToDel: TButton);
procedure BtnClicked(Sender: TObject);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.DeleteBtn(BtnToDel: TButton);
var
i: Integer;
begin
// Check each button in the array to see if it's BtnToDel. If so,
// remove it and set the array entry to nil so it can't be deleted
// again.
for i := Low(BtnArray) to High(BtnArray) do
begin
if BtnArray[i] = BtnToDel then
begin
FreeAndNil(BtnArray[i]);
Break;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
// Create 10 buttons on the form
CreateButtons(10);
end;
// Called when each button is clicked. Assigned in CreateButtons() below
procedure TForm1.BtnClicked(Sender: TObject);
begin
// Delete the button clicked
if (Sender is TButton) then
DeleteBtn(TButton(Sender));
end;
procedure TForm1.CreateButtons(const NumBtns: Integer);
var
i: Integer;
begin
// Allocate storage for the indicated number of buttons
SetLength(BtnArray, NumBtns);
// For each available array item
for i := Low(BtnArray) to High(BtnArray) do
begin
BtnArray[i] := TButton.Create(nil); // Create a button
BtnArray[i].Parent := Self; // Tell it where to display
BtnArray[i].Top := i * (BtnArray[i].Height + 2); // Set the top edge so they show
BtnArray[i].Name := Format('BtnArray%d', [i]); // Give it a name (not needed)
BtnArray[i].Caption := Format('Btn %d', [i]); // Set a caption for it
BtnArray[i].OnClick := BtnClicked; // Assign the OnClick event
end;
end;
If you put this code in a new blank VCL forms application and run it, you'll see 10 buttons ('Btn 0throughBtn 9`) on a form. Clicking on a button will remove it from the form (and the array).

Using joystick (gamepad) buttons in form even if window hidden in tray. Is it possible in Delphi?

Using the code below, or maybe modifying it, possible to achive my goal?
Or not by using this code, but it must be joystick buttons using when form is hidden in tray.
Thanks
type
TForm125 = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
HotKey1 : Integer;
procedure WMHotKey(var Msg: TWMHotKey); message WM_HOTKEY;
public
end;
var
Form125: TForm125;
implementation
{$R *.dfm}
procedure TForm125.FormCreate(Sender: TObject);
begin
HotKey1 := GlobalAddAtom('MyAppHotkey1');//create a unique value for identify the hotkey
if not RegisterHotKey(Handle, HotKey1, MOD_CONTROL, VK_F1) then //register the hotkey CTRL + F1
ShowMessage('Sorry can not register the hotkey');
end;
procedure TForm125.FormDestroy(Sender: TObject);
begin
UnRegisterHotKey(Handle, HotKey1);//unregister the hotkey
GlobalDeleteAtom(HotKey1);//remove the atom
end;
procedure TForm125.WMHotKey(var Msg: TWMHotKey);
begin
if Msg.HotKey = HotKey1 then
ShowMessage('Hello'); // do your stuff
end;
Sorry, this is a follow up on Chris' answer, but it seems OP needs a little more assistance.
I also believe that the use of a joystick component is the way to go.
For example, NLDJoystick. The installation instructions are included, as well as a mini manual.
You will need to follow these steps:
Place the component on your form,
Set Active to True (this won't succeed when there is no joystick attached),
Implement the OnButtonDown event, as follows:
procedure TForm1.NLDJoystick1ButtonDown(Sender: TNLDJoystick;
const Buttons: TJoyButtons);
begin
Beep;
end;
The TJoyButtons type is a set of JoyBtn1..JoyBtn32, so if you wish you can react to a specific button, or a combination of multiple pressed buttons:
procedure TForm1.NLDJoystick1ButtonDown(Sender: TNLDJoystick;
const Buttons: TJoyButtons);
begin
if JoyBtn1 in Buttons then Beep;
//or:
if Buttons = [JoyBtn1, JoyBtn2] then Beep;
end;
Note that if Advanced is False (the default setting) that there are only 4 buttons supported.
You can check the state of the buttons of your joystick(s) when you need to check them... if works even if the form is hidden:
uses ..., MMSystem;
const
iJoystick = 1; // ID of the joystick
var
myjoy : TJoyInfoEx;
begin
myjoy.dwSize := SizeOf(myjoy);
myjoy.dwFlags := JOY_RETURNALL;
if (joyGetPosEx(iJoystick, #myjoy) = JOYERR_NOERROR) then
begin
if (myjoy.wbuttons and joy_button1) > 0 then // you can do it for all the buttons you need
begin
ShowMessage('button 1 down');
end;
end;
end;
Eventually, you can create a timer which often checks their status to know if the status has change and trigger what you need...

Display a ToolTip hint on a disabled menu item of a popup menu

So I have a TMenuItem attached to a TAction on a TPopupMenu for a TDBGrid (actually 3rd party, but you get the idea). Based on the selected row in the grid, the TAction is enabled or disabled. What I want is to be able to display a hint to the user explaining why the item is disabled.
As far as why I want a hint on a disabled menu item, lets just say I am in agreement with Joel.
All TMenuItem's have a hint property, but as best I can tell they are only used the the TApplicationEvent.OnHint event handler to stick the hint in a TStatusBar or some other special processing. I found an article on how to create your own even window for a TMainMenu's TMenuItems, but it doesn't work on a TPopupMenu's TMenuItem. It works by handling the WM_MENUSELECT message, which as far as I can tell is not sent on a TPopupMenu.
WM_MENUSELECT is indeed handled for menu items in popup menus also, but not by the windows proc of the form containing the (popup) menu, but by an invisible helper window created by Menus.PopupList. Luckily you can (at least under Delphi 5) get at this HWND via Menus.PopupList.Window.
Now you can use the old-fashioned way to subclass a window, as described for example in this CodeGear article, to handle WM_MENUSELECT also for popup menus. The HWND will be valid from after the first TPopupMenu is created to before the last TPopupMenu object is destroyed.
A quick test with the demo app in the linked article in the question should reveal whether this is going to work.
Edit: It does indeed work. I changed the linked example to show hints also for the popup menu. Here are the steps:
Add a handler for OnDestroy, a member variable for the old window proc and a method for the new window proc to the form:
TForm1 = class(TForm)
...
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure ApplicationEvents1Hint(Sender: TObject);
private
miHint : TMenuItemHint;
fOldWndProc: TFarProc;
procedure WMMenuSelect(var Msg: TWMMenuSelect); message WM_MENUSELECT;
procedure PopupListWndProc(var AMsg: TMessage);
end;
Change the OnCreate handler of the form to subclass the hidden PopupList window, and implement the proper restoration of the window proc in the OnDestroy handler:
procedure TForm1.FormCreate(Sender: TObject);
var
NewWndProc: TFarProc;
begin
miHint := TMenuItemHint.Create(self);
NewWndProc := MakeObjectInstance(PopupListWndProc);
fOldWndProc := TFarProc(SetWindowLong(Menus.PopupList.Window, GWL_WNDPROC,
integer(NewWndProc)));
end;
procedure TForm1.FormDestroy(Sender: TObject);
var
NewWndProc: TFarProc;
begin
NewWndProc := TFarProc(SetWindowLong(Menus.PopupList.Window, GWL_WNDPROC,
integer(fOldWndProc)));
FreeObjectInstance(NewWndProc);
end;
Implement the subclassed window proc:
procedure TForm1.PopupListWndProc(var AMsg: TMessage);
function FindItemForCommand(APopupMenu: TPopupMenu;
const AMenuMsg: TWMMenuSelect): TMenuItem;
var
SubMenu: HMENU;
begin
Assert(APopupMenu <> nil);
// menuitem
Result := APopupMenu.FindItem(AMenuMsg.IDItem, fkCommand);
if Result = nil then begin
// submenu
SubMenu := GetSubMenu(AMenuMsg.Menu, AMenuMsg.IDItem);
if SubMenu <> 0 then
Result := APopupMenu.FindItem(SubMenu, fkHandle);
end;
end;
var
Msg: TWMMenuSelect;
menuItem: TMenuItem;
MenuIndex: integer;
begin
AMsg.Result := CallWindowProc(fOldWndProc, Menus.PopupList.Window,
AMsg.Msg, AMsg.WParam, AMsg.LParam);
if AMsg.Msg = WM_MENUSELECT then begin
menuItem := nil;
Msg := TWMMenuSelect(AMsg);
if (Msg.MenuFlag <> $FFFF) or (Msg.IDItem <> 0) then begin
for MenuIndex := 0 to PopupList.Count - 1 do begin
menuItem := FindItemForCommand(PopupList.Items[MenuIndex], Msg);
if menuItem <> nil then
break;
end;
end;
miHint.DoActivateHint(menuItem);
end;
end;
This is done for all popup menus in a loop, until the first matching item or submenu is found.
Not sure if it helps, but I have created my own multi-line hint window (for Delphi7) to be able to show more then just one line of text.
It's open source and you can find it here.
There is some work involved showing it on the right location on the screen, but you have full control over it.

Resources