How to capture the active control in Firemonkey? - delphi

I am migrating a VCL application to FMX. I need to know the class of a control that has focus. The application works with various dynamically created frames with numerous input controls.
In VCL I use the VCL.Forms.TScreen.OnActiveControlChange as this is the one place to consistently capture the active control. This event is not available in FMX.Forms.TScreen. What would be an alternative approach in FMX?

The most similar approach in FMX would be to listen to the TForm.OnFocusChanged event. From within the event handler you could then look up theTForm.Focused property.

Sadly in Delphi 10.3, the approach outlined by #iamjoosy fails spectacularly with an 'Access Violation' under certain circumstances (especially when TabControls / TabItems are used as containers for other controls).
The code I am using:
procedure TForm1.FormFocusChanged(Sender: TObject);
var
Control : iControl;
MyControl : TFMXObject;
begin
Control := form1.focused;
try
MyControl := TFmxObject(Control.GetObject);
form1.Caption := MyControl.Name + ' of type ' + MyControl.ClassName;
finally
MyControl := nil;
Control := nil;
end;
end;
Yet to add some intrigue, the above approach reverts to working fine if:
1) There's no TabControl/TabItem objects
2) If I add the following event handler to each child button (e.g. setting focus back to its parent TabItem):
procedure TForm1.Button2Click(Sender: TObject);
begin
TabItem1.SetFocus;
end;
Hoping someone can offer advice as to whether I'm doing something stupid, or whether I've run into an FMX bug.

Related

wsMaximized forms do not appear maximized

