Can't hide a window that's not been fully initialized - delphi

I have this awkward situation that causes an exception. I've found the problem but the solution is so far tricky to implement for me.
In our application, if a user stays in-active for a certain period of time, the security time-out procedure kicks in to prompt the user the password entry box.
However, whenever a form has a message box displayed during the FormShow() event for any particular reason (thing to pay attention here; the formShow event execution hasn't been fully completed yet) and the user decided to not click the OK button of the dialog box for a certain time, the security code kicks in and tries to hide all forms so that it can prompt a password.
This scenario will trigger the exception "Cannot change Visible in OnShow or OnHide".
Security code loops all forms using TScreen.FormCount and hides them using TForm(TScreen.Forms[ii]).Hide individually. Hide procedure will cause the exception, because I think this form has not completed it's loading procedure fully yet.
I've done tests, and if I display a message box after the FormShow() event is executed, the security code works perfectly fine and hides all windows without any issues.
I've tried several properties and window message checking to do an "if check" before hiding forms, like Screen.Forms[ii].Visible, Screen.Forms[ii].Active but no luck so far. The mentioned form will be visible and there's no guarantee that it will be active, and if it's active how am I going to hide other non active forms. So my question is, which property or Windows message would indicate that a form is fully loaded, or at least it has past the FormShow event of any given form that exists in TScreen.Forms?
I need an answer to what I am asking please, I need a generalized solution that needs to be implemented in the security code, I can't go through over a thousand forms we have in this giant application and individually try to find solutions to any validation/warning logic exist in those forms.
Thank you

The simple answer is to stop showing the modal dialog in the OnShow of the owner form. Wait until the form has finished showing before you display the modal dialog. If you make that change, and that change alone, your existing code will start to work.
The question title you chose was:
Can't hide a window that's not been fully initialized
And so the obvious solution is to wait until the window has been fully initialized.
The simplest way to achieve this is to move your code that currently runs in OnShow into a handler for CM_SHOWINGCHANGED:
procedure CMShowingChanged(var Message: TMessage); message CM_SHOWINGCHANGED;
Implement it like this:
procedure TMyForm.CMShowingChanged(var Message: TMessage);
begin
inherited; // this is what invokes OnShow
if Visible then
begin
// do what you previously did in OnShow
end;
end;

David Heffernan's solution gave me an idea and I solved this issue on my end.
I created following;
const
WM_SHOW_MESSAGE = WM_USER + 1;
private
procedure WMShowMessage(var Msg: TMessage); message WM_SHOW_MESSAGE;
Inside constructor;
PostMessage(Handle, WM_SHOW_MESSAGE, 0, 0);
And this will have the message box logic:
procedure MyMethod.WMShowMessage(var msg: TMessage); message WM_SHOW_MESSAGE;

Related

Delphi application main form moving behind other windows on modal close

I'm starting to have issues with my main form disappearing behind other application windows on closing modal forms and I was hoping someone would have come across (and solved!) this issue previously or have suggestions on where to locate breakpoints to debug the problem.
My issues originally started with the classic 'shy dialog' problem with modal dialogs appearing under the main form which occurred intermittently. To try to sort this I changed all my modal forms' popupmode to pmAuto and also added
Application.ModalPopupMode := pmAuto;
and Application.MainFormOnTaskBar := true; to my application dpr.
Now I'm getting the main form disappearing behind other windows on closing the modal pop-ups. I have suspicion is that the behaviour is mainly caused when a modal form opens a second window (I've problems with both a MessageDlg and a straight Form.create(Application); Form.show;), though there's no obvious problems with the show/free code (ShowModal forms are created owner = nil, modeless with owner = application). In both cases the form disappears on closing the first original modal form, but manipulating the modal form without triggering a new form/dialog to appear seems to work as expected.
There are other nasties going on in the background on the main form with a refresh timer that activates a background thread, but usually this hasn't fired in the time it takes to see it not working. Other than that we are firing off calls to a remote server via a third-party DLL (the application is effectively a client-side GUI).
Annoyingly I can't get a mini program to mimic the behaviour and running in the IDE makes seeing the behaviour difficult, as the IDE itself contains a lot of windows that muddy the Z-ordering.
Edit - After writing my answer below, it appears I'm getting a deactivate event sent to the application (I can catch it through Application.OnDeactivate) - it seems similar to WPF App loses completely focus on window close Delphi doesn't have the Activate method that the c# solutions have, but I'll play with some windows messaging to see if I get anywhere
Following David's advice in comments I created a little logging form to be created on startup containing memo, timer and the following OnTimer event:
procedure TForm1.Timer1Timer(Sender: TObject);
function logtomemo(aHandle: HWND): TWinControl;
var
form: TWinControl;
begin
form := findControl(ahandle);
if form <> nil then
memo1.Lines.Add(format('handle %d - form %s', [ahandle, form.Name]));
result := form;
end;
var
handle: HWND;
form: TWinControl;
begin
memo1.Clear;
handle := application.ActiveFormHandle;
repeat
form := logtomemo(handle);
handle := GetWindow(handle, GW_OWNER);
until (handle = application.MainFormHandle) or (form = nil);
logtomemo(handle);
end;
Clicking around I noticed that as soon as I clicked outside of my application, our splash form appeared as the only form in the list. (Historically our splash screen used to only be freed after Application.Run, as they used to keep some other references on it for some reason - before my time and wasn't really needed anymore).
Changing the lifetime of the splashscreen to be destroyed before Application.Run appears to have sorted the issue - something that I'd never have guessed would be the cause in a million years.
Need a final sign-off that it doesn't reappear once I get rid of this little debug form, but hopefully a problem that's been frustrating me for a few days is now sorted - thanks!
Edit
As I noted in my edit and the comments to this question, the above debug didn't work, as the presence of the new form 'fixed' the problem. Changing the code so the output was sent to the Event Log or a text file rather than requiring a form also didn't reveal anything - all forms in the Z order remained in place.
In the end, I was able to fix the symptom rather than the cause by attaching the following code to Application.OnModalEnd
if Application.ModalLevel = 0 then
Windows.SetActiveWindow(Application.MainFormHandle);
This successfully sets the active window back to the main form after the last modal dialog has been closed.
This may have some side-effects if the user is expecting a non-modal form that isn't the main form to regain focus, but our application architecture doesn't really follow this structure and with Application.MainFormOnTaskbar, the main form won't hide the other forms (as long as they're not unparented)

