Firemonkey - TPopUp memory issue - delphi

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;
....

Related

Delphi Firemonkey - can't add sub-subclass of TTabItem to TTabControl

I can add a TTabItem to a TTabControl, I can add a subclass of a TTabItem to a TabControl, but I can't add a sub-subclass of a TTabItem to a TabControl.
Example Firemonkey application - a form with a TTabControl:
type
TTabItem_subclass = class (TTabItem);
TTabItem_sub_subclass = class (TTabItem_subclass);
procedure TForm1.FormCreate(Sender: TObject);
procedure add_tab (t: TTabItem);
begin
t.Text := t.ClassName;
t.Parent := TabControl1
end;
begin
add_tab (TTabItem.create (TabControl1)); // <-- works
add_tab (TTabItem_subclass.create (TabControl1)); // <-- works
add_tab (TTabItem_sub_subclass.create (TabControl1)); // <-- fails
end;
When the application is run the TTabItem_sub_subclass doesn't display:
I've tried this in both XE5 and Tokyo with the same results. What am I missing?
Short answer: I don't think you are missing anything. If fact, your code does successfully add the sub-sub-classed item to the TabControl, it just doesn't get displayed. I think this problem is caused by a flaw in the way the FMX code derives the style to be used to paint a class which is a sub-sub-class of TTabItem. I don't know enough about FMX to idemtify the exact cause of the problem, but I have identified what seems to be a functional work-around.
Please see the code below of a sample project which successfully displays both
TabItem subClass and TabItem sub_subClass tabs.
The reason the code is structured as it is is to make it easy to set a changed-memory breakpoint on
the FResourceLink field of the TabItem (the variable Item in the code), while I was
trying to trace how the painting process occurs.
From watching the TabItem.Paint method, it was obvious that the tab would only paint
if its FResourceLink is not nil. The problem with your original code (and mine)
was that when Paint is called on TabItem_subClass, its FResourceLink has been assigned
a value whereas for TabItem_sub_subClass it has not. Evidently the FResourceLink
is where it picks up the name of the style used to paint the TabItem and if
it can't be found the TabItem doesn't get painted.
I'm afraid that as I'm no expert in FMX I find its code something of a labyrinth
at the best of times and its implementation of styles even more so. But it
struck me that if I could ensure that a valid style name is returned for
the TabItem GetParentClassStyleLookupName metod, that should suffice. That's the reason
for the TCustomItem_sub_subclass.GetParentClassStyleLookupName override. I imagine
an FMX expert might see it as a bit of a sledgehammer to crack a walnut, but there
you go.
Code
type
TForm1 = class(TForm)
TabControl1: TTabControl;
StyleObject1: TStyleObject; // ignore this
procedure FormCreate(Sender: TObject);
private
public
Item : TTabItem;
end;
[...]
implementation
[...]
type
TCustomItem_subclass = class (TTabItem)
public
constructor Create(AOwner : TComponent); override;
end;
TCustomItem_sub_subclass = class (TCustomItem_subclass)
public
constructor Create(AOwner : TComponent); override;
function GetParentClassStyleLookupName: string; override;
end;
procedure TForm1.FormCreate(Sender: TObject);
procedure add_tab (t: TTabItem);
begin
t.Text := t.ClassName;
t.Parent := TabControl1
end;
begin
{$define UseSubSub}
{$ifdef UseSubSub}
Item := TCustomItem_sub_subclass.Create(TabControl1);
{$else}
Item := TCustomItem_subclass.Create(TabControl1);
{$endif}
Item.Text := Item.ClassName;
Item.Parent := TabControl1;
Caption := TabControl1.ActiveTab.Text;
Item := TCustomItem_subclass.Create(TabControl1);
Item.Text := Item.ClassName;
Item.Parent := TabControl1;
end;
constructor TCustomItem_subclass.Create(AOwner: TComponent);
begin
inherited;
end;
constructor TCustomItem_sub_subclass.Create(AOwner: TComponent);
begin
inherited;
end;
function TCustomItem_sub_subclass.GetParentClassStyleLookupName: string;
begin
Result := 'tabitemstyle';
end;
Btw, in doing this I noticed what seems to be a lurking bug in the function
TStyledControl.GenerateStyleName(const AClassName: string): string in FMX.Controls.Pas'
If the AClassName argument, stripped of a leading TCustom, starts with a double-TT,
as in TCustomTabItem, the code incorrectly removes the T of TabItem. I didn't have
time or energy to explore this further but it's why my TabItem sub-classes omit
the Tab from their names.

Delphi XE7 AcroPDF (Acrobat Reader PDF ActiveX) component steals focus

When I open a pdf document using the AcroPDF component, AcroPDF steals the focus of my edit component (edSuchName)...
the only way that I found to get it back is the following, but I'm not really glad with it. So I'm searching for something better
TMainForm = class(TForm)
AcroPDF1: TAcroPDF;
tmrBringFocusBack: TTimer;
edSearchName: TEdit;
procedure tmrBringFocusBackTimer(Sender: TObject);
procedure OpenDocument;
end;
// ...
procedure TMainform.tmrBringFocusBackTimer(Sender: TObject);
begin
tmrBringFocusBack.Enabled := False;
edSearchName.SetFocus;
end;
procedure TMainform.OpenDocument;
begin
AcroPDF1.LoadFile(AFilename);
AcroPDF1.setShowToolbar(false);
AcroPDF1.setPageMode('none');
AcroPDF1.Show;
tmrBringFocusBack.Interval:= 200; // and sometimes more (when trying, 20 is too less)
tmrBringFocusBack.Enabled := True; // <<<< I have to trigger this timer <<<<
end;
Does anybody know a better way?

Creating a popup menu at runtime

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).

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