Setting a form to WindowState = wsMaximized will sometimes cause the form to be maximized but not:
Long-time bug: this is a question I first asked in the Borland newsgroups in 2003:
Accepted fix for WindowState = wsMaximized?
and then again in 2006:
wsMaximized breaks it, NOT caused by Position=poScreenCenter, reproducible dfm
and then again in 2008:
Forms not starting maximized
Someone asked it on the Embarcadero forums in 2012:
Thread: Application not starting with maximized window
Now it's time to port the 18 year old bug to Stackoverflow. Maybe someone's finally figured out a workaround.
Steps to reproduce:
My posts contained half a dozen failure modes, but the easiest is:
Drop a Label and an Edit on a form:
Add an OnEnter event for the TEdit:
procedure TForm1.Edit1Enter(Sender: TObject);
begin
Label1.Font.Style := Label1.Font.Style + [fsBold];
end;
and set the form:
WindowState to wsMaximized
AutoScroll to False
And bazinga, fails.
One of the other set of steps from the 2008 post:
Create a new app and a form.
Set the form to maximized (WindowState = wsMaximized) at design time.
Drop a ListView control on the form
During OnShow, add 20 empty items to the list view:
procedure TForm1.FormShow(Sender: TObject);
var
i: Integer;
begin
for i := 1 to 20 do
ListView1.Items.Add;
end;
Set the form's AutoScroll property to false (AutoScroll = False) at design time
Of course what I'm not after is "fixed in version n of RadStudio. Just use that". I'm looking for an actual fix (if there is one); which could include quoting relevant changes to the VCL source when CodeGear finally did fix it. (If it is even fixed).
Note: Changing Position from poDesigned to anything else doesn't fix it.
Workaround
A horrible, ugly, awful, disgusting, workaround I had been using was to start a timer during OnShow, and then when the timer fires, maximize the form:
procedure TForm1.tmrVclMaximizeHackTimer(Sender: TObject);
begin
Self.WindowState := wsMaximized;
end;
I later improved this hack to post a message during OnShow; which is essentially the same as a timer message, without having to use a timer:
const
WM_MaximizeWindow = WM_APP + $03;
procedure TForm1.FormShow(Sender: TObject);
begin
if (Self.WindowState = wsMaximized) then
begin
Self.WindowState := wsNormal;
PostMessage(Self.Handle, WM_MaximizeWindow , 0, 0);
end;
end;
private
procedure WMMaximizeWindow(var Message: TMessage); message WM_MaximizeWindow;
procedure TForm1.WMMaximizeWindow(var Message: TMessage);
begin
Self.WindowState := wsMaximized;
end;
Sometimes I invent the OnAfterShow event that Delphi never did:
const
WM_AfterShow = WM_APP + $02;
procedure TForm1.FormShow(Sender: TObject);
begin
PostMessage(Self.Handle, WM_AfterShow, 0, 0);
if (Self.WindowState = wsMaximized) then
begin
Self.WindowState := wsNormal;
FMaximizeNeeded := True;
end;
end;
private
procedure WMAfterShow(var Message: TMessage); message WM_AfterShow;
procedure TForm1.WMAfterShow(var Message: TMessage);
begin
if FMaximizeNeeded then
begin
FMaximizeNeeded := False;
Self.WindowState := wsMaximized;
end;
end;
But no hacks are better than hacks.
I Can reproduce with D7/Win7.
I don't use wsMaximized at all (similar random problems as you describe).
Workaround: use OnActivate -> ShowWindow(Handle, SW_MAXIMIZE) e.g.:
procedure TForm1.FormActivate(Sender: TObject);
begin
// Maximize only once when the Form is first activated
if not FMaxsimized then
begin
FMaxsimized := True;
ShowWindow(Handle, SW_MAXIMIZE);
end;
end;
This method will not work during OnShow.
Better Workaround: use ShowWindowAsync during OnShow or OnCreate e.g:
procedure TForm1.FormCreate(Sender: TObject);
begin
ShowWindowAsync(Handle, SW_MAXIMIZE);
end;
This sets the show state of a window without waiting for the operation to complete.
I only tested the first reproduction case (with D7, D2007, XE2), and am able to duplicate the problem with D7 and D2007 but not with XE2.
The problem, as I see it, is that the label, having its font changed, requests its parent to re-align itself. This eventually leads to a SetWindowPos call on the form (in TWinControl.AdjustSize) with restored width/height even though the form is already maximized - which leads to the strange, behaviorally maximized but not visually maximized, form sitting on the screen.
I traced the code in D2007 and XE2 to be able to come up with what is different. The code in TWinControl.AlignControls is different between the two versions. What specifically matters is the last statement.
D2007:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
..
{ Apply any constraints }
if Showing then AdjustSize;
end;
XE2:
procedure TWinControl.AlignControls(AControl: TControl; var Rect: TRect);
..
// Apply any constraints
if FAutoSize and Showing then
DoAdjustSize;
end;
I hope this, somehow, helps you devising/deciding what workaround to use.
The workaround I could suggest (although I haven't tested it throughly) is to force show the form maximized early:
procedure TForm1.FormCreate(Sender: TObject);
var
wplc: TWindowPlacement;
begin
if not AutoScroll and (WindowState = wsMaximized) then begin
wplc.length := SizeOf(wplc);
GetWindowPlacement(Handle, #wplc);
wplc.rcNormalPosition.Right := wplc.rcNormalPosition.Left + Width;
wplc.rcNormalPosition.Bottom := wplc.rcNormalPosition.Top + Height;
wplc.showCmd := SW_MAXIMIZE;
SetWindowPlacement(Handle, #wplc);
end;
end;
The above works because it forces to set the focus to the edit control (OnEnter event) before the VCL sets the visible flag for the form. In turn, the label's alignment request does not result with form size adjustment. Also, since, by the time VCL calls ShowWindow the form's window is already visible, it doesn't cause the form to be shown in a restored state at any stage.
However, I don't know if it would help with different reproduction scenarios.
Finally, although I can see that the behavior is corrected in newer Delphi versions, I wouldn't consider this to be a bug in the VCL. In my opinion, user code should be responsible not to cause window adjustment while window showing state is changing. The course of action I'd take for the specific scenario would be to defer to modify label's font until the VCL is done displaying the form.
I don't think this is a bug in Delphi but rather a bug (or just odd behavior) in the Windows CreateWindow function. If you search for CreateWindow and WS_MAXIMIZE not working you'll find similarly very old threads and discussions from people calling CreateWindow or CreateWindowEx passing WS_MAXIMIZE in the style parameter and not seeing a maximized window when they run the application.
Excerpt from an old gamedev.net thread
the problem is that WS_MAXIMIZE apparently does not apply when using WS_OVERLAPPEDWINDOW. if you replace WS_OVERLAPPEDWINDOW with WS_POPUP you will get a maximized window. of course this may not apply to all versions of Windows, or even all versions of the Windows shell UI for that matter.
WS_OVERLAPPEDWINDOW is MS's old default window "type" and they apparently coded CreateWindow/Ex to ignore certain styles, thinking that ShowWindow would be called with SW_SHOWDEFAULT, which causes the window to be displayed according to the CreateProcess startup info parms. this ultimately gives the user the control of how an app's main window would be displayed by using the shell's shortcut settings.
The workaround is just to call ShowWindow. It should work in Delphi, too:
procedure TForm1.FormShow(Sender: TObject);
begin
ShowWindow(Handle, SW_MAXIMIZE);
end;
Hope the solution i use helps others (i known window is first shown with design time size):
Add a timer with interval as less as just 1 (do not put 0).
Code for it: theTimer.Enabled:=False;WindowState:=wsMaximized;
It never fail to me.
As soon as form is shown and all pending tasks for such show are finished, the timer triggers and window is maximized.
Some time ago, i was using the Trick of sending a mouse click where maximize button was, but i discovered checking Windows OS version, plugins (on Linux), etc makes the thing so hard. That one worked exactly as if the user asks for maximize the window.
The Timer i now use does exactly the same, but avoid OS checking, etc.
Not to mention: Put WindowState to wsNormal on DesignTime (do not set it to wsMinimized, neither to wsMaximized).
Wow! i did not see on the post:
ShowWindowAsync(Handle,SW_MAXIMIZE);
Thanks for that, with it my problem is solved much better than with a Timer, but not perfect, it still causes a little flicker (window is shown on incomplete render, then it goes to maxized state), it is much better than the timer, less time shown in non maximized state.
And it is compatible with a prior SetBounds() on the OnShow method.
I wish: Set initial size (Width, Height) and maybe also initial position (Left, Top) of a form prior to show it, but that form must be shown maximized; such position and sizes are for when the user un-maximize the window.
That ShowWindowAsync works perfect for such objective, and no need to add an ugly timer (with interval=1 and .Enabled=False on its code as first sentence).
How could i miss it when i read the post!
So, now on i will use (just as example os initial size relative to monitor):
procedure TtheForm.FormShow(Sender: TObject);
var
theInitialDefaultWidth,theInitialDefaultHeight:Integer;
begin
theInitialDefaultWidth:=Round(Screen.Width*3/5);
theInitialDefaultHeight:=Round(Screen.Height*3/5);
WindowState:=wsNormal; // So it can still have at design time wsMaximized, this is for the SetBounds to work on a non maximized state
SetBounds((Screen.Width-theInitialDefaultWidth)div 2,(Screen.Height-theInitialDefaultHeight)div 2,theInitialDefaultWidth,theInitialDefaultHeight); // Set default position and default size as i wish
ShowWindowAsync(Handle,SW_MAXIMIZE); // Make the window to be shown maximized when it will be visible
// ... // Rest of actions for the FormShow method
end;
Works perfect! I do not need to touch design time properties, i can let them as they are (WindowState=wsMaximized, Position=poScreenCenter, etc).. 100% code solution for the problem.
Thanks a lot!
P.D.: Will it work on Linux? I mean when code is compiled for Linux (in Lazarus), i must test it and see, if it does work it will be a great immprove on what i was using till now.

TJvDockServer and dockable controls

I'm programming in Delphi (BDS 2006) and the JVCL library, using the docking modules. I have one problem - if the control has properties DragKind = dkDock and DragMode = dmAutomatic, then inexplicably TJvDockServer component takes the controls are both clients and provides docking. This is wrong, because, as I found out, JVCL's docking functions normally only control class TForm which contain a component class TJvDockClient. I would like to know whether it is possible in some way to prevent TJvDockServer from docking controls whose class is different from TForm? During a typical docking in Delphi for each event is called OnGetSiteInfo dock and it is possible to filter clients, but there is no such event in TJvDockServer.
The property DragKind and DragMode are standard VCL properties. Docking is built into the VCL, and from looking at it, it seems to work pretty good without any Jedi Code involved.
The ability to dock something other than a form, is already built into the VCL. Therefore that you find this inexplicable suggests to me that you thought Jedi added docking to the VCL. No, it just added some pretty things like "tabbed notebook docking" and "conjoined areas" with fake window titlebars.
That being said, Forms are also inheriting from TCustomControl, and any TCustomControl can in fact, be docked. And just like the VCl lets you drag and dock and land on top of TPanels. Okay it's a quirky feature, that your panel can turn into a form on you at runtime, but if you don't believe me, try it. It's the VCL doing this to you, not Jedi.
If in your wisdom, you want to block anything that is not a TForm, I thought that you can.
Surely you can right? Update. Yes you can. OnDockOver works fine to block docking on any panel you want to block docking on. The trick with the Jedi JvDockPanels is that you don't see them at designtime, so you need to access their events by hooking them up in code, at runtime.
Just like regular TPanels, JvDockPanels have a TPanel.OnDockOver event, and if you want to check the thing you're docking, and set the Accept to false, it will be prevented from docking.
Okay, this works:
type
TCustomControlAccess = class(TCustomControl);
procedure TMainForm.FormCreate(Sender: TObject);
begin
TCustomControlAccess(dockServer.TopDockPanel).OnDockOver := MyDockOverEvent;
TCustomControlAccess(dockServer.CustomDockPanel).OnDockOver := MyDockOverEvent;
...
end;
The JvDockPanel.OnDockOver panel events DO fire, but you need to resort to a hack like the above hack, to actually handle the events yourself.
Update previously thought there was no way to block this. But I was wrong. Figured it out.
while i cannot reproduce exactly your behaviour in Delphi XE2, generally i seem manage to block VCL-frag-n-drop for JediVCL components.
Maybe it is not the best possible way, but i don't know which were original ideas of the framework creator.
http://wiki.delphi-jedi.org/wiki/JVCL_Help:TJvDockServer claims only forms should be docked. Did not enforced that, just hardwired JVCL check routine to be always called.
unit JvDockSupportControl;
....
TJvDockCustomControl = class(TJvCustomControl)
....
protected
procedure GetSiteInfo(Client: TControl; var InfluenceRect: TRect;
MousePos: TPoint; var CanDock: Boolean); override;
...........
function TJvDockCustomControl.GetJvDockManager: IJvDockManager;
begin
// Result := IJvDockManager(DockManager);
DockManager.QueryInterface(IJvDockManager, Result);
end;
procedure TJvDockCustomControl.GetSiteInfo(Client: TControl; var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);
var jdm: IJvDockManager; idm: IDockManager;
begin
idm := DockManager;
if nil <> idm then
idm.QueryInterface(IJvDockManager, jdm);
if nil = jdm
then CanDock := false
else jdm.GetSiteInfo(Client,InfluenceRect, MousePos, CanDock);
end;
unit JvDockTree;
.....
procedure TJvDockTree.GetSiteInfo(Client: TControl;
var InfluenceRect: TRect; MousePos: TPoint; var CanDock: Boolean);
begin
CanDock := IsDockable(DockSite, Client);
If CanDock then begin
GetWindowRect(DockSite.Handle, InfluenceRect);
InflateRect(InfluenceRect, DefExpandoRect, DefExpandoRect);
end;
end;
http://issuetracker.delphi-jedi.org/view.php?id=5271
http://issuetracker.delphi-jedi.org/view.php?id=5974

Proper handling of modal window "Minimize" behavior

I have the following issue: we're building a rather large application (win32, Delphi 6 Enterprise). In several part of the application, modal windows are used, usually containing the detail of the selection of the main window.
We included a modification of the handling of the WM_SYSCOMMAND messages so that, if the window is modal, then a SW_SHOWMINNOACTIVE message will be sent to the application's main window. This causes the whole application to be minimized instead of just the modal form.
There is, however, an issue happening in a specific case: if the calling window is set to full screen, then upon restoration, the modal window will appear UNDER the (disabled) maximized main window (this seems to happen on Windows 7)
My problem is two fold:
First, I don't seem to get any syscommand message when the application is restored any more so I cannot introduce code to restore the Z-Order because I don't know where to put it.
Second, it seems to me that, if the whole application is minimized, clicking on the app's button in the task bar should restore it in the same state, not with a modal window under it. Is there a way to fix that ?
Edit: we did some additional testing and it seems we can actually detect the problem in the WM_ACTIVATE handler for the main form. We can also identify the modal window at that stage. I cannot, however, find a way to restore it to the top of the Z-Order.
Edit2: here is the code that minimizes the application when the modal form is minimized:
procedure TfmGITForm.WMSysCommand(var Message: TWMSysCommand);
begin
if (fsModal in FormState) or
not Application.MainForm.Visible then
begin
case Message.CmdType of
SC_MINIMIZE:
begin
ShowWindow(Application.Handle, SW_SHOWMINNOACTIVE);
end;
SC_RESTORE:
begin
ShowWindow(Application.Handle, SW_SHOWNORMAL);
inherited;
end;
else
inherited;
end; // case
end
else
inherited;
end;
All our forms descend from that one.
Override the dialog's CreateParams function and set Params.WndParent to the full-screen window (or Owner.Handle if you're owning things properly). The default is Application.Handle, which will cause these kinds of problems. The PopupParent properties introduced in the later Delphi releases does the exact same thing.
This has to do with the Window ghosting by Windows which was introduced in (I think) XP. I have the same issues in a D5 app on these operating systems. Peter Below offered the following work around at the time and it still serves me well:
procedure DisableProcessWindowsGhosting;
type
TDisableProcessWindowsGhostingProc = procedure; stdcall;
const
sUser32 = 'User32.dll';
var
ModH: HMODULE;
_DisableProcessWindowsGhosting: TDisableProcessWindowsGhostingProc;
begin
ModH := GetModuleHandle(sUser32);
if ModH <> 0 then begin
#_DisableProcessWindowsGhosting := nil;
#_DisableProcessWindowsGhosting := GetProcAddress(ModH,
'DisableProcessWindowsGhosting');
if Assigned(_DisableProcessWindowsGhosting) then begin
_DisableProcessWindowsGhosting;
end;
end;
end;
I call it at the beginning of the app's main form's OnCreate handler.

How to avoid the focus on Delphi application?

I need develop an application (Delphi) that never receive the focus, I use the DLL to display the video on second monitor (I found in Torrys Delphi - Dr.SAGURA Media Player v.1.0) that receive the focus every time play the video, how avoid this ?
I try :
procedure TForm.WMActivate(var Msg: TWMActivate);
begin
WA_ACTIVE :
begin
Msg.Result := 0;// cancel focus
end;
end;
Unsuccessful !
Thanks
Jean Alysson
In your project DPR set Application.MainFormOnTaskBar := False.
Override the form's OnShow and OnActivate events and add this to both of them: ShowWindow(Application.Handle, SW_HIDE);
Override your form's CreateParams procedure and add WS_EX_NOACTIVATE to Params.ExStyle.
The first two items hide the application from the taskbar and the Alt+Tab list and the last keeps it from gaining focus when it's shown and when clicking on it.
What's the intention behind not be focusable? You want to avoid the user from closing/minimize/maximize the window?
Maybe your need can be acomplished understanding the real problem.
Maybe having a sevice instead of a regular application can make the trick.

Under what conditions will a TForm fire OnResize on show?

As an extension of this question:
TForm.OnResize is sometimes fired before a form is first shown, but not always. For example, if BorderStyle is either bsDialog or bsNone, then OnResize will not fire. For all other BorderStyle values (and with all other properties at their defaults), OnResize does fire.
Are there other things that affect whether OnResize will fire before the form is shown? For example, other properties, or combinations of properties, that can affect this?
The OnResize event is a result of the ShowWindow API function sending a WM_SIZE message to the window. That bears repeating: the message is coming from Windows, not from Delphi. It's a Windows function (ShowWindow) that's (sometimes) sending the message that triggers the event -- so the VCL source code is not really helpful in this case.
Bonus points for definitive answers based on documented ShowWindow / WM_SIZE behavior, e.g. references to MSDN documentation or Petzold books.
Maybe it even depend on user's display settings or desktop theme or Windows version. If OnResize were giving me problems like this, I would build my program to always expect it and handle it in any situation, no matter what I think to be the cause.
I believe that OnResize will fire when an event dispatch a message
saying that form size (left, bottom, width, height) will be modified.
Since you already discovered which message fires that event, you need
now trace where the message is sent in the vcl.
Look at the vcl source code to see if you can spot those operations.
Edit: let's go low level. Forms in windows (grossly talking) have what
is called "window class" (it's not a class like we know it oop). All times the window class of the form is resized (and form is visible), the WM_SIZE is sent.
So it will not happen all the times the form is shown, but only the it's dimensions are changed compared with underlying window class.
As you have observed, many properties valuez change the dimensions of the form (even a few pixels).
This is a very superficial explanation, that's a ton of other details - but it's my understanding how things works "under the hood".
There's no substitute for testing. How about creating a form in code, setting the properties you're interested in and recording when the resize event is called.
If you'll excuse the ugliness of the code, here's a rough proof of concept that tests all combinations of BorderStyle and Position without explicitly coding for each one. You can add more properties and take it as far as you like. A tool like CodeSite would make the logging cleaner and easier, too.
Create an application with 2 forms. Make sure the second one isn't auto-created.
In the second form, add a property and add a little logging code to the form's Resize event:
private
FOnResizeFired: TNotifyEvent;
public
property OnResizeFired: TNotifyEvent read FOnResizeFired write FOnResizeFired;
end;
...
procedure TForm2.FormResize(Sender: TObject);
begin
if Assigned(FOnResizeFired) then
FOnResizeFired(self);
end;
In the main form, add TypInfo to the uses clause and drop a button and a memo on the form.
Add a simple procedure:
procedure TForm1.ResizeDetected(Sender: TObject);
begin
Memo1.Lines.Add(' *** Resize detected');
end;
Now add the following to the ButtonClick event:
procedure TForm1.Button1Click(Sender: TObject);
var
lBorderStyle: TFormBorderStyle;
lBorderStyleName: string;
lPosition: TPosition;
lPositionName: string;
lForm: TForm2;
begin
Memo1.Clear;
for lBorderStyle in [low(TFormBorderStyle) .. high(TFormBorderStyle)] do
begin
for lPosition in [low(TPosition) .. high(TPosition)] do
begin
lBorderStyleName := GetEnumName(TypeInfo(TFormBorderStyle), Integer(lBorderStyle));
lPositionName := GetEnumName(TypeInfo(TPosition), Integer(lPosition));
Memo1.Lines.Add(Format('Border: %s Position: %s', [lBorderStyleName, lPositionName]));
Memo1.Lines.Add(' Creating form');
lForm := TForm2.Create(self);
try
Memo1.Lines.Add(' Form Created');
lForm.OnResizeFired := ResizeDetected;
Memo1.Lines.Add(' Setting border style');
lForm.BorderStyle := lBorderStyle;
Memo1.Lines.Add(' Setting Position');
lForm.Position := lPosition;
Memo1.Lines.Add(' Showing form');
lForm.Show;
Memo1.Lines.Add(' Form Shown');
lForm.Close;
Memo1.Lines.Add(' Form Closed');
finally
FreeAndNil(lForm);
Memo1.Lines.Add(' Form Freed');
end;
end;
end;
end;
You'll notice that resize fires when some properties are set before the form is shown, and I see that in some combinations, resize seems to fire twice when the form is shown. Interesting.

Resources