OpenDialog goes behind modal window on alt+tab - delphi

My application have several MDI forms and one of this form have child modal form with detailed information. So, when I open this modal form from my MDI form, I click 'browse' button and create OpenFileDialog. Everything works fine, except when I ALT+TAB.
When I ALT+TAB and then ALT+TAB back to my application I see that OpenFileDialog (messageboxes too) is BEHIND my modal window, but in fron of MDI window.
There is no StayOnTop or something like that.
Only way to bring back OpenDialog in front of all windows is to make second ALT+TAB to my application. This causes Dialog to pop in front of all other windows.
What can I do to prevent Dialog from hiding behind my Modal form? Any suggestion?
I use delphi7 and can't use greater version

I'm hypothesising that the issue is related to window ownership. In Delphi 7, file dialogs have the hidden application window as their window owner. But the window owner really needs to be the window of the active form.
There are plenty of ways to fix this, but perhaps the simplest is to subclass TOpenDialog and override its TaskModalDialog like this:
function TMyOpenDialog.TaskModalDialog(DialogFunc: Pointer;
var DialogData): LongBool;
var
hwndOwner: HWND;
begin
hwndOwner := Screen.ActiveForm.Handle;
if hwndOwner = 0 then
hwndOwner := Application.MainForm.Handle;
if hwndOwner = 0 then
hwndOwner := Application.Handle;
TOpenFilename(DialogData).hwndOwner := hwndOwner;
Result := inherited TaskModalDialog(DialogFunc, DialogData);
end;
I don't have Delphi 7 at hand to test this, but I'm reasonably confident that something along these lines (with perhaps some tweaking of the hwndOwner choice) will sort it out.

Related

How to show a modal dialog from a modeless form?

I have two "modeless" forms:
one is the special MainForm
the other is a modeless form
You can see:
both exist on the taskbar
both have a taskbar button
both can be independantly minimized
both can be independantly restored
neither is always on top (owned) by the other
Now show a modal form
From this modeless form, i want to show a modal one:
The Modal form is being constructed as:
var
frmExchangeConfirm: TfrmExchangeConfirm;
begin
frmExchangeConfirm := TfrmExchangeConfirm.Create(Application);
try
//Setting popupMode and popupParent still makes the MainForm disabled
// frmExchangeConfirm.PopupMode := pmExplicit;
// frmExchangeConfirm.PopupParent := Self; //owned by us
frmExchangeConfirm.OwnerForm := Self; //tell the form which owner to use
frmExchangeConfirm.ShowModal;
finally
frmExchangeConfirm.Free;
end;
The modal form is told which owner to use through a new OwnerForm property:
protected
procedure SetOwnerForm(const Value: TForm);
public
property OwnerForm: TForm read GetOwnerForm write SetOwnerForm;
end;
which forces an handle recreation:
procedure TfrmExchangeConfirm.SetOwnerForm(const Value: TForm);
begin
FOwnerForm := Value;
if Self.HandleAllocated then
Self.RecreateWnd;
end;
and is then the second time through CreateParams:
procedure TfrmExchangeConfirm.CreateParams(var Params: TCreateParams);
begin
inherited;
if FOwnerForm <> nil then
Params.WndParent := FOwnerForm.Handle;
end;
The problem is:
once this owned modal form is shown, i cannot interact with the MainForm
i cannot minimize the MainForm using the taskbar button
i cannot minimize the Modal, or its owning parent, using the taskbar button
if i minimize the modal form using the Minimize button, the MainForm disappears
i can activate the MainForm using its taskbar button; but i cannot interact with it
I've asked this question about 7 times over the last decade. The last time i was promised that making the main form the MainForm would solve everything.
Bonus: WinForms has handled this correctly since .NET 1.0.
There is a lot of confusion about what a modal dialog is. A dialog is modal when you must interact with it before you can continue to use its owner. From the Windows Interface Design Guidelines:
Dialog boxes have two fundamental types:
Modal dialog boxes require users to complete and close before continuing with the owner window. These dialog boxes are best used for critical or infrequent, one-off tasks that require completion before continuing.
Modeless dialog boxes allow users to switch between the dialog box and the owner window as desired. These dialog boxes are best used for frequent, repetitive, on-going tasks.
Windows has the concept of an "owner". When a window is "owned" that will will always appear on top of its owner. When a window is "modal", it means that the owner is disabled until the modal task is complete.
You an see this effect in the ProgressDialog API:
HRESULT StartProgressDialog(
[in] HWND hwndParent,
IUnknown *punkEnableModless,
DWORD dwFlags,
LPCVOID pvReserved
);
hwndParent [in]
Type: HWND
A handle to the dialog box's parent window.
dwFlags
Type: DWORD
PROGDLG_MODAL
The progress dialog box will be modal to the window specified by hwndParent. By default, a progress dialog box is modeless.
Sure, you could be mean, and disable all other windows
in the thread
the process
or the system
But i want to have the correct behavior. I want to do:
what Windows does
what Office applications do
what Beyond Compare does
what WinForms does
what WPF does
what every application i've ever used does
and what any user would expect
I've wanted this in my Delphi apps since 1998; when realized Delphi 3 didn't properly support Windows 95 and the taskbar.
ShowModal disables all other top level windows in the same thread. That includes your main form.
You'll have to finesse the showing of this form to make it behave the way you want. Do the following:
Disable the modeless owner form.
Show the "modal" form by calling Show.
When the "modal" form is closed, enable the modeless owner. Make sure the owner is enabled before the "modal" form's window is destroyed, as explained below.
You could potentially run your own modal message loop in between steps 2 and 3, as ShowModal does but this might be overkill. I'd just show the form modeless but disable its owner to make it "modal" with respect to that owner.
This process is a little delicate. Look to the source of ShowModal for inspiration. Also, Raymond's epic series of articles on modality is essential reading. I link to it all here: Why does a MessageBox not block the Application on a synchronized thread?
And even more from Raymond: The correct order for disabling and enabling windows:
When you destroy the modal dialog, you are destroying the window with foreground activation. The window manager now needs to find somebody else to give activation to. It tries to give it to the dialog's owner, but the owner is still disabled, so the window manager skips it and looks for some other window, somebody who is not disabled.
That's why you get the weird interloper window.
The correct order for destroying a modal dialog is
Re-enable the owner.
Destroy the modal dialog.
This time, when the modal dialog is destroyed, the window manager looks to the owner and hey this time it's enabled, so it inherits activation.
No flicker. No interloper.

