Delphi application main form moving behind other windows on modal close - delphi

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)

Related

Delphi, form release, preventing Application.MessageBox execution

so there have been a hundred questions whether to Release vs Free forms in Delphi.
I have encountered an interesting issue that I couldn't explain, below is the (over-simplified) pseudo-code:
procedure SomeProc;
var vForm: TForm;
begin
vForm := create;
try
vForm.ShowModal;
finally
vForm.Release;
// more stuff
Application.MessageBox ('some message');
end;
end;
expected behavior:
upon closing my modal form, the messagebox would show up.
actual behavior:
Messagebox doesn't show, and execution proceeds as if user pressed 'No' ('No' is the default in the actual code)
tracing it:
ShowModal ends, no problems here
release is called, CM_RELEASE is posted in the event queue
MessageBox executes // begin internals of MessageBox code, VCL.Forms unit
TaskActiveWindow := ActiveWindow; // a handle to the active window is saved. who is the active window?
MessageBox invokes Application.ProcessMessages
our modal form destructor is finally invoked.
external call to Windows, with the saved handle TaskActiveWindow
If the call to Release is replaced by Free, the issue never occurs. Since the form is a Modal (internally, busy looping processing messages), there is no harm in calling Free, so that solves the immediate problem.
However it does not explain what is actually going on under the hood.
I can assume, but cannot prove, that the active window was still the modal form upon saving the handle. And since the handle's underlying form is then freed, this is why the MessageBox is not showing?
Would love some opinions / insights on the above.
PS: copy-pasting this may not reproduce the issue at your end, but what is definitely true is, no issues are faced when calling Free instead of Release.

JclMapi - e-mail message window goes underneath the main form

I'm experiencing some troubles with JclMAPI. Currently I'm using JCL 2.6 Build 5178 with Delphi XE3.
The main form of my application is a MDIForm which handles different MDIChild forms. From one of these I can display a modal form and from it I call the JclSimpleBringUpSendMailDialog assigning the ParentWND parameter with the modal form handle.
Normally this method opens the email message window in front of the modal form.
My problem is that sometimes the e-mail message window goes underneath the application mainForm and I'm not able to bring it to the front anymore.
So the application waits for the return value of the Jcl Method and I'm not able to reactivate it. Real problem is that the e-mail window is behind my application and I can't compose the message.
i've had no luck on the internet searching.
Have you ever experienced this problem?
You might want to switch to using the Outlook Object Model instead of Simple MAPI. This way you can bring Outlook's main window to the foreground first before displaying the message. Outlook's HWND can be retrieved by casting the Explorer object (returned buy Application.ActiveExplorer) to IOleWindow and calling IOleWindow.GetWindow. Once you have HWND, you can bring it to the foreground using something like the folloowing:
function ForceForegroundWindow(hWnd: THandle): BOOL;
var
hCurWnd: THandle;
begin
hCurWnd := GetForegroundWindow;
AttachThreadInput(
GetWindowThreadProcessId(hCurWnd, nil),
GetCurrentThreadId, True);
Result := SetForegroundWindow(hWnd);
AttachThreadInput(
GetWindowThreadProcessId(hCurWnd, nil),
GetCurrentThreadId, False);
end;

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

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;

Delphi MainFormOnTaskBar Modal windows bug