Which is the best place to initialize code? [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Splash Screen Programatically
Show a splash screen while a database connection (that might take a long time) runs
Which is the best place to initialize code such as loading INI file? I want first to show the form on screen so the user know that the app is loading and ONLY after that I want to call lengthy functions such as LoadIniFile or IsConnectedToInternet (the last one is REALLY slow).
The OnCreate is not good because the form is not yet ready and it will not show up on screen.
I do this I DPR but not working always:
program Test;
begin
Application.Initialize;
Application.Title := 'Test app';
Application.CreateForm(TfrmTest, frmTest);
frmTest.Show; <---------------------- won't show
LateInitialize;
Application.Run;
end.
The form will not show until LateInitialize (4-5 seconds) is executed.
procedure LateInitialize;
begin
CursorBussy;
TRY
// all this won't work also. the form won't show
frmTest.Visible:= TRUE;
Application.ProcessMessages;
frmTest.Show;
Application.ProcessMessages;
frmTest.BringToFront;
frmTest.Update;
Application.ProcessMessages;
DoSomethingLengthy; {4-5 seconds}
FINALLY
CursorNotBussy;
END;
end; <--------- Now the form shows.
And yes, frmTest it is my only form (the main form).
After calling frmTest.Show, you can call frmTest.Update to let it render onscreen, before then calling LateInitialize. But until Application.Run is called, the main message loop will not be running, so the form will not be able to do anything else until then.
Another option is to use the form's OnShow event to post a custom window message back to the form via PostMessage(), then have the form call LateInitialize when it receives that message at a later time. That will allow the form to process painting messages normally until LateInitialize is called.
Anything that blocks the main thread for more than a few milliseconds/seconds really should be moved into a separate worker thread instead (especially things like IsConnectedToInternet). The main thread should be used for running the UI.
An easy way to do this, is to send a message to yourself.
I do this all the time
const
MSG_AFTERCREATE = WM_APP + 4711;
...
procedure OnCreate(Sender: TObject);
procedure AfterCreate(var message: TMessage); message MSG_AFTERCREATE;
...
Implementation
procedure OnCreate(Sender: TObject);
begin
PostMessage(Self.Handle, MSG_AFTERCREATE, 0, 0);
end;
procedure AfterCreate(var message: TMessage);
begin
//Do initializing here... the form is done creating, and are actually visible now...
end;
Variant 1: Use TTimer with a 1 second delay, run it from main form's OnShow
In TTimer do the initialisation
This will give time for most components to initialize and draw themselves
Variant 1.1: use message method in function and call Win API PostMessage (but not SendMessage aka Perform) from OnShow. This is seemilar but more cheap and fast. However that message "do init now" sometimes may be received before some complex component on the form would fully draw itself.
Variant 2: use threads (OmniThreadsLib or even plain TThread)
Launch it from MainForm OnCreate and let it prepare all data in background, then enable all needed buttons, menus, etc
That is truly the best way if you have long and blocking functions, liek you described IsConnectedToInternet.
Variant 3: use SplashScreen before showing main form.
That is good because users see that application not read yet.
That is bad for that very reason - people start feeling your program is slow. Google Chrome was told to draw their main form as picture in 1st moments just to make look "we are already started" even the actual control would be ready a bit later.
A long time ago in another forum far far away, someone posted the following to document the life cycle of a form. I have found it useful, so am sharing it here.
Create OnCreate
Show OnShow
Paint OnPaint
Activate OnActivate
ReSize OnResize
Paint OnPaint
Close query OnCloseQuery
Close OnClose
Deactivate OnDeactivate
Hide OnHide
Destroy OnDestroy
Try the OnActivate event.

DELPHI Edit.OnExit by TAB, show window result on focus bug

I'm having trouble with the following scenario:
2 Edit's
Type something in Edit1 and press TAB, focus goes to Edit2
Edit1.OnExit -> show a Form with a message "Processing..." (makes a lengthy validation)
After the form closes, the focus on Edit2 seems to be "crashed"...
- the hole TEXT in Edit2 isn't selected
- the carret isn't flashing
Example:
Create a new form
Put 2 edits
Set this as OnExit event in Edit1:
procedure TForm1.Edit1Exit(Sender: TObject);
begin
with TForm.CreateNew(self) do
try
Width := 100;
Height := 50;
Position := poMainFormCenter;
show;
sleep(200);
finally
Free;
end;
end;
Run the application
Set focus in the Edit1 and press TAB
I'm using:
Delphi 7 Enterprise
Windows 7 x64
This is a known problem. Windows has problems when you change focus before it's completed the last focus change (eg., focus starts changing from Edit1 to Edit2, but Edit1.OnExit does something to change focus to another control or form.
This happens, for instance, when apps try to do validations in an OnExit event and then try to return focus to the original control when the validation fails.
The easiest solution is to post a message to your form handle in the OnExit instead, and handle the focus change need there. It will fire once the target control gets the input focus, and Windows doesn't get confused.
const
UM_EDIT1_EXITED = WM_USER + 1;
type
TForm1=class(TForm)
...
private
procedure UMEdit1Exited(var Msg: TMessage); message UM_EDIT1_EXITED;
end;
implementation
procedure TForm1.Edit1Exit(Sender: TObject);
begin
PostMessage(Handle, UM_EDIT1_EXITED, 0, 0);
end;
procedure TForm1.UMEdit1Exited(var Msg: TMessage);
begin
// Show your other form here
end;
From an old Borland NG post by Dr. Peter Below of TeamB:
here is my general sermon on the "show dialog from OnExit" problem:
If an OnExit handler is triggered (which happens in response to the
Windows
message WM_KILLFOCUS) Windows is in the midst of a focus change. If you do
something in the handler that causes another focus change (like popping up
a message box or doing a SetFocus call) Windows gets terribly confused.
The
missing cursor is a symptom of that.
If you have to display a message to your user from an OnExit handler, do
it
this way:
Define a constant for a user message somewhere in the INterface
section
of your unit, above the type declaration for your form
'Const
UM_VALIDATE = WM_USER + 200;'
Give your Form a handler for this message, best placed in the private
section of the class declaration:
Procedure UMValidate( Var Msg: TMessage ); message UM_VALIDATE;
Post a UM_VALIDATE message to the form from the OnExit handler if
the contents of the field are not ok. You can pass additional
information in the wparam and lparam parameters of the message, e.g.
an error number and the Sender object. In fact you could do the whole
validation in the UMValidate handler!
I'm not sure precisely what's going on here, but it looks like the order of processing of messages is a bit messed up. Instead of killing your other form with Free, use Release and the focus will behave as you desire.
Another option is to use ShowModal instead of Show. Normally you show a processing dialog modally because you don't want the user making modifications to the main form whilst you are processing. If you do that then you can carry on using Free.

Delphi GUI Testing and Modal Forms

In this interesting blog post on delphiXtreme I read about DUnit's built-in GUI testing capabilities (basically an alternative test case class TGUITestCase defined in unit GUITesting that has several utility functions for invoking actions in the GUI). I was quite happy with it until I noticed that it didn't work with modal forms. For example the following sequence won't work if the first button shows a modal configuration form:
Click ('OpenConfigButton');
Click ('OkButton');
The second Click is only executed when the modal form is closed, which I have to do manually.
I don't know much about how modal forms work in the background but there must be some way to circumvent this behaviour. Naively, I want to somehow execute the ShowModal "in a thread" so that the "main thread" stay responsive. Now I know that running ShowModal in a thread will probably mess up everything. Are there any alternatives? any way to circumvent the blocking nature of a ShowModal? Has anybody some experiences with GUI testing in Delphi?
I know about external tools (from QA or others) and we use those tools, but this question is about GUI testing within the IDE.
Thanks!
You can't test modal forms by calling ShowModal; because as you have quite rightly discovered, that results in your test case code 'pausing' while the modal form awaits user interaction.
The reason for this is that ShowModal switches you into a "secondary message loop" that does not exit until the form closes.
However, modal forms can still be tested.
Show the usually Modal form using the normal Show method.
This allows your test case code to continue, and simulate user actions.
These actions and effects can be tested as normal.
You will want an additional test quite particular to Modal forms:
A modal form is usually closed by setting the modal result.
The fact that you used Show means the form won't be closed by setting the modal result.
Which is fine, because if you now simulate clicking the "Ok" button...
You can simply check that the ModalResult is correct.
WARNING
You can use this technique to test a specific modal form by explicitly showing it non-modally. However, any code under test that shows a modal form (e.g. Error Dialog) will pause your test case.
Even your sample code: Click ('OpenConfigButton'); results in ShowModal being called, and cannot be tested in that manner.
To resolve this, you need your "show commands" to be injectible into your application. If you're unfamliar with dependency injection, I recommend Misko Hevery's Clean Code Talks videos available on You Tube. Then while testing, you inject a suitable version of your "show commands" that won't show a modal form.
For example, your modal form may show an error dialog if validation fails when the Ok button is clicked.
So:
1) Define an interface (or abstract base class) to display an error messages.
IErrorMessage = interface
procedure ShowError(AMsg: String);
end;
2) The form you're testing can hold an injected reference to the interface (FErrorMessage: IErrorMessage), and use it to show an error whenever validation fails.
procedure TForm1.OnOkClick;
begin
if (Edit1.Text = '') then
FErrorMessage.ShowError('Please fill in your name');
else
ModalResult := mrOk; //which would close the form if shown modally
end;
3) The default version of IErrorMessage used / injected for production code will simply display the message as usual.
4) Test code will inject a mock version of IErrorMessage to prevent your tests from being paused.
5) Your tests can now execute cases that would ordinarily display an error message.
procedure TTestClass.TestValidationOfBlankEdit;
begin
Form1.Show; //non-modally
//Do not set a value for Edit1.Text;
Click('OkButton');
CheckEquals(0, Form1.ModalResult); //Note the form should NOT close if validation fails
end;
6) You can take the mock IErrorMessage a step further to actually verify the message text.
TMockErrorMessage = class(TInterfaceObject, IErrorMessage)
private
FLastErrorMsg: String;
protected
procedure ShowError(AMsg: String); //Implementaion trivial
public
property LastErrorMsg: String read FLastErrorMsg;
end;
TTestClass = class(TGUITesting)
private
//NOTE!
//On the test class you keep a reference to the object type - NOT the interface type
//This is so you can access the LastErrorMsg property
FMockErrorMessage: TMockErrorMessage;
...
end;
procedure TTestClass.SetUp;
begin
FMockErrorMessage := TMockErrorMessage.Create;
//You need to ensure that reference counting doesn't result in the
//object being destroyed before you're done using it from the
//object reference you're holding.
//There are a few techniques: My preference is to explicitly _AddRef
//immediately after construction, and _Release when I would
//otherwise have destroyed the object.
end;
7) Now the earlier test becomes:
procedure TTestClass.TestValidationOfBlankEdit;
begin
Form1.Show; //non-modally
//Do not set a value for Edit1.Text;
Click('OkButton');
CheckEquals(0, Form1.ModalResult); //Note the form should NOT close if validation fails
CheckEqulsString('Please fill in your name', FMockErrorMessage.LastErrorMsg);
end;
There is actually a way to test modal windows in Delphi. When a modal window is shown your application still processes windows messages so you could post a message to some helper window just before showing the modal window. Then your message would be handled from the modal loop allowing you to execute code while the modal window is still visible.
Recently I've been working on a simple library to handle this very problem. You can download the code from here: https://github.com/tomazy/DelphiUtils (see: FutureWindows.pas).
Sample usage:
uses
Forms,
FutureWindows;
procedure TFutureWindowsTestCase.TestSample;
begin
TFutureWindows.Expect(TForm.ClassName)
.ExecProc(
procedure (const AWindow: IWindow)
var
myForm: TForm;
begin
myForm := AWindow.AsControl as TForm;
CheckEquals('', myForm.Caption);
myForm.Caption := 'test caption';
myForm.Close();
end
);
with TForm.Create(Application) do
try
Caption := '';
ShowModal();
CheckEquals('test caption', Caption);
finally
Free;
end;
end;