Form moving to the back and staying active

I have a largish Delphi 6 app that I have ported to Delphi XE3. At one point the main form launches another non-modal form. Sometimes (say 50%) after a second or two the newly created form moves behind the main form. Even thought it is now at the back, the newly created form still has focus so there are no activate/deactivate events. There are a few Timer controls and I have disabled them. It still happens.
I can accept my code is doing this -- but how can I find out what is happening? Is there a way to intercept when then new form moves to the back?
Just to be clear: I want both forms to be used separately. Any of them can appear behind the other. What is happening at the moment is that the z-order seems to be changing.
I have found the answer to this. I discovered I had added a CreateParams override that did this:
// make a taskbar window
inherited CreateParams( params );
params.ExStyle := params.ExStyle or WS_EX_APPWINDOW;
params.WndParent := GetDesktopwindow; // this line caused the problem
Commenting the WndParent solved it. The effect is bizarre though. It is as if there is a timer that goes off about a second after any key or mouse event that forces the window behind others. Thanks to David Heffernan whose comments about stepping CreateParam made me notice it.

Delphi 6 form set to position itself with poDesktopCenter ends up on "extended" monitor

I have a Delphi 6 application that launches a Wizard after the main form appears. The Wizard is a modal form. One of my users has their Windows desktop extended to more than one monitor. In their case the main form appears on the primary monitor and the Wizard appears on the Extended monitor. This creates confusion because they think the app has frozen when they try to click on the main form. Since the Wizard is open and modal, nothing happens except they hear the warning "ding" tone that tells you a form is not able to receive input.
What can I do to make sure the Wizard form appears on the same monitor as the main form, in this case the primary monitor? I have the Wizard form set to poDesktopCenter.
Manual theory:
Use poMainFormCenter when you want your form to be centered by the Application.MainForm. The application main form is, in short, the first form you can see when you run your application, and you should consider that this main form can be on a different monitor than the active window from which you create and center a new form.
Or if you want to center your form by its Owner, use the poOwnerFormCenter which is IMHO better for user's experience because when you have more than two windows opened by each other, you can move the window to another monitor and create the new window on the monitor where user currently works on.
Practical usecase:
User ran your application on the 1st monitor. The application created the Form2 from its MainForm. User moved that Form2 on the 2nd monitor and from there pressed the button which created another form, Form3.
If you designed your Form3 to use the poMainFormCenter position, the Form3 will be centered by the MainForm which is at this time on a different monitor, what is IMHO confusing.
If you would use code like this for creating and showing Form3:
procedure TForm2.Button1Click(Sender: TObject);
begin
// the Owner parameter Self (or Form2 here) in the Form3 constructor along
// with the Position set to poOwnerFormCenter will ensure you that the form
// will be centered by the current form position, so on the current monitor
// where the user works on as well
Form3 := TForm3.Create(Self);
try
Form3.Position := poOwnerFormCenter;
Form3.ShowModal;
finally
Form3.Free;
end;
end;
You will get Form3 centered by the Form2 but mainly on the same monitor as the Form2 currently lies on, as you currently work on:

How to make OpenDialog show modal in Firemonkey?

When I'm calling an OpenDialog from my form on ButtonClick event. The dialog does not shows as modal and is also displays in taskbar (in WindowsXP). I can return to main form and click Open again and again - popping up several dialogs at once..
How do I make an OpenDialog to be modal in Firemonkey?
Is it specifically made so that no modal dialogs are allowed due to multi-platform anture of FM?
EDIT: The bug is fixed in Update 3.
I think it's a bug. There are a lot of modal type bugs with FireMonkey, and hopefully they will be fixing them soon. Currently, even modal forms aren't modal.
For your problem, I have a workaround for Windows, but you might not like it.
You need to fix the following line in the TPlatformWin.DialogOpenFiles() method in FMX.Platform.Win.
Under with OpenFile do change:
hwndOwner := 0;
To this:
hWndOwner := FmxHandleToHWND(Application.MainForm.Handle);
The function utilizes the Windows GetOpenFileName API call, even though it's deprecated on Vista and above. If a owner handle is passed in, the dialog is modal, otherwise it's not.
You might want to submit this as a bug to qc.embarcadero.com along with the workaround.

How to make a floating DirectX Child Window with Delphi?

I have a Delphi project and the main form is a child window of another application. The parent application (a 3D game coded in C++) has two window states, windowed and full screen. In windowed mode the Delphi form floats on top of the parent application perfectly. It can be clicked on, dragged around, no problem (using params.Style := WS_POPUP; in the CreateParams procedure of my main form):
procedure TMyForm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
HandleToTheOtherApp := FindWindow('THE_OTHER_APP', nil);
if HandleToTheOtherApp = 0 then
begin
ShowMessage('The parent app was not found');
exit;
end;
params.Style := WS_POPUP;
params.WndParent := HandleToTheOtherApp;
params.WinClassName := 'MyAppClassName';
end;
However if the parent application is put into full screen mode (it uses DirectX 9/10) my Delphi form is hidden from view. It appears to still be on the screen as I can see if flashing occasionally, but is not being redrawn.
If I click on the window (or where it appears to be) it re-appears for a second and then dumps the game out of full screen mode and back into windowed mode. Clearly there is something it doesn't like about the form. The form used to appear in previous editions of this game, but perhaps due to changes in the DirectX version (my guess) it no longer appears.
The form doesn't display anything complicated like 3D graphics, just basic controls like buttons and list boxes. I have tried various Window styles and made it Doublebuffered, all to no avail. Perhaps there is someone out there who has been successful in getting a window to show in these circumstances using a particular function, Window style or header?
Is there some way to make a Delphi form appear correctly within a DirectX environment?
I don't think this can be done.
When a DirectX Video window becomes full screen, then DirectX takes over the complete screen drawing (similar to when you make a Windows Media Player full screen: all other apps disappear, and sometimes even the resolution changes).

Resources