HI
I'm using Delphi 2007 and have set the MainFormOnTaskBar property to true.
The issue that I'm having is this.
If you open a child window from the main form and then you show a message dialog from the child window you just opened. When you close the message dialog and then close the child window, the main form will be sent to the back of any other application you have on the screen.
This happens under windows Vista and Windows 7. Does anyone know why this is happens and how can I fix it?
I guess that would be QC66892-Closing forms deactivates the application, which appears to have been fixed with Delphi 2009 according to the report. At the bottom of the QC report you'll find a comment by Andreas Hausladen including a link to his fix of the bug. But you'd really want to utilize his VCL Fix Pack which includes many other fixes as well.
I've fixed this in two ways.
Firstly by adding stdcall to the end of DoFindWindow in Forms.pas as described by Andreas Hausladen. This handles when a child form is hidden (CloseAction = caHide) instead of released when closing the form.
Secondly - copied the code from TCustomForm.CMShowingChanged that calls FindTopMostWindow and then activates the window that was returned into TCustomForm.CMRelease.
(Edit: code block needs to be indented by 4 spaces)
procedure TCustomForm.CMRelease;
var
NewActiveWindow: LongInt;
begin
if Application.MainFormOnTaskbar then
begin
NewActiveWindow := 0;
if (GetActiveWindow = Handle) and not IsIconic(Handle) then
begin
NewActiveWindow := FindTopMostWindow(Handle);
end;
if NewActiveWindow <> 0 then
begin
SetActiveWindow(NewActiveWindow);
end;
end;
Free;
end;
This seems to have done it, I'll continue testing to make sure.
The PopupMode and PopupParent properties were added specifically to TForm to address this issue. Before showing the child form, set it's PopupParent to the main form, and it's PopupMode to pmAuto.
PopupParent specifically affects the Z-order of windows when other windows are shown.
The Delphi 2007 help has some documentation on these two properties, but you have to go through TForm to get to them. Use 'TForm,Pop' as your search topic (w/o the quotes, obviously ) to get there. The docs are a little confusing about PopupParent, because it discusses the effect that PopupMode has on the automatic assignment of PopupParent. A little experimentation after reading the docs should pay off, though.

Problems in Showmodal after assigning to Setparent(..)

I created two application MainApps and SubApps, the SubApps has a modal type dialogbox such as login/logout form etc. and its working fine.
After I attach it to the MainApps, the Modal Dialog box shows like normal box form. It behaves like "DIALOG.SHOW" instead of "DIALOG.SHOWMODAL";
I am using delphi compiler
SubApps buttonclick;
begin
with TfrmDialog.Create(Self, dtLogout) do
try
iMsgResult := ShowModal;
finally
Free;
end;
if iMsgResult = mrOk then
begin
dmVoca.FHomeworkXMLDoc.Active := False;
//Disabled Double Login
dmVoca.tmrDoubleLogin.Enabled := False;
................
end;
end;
MainApps ButtonClick
begin
setparent(findwindow(nil,'SubApps'),TabSheet1.Handle);
.........
end;
Don't be surprised, what you are trying is unusual at best. ShowModal achieves the modal effect by disabling all the windows of the calling thread but the modal form. Since your parent form do not belong to the same thread, not even to the same process, it does not get disabled. See DisableTaskWindows in forms.pas to understand how the forms are disabled when 'ShowModal' is called.
You have to devise your own modal procedure; test if the application is parented in a top level window that's not the desktop, disable that window if that's the case.
But if I were you I would think on the design first, what if, f.i., you close the parent form, how do you end the parented form's process?
edit: for 3rd comment below - you might try having the modal form "owned" by the MainApps's form. Similiar to forms being owned by the application main form while MainFormOnTaskbar is true. See owned windows on Window Features topic of msdn.
var
frmDialog: TfrmDialog;
begin
[...]
frmDialog := TfrmDialog.Create(Self, dtLogout);
try
SetWindowLong(frmDialog.Handle, GWL_HWNDPARENT, GetAncestor(Handle, GA_ROOT));
iMsgResult := frmDialog.ShowModal;
[...]
I'd humbly suggest you to ask a question on a suggestion of a design for what you want to achieve, for instance, if it is about code reuse you could host your SubApps forms in a dll... This design is fragile, you may continue to run into problems with it...
Try making your windows "system modal" instead of "application modal". Actually, I have no idea if you can even do that. It might be impossible, or a bad idea. In fact, the whole question gives me the "bad idea" smell.

Resources