Delphi Application Loses Focus

I implement this chevron tool bar in my application and it works perfectly great; however, when I clicked on any items on the menu, my application loses focus. Even if I move my mouse over the the corner of the form, the cursor doesn't change to the resize handle. I need to click on the form or application in order to regain focus which I would not like to have to do. Calling MainForm.Setfocus after calling the menu item doesn't help. I would like to have the focus be automatically on my application so my users don't need to click on the form before doing the things they need to do.
Any idea on how to regain focus on the form and/or application?
Thanks
Intercept the WM_KillFocus message.
pseudo code
don't have Delphi on this terminal, will fill in the blanks when home.
type
TForm1 = class(TForm)
...
protected
procedure WMKillFocus(message: TWM_Killfocus); message WM_KillFocus;
...
procedure TForm1.WMKillFocus(message: TWM_Killfocus);
begin
//do stuff to prevent the focus from shifting.
//do *NOT* call SetFocus, it confuses Windows/Delphi and leads to suffering
//Call PostMessage or handle the KillFocus message
//From MSDN
//While processing this message, do not make any function calls that display
//or activate a window. This causes the thread to yield control and can
//cause the application to stop responding to messages. For more information
//see Message Deadlocks.
//Also don't use SendMessage, PostMessage is OK though.
//Suggestion:
PostMessage(Self.handle, WM_SETFOCUS, 0, 0);
end;

Resources