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.
Related
I have a systray application without any taskbar icon. Nowdays most systray icons are hidden and I want to make an easier access to app.
There are no forms created with Application.FormCreate so Delphi cannot show icon itself.
How to show a normal app icon always on taskbar even when there is no visible form available?
I want to catch the click like this and when its clicked show the GUI with my custom function:
class procedure TTrayMain.HandleMessages(var Msg: TMsg; var Handled: Boolean);
begin
if (Msg.wparam = SC_RESTORE ) then begin
MenuPopup (popup,2);
Exit;
end;
A Taskbar button cannot exist without a window to represent. There are only 3 ways for a Taskbar button to be created:
create a visible window with the WS_EX_APPWINDOW style
create a visible top-level unowned window
use ITaskbarList::AddTab()
All of them require a window. But, that doesn't mean the user has to see the window. You can create a visible window with width/height set to 0 (some frameworks do this so no single app window owns the Taskbar button, but the button can show/hide the entire app as a whole), or move it offscreen. And then the window can respond to state changes as need, such as via the Taskbar button.
Otherwise, since your tray app likely has a hidden window to receive icon notifications, you can try using that window with ITaskbarList. I just don't know if it will actually do anything meaningful when the user clicks on the button. So, consider changing your tray app to use a visible but unseen window for notifications, then it can have a Taskbar button without involving ITaskbarList.
I have an application which is designed for multiple monitors. It starts up, and we try to avoid activating windows that do not need to be activated, because the user only does keyboard input in one place, and each time we Activate a new form on a secondary monitor, it grabs keyboard focus, something we wish to avoid.
Our in-house base TForm class has a method like this, which is using the Win32 ShowWindow function directly, avoiding the VCL framework's internal visibility change system which grabs focus:
procedure TOurForm.ShowWithoutActivate;
begin
ShowWindow(Self.Handle, SW_SHOWNOACTIVATE);
Self.Visible := true;
end;
If I just did this, it would grab focus:
Self.Visible := true; // TWindow.Visible = true, will grab focus, plus make window visible.
This works, but the next thing I'd like to be able to do is set Maximized state so that the
form will maximize itself on the Monitor that it is currently on. How do we get it onto a particular monitor? The same way it always worked, with modification of the Left and Top properties of the Form. We have to take care that if we store Left/Top/Width/Height on form, and then restore it, that the results are still valid when we reload it. That is NOT what I'm asking about.
I'm specifically asking about how to maximize the form now that I have "showed" it using the above custom function. One hack begets another hack. Here is how far down this rabbit hole I've gone:
When a TForm, which is also a TWinControl's private field FShowing is false, setting Form.Maximized has no effect.
When a TForm has its TWinControl.FShowing field set true, setting the windowState to wsMaximized also causes the form to activate.
Is it possible to both make this form visible and make it take the window state I want it to take without activating? If I can't do this, then users are going to lose their keyboard focus when I show this form on a secondary monitor, something that I really want to avoid.
What I tried is to use Win32 ShowWindow API to do SW_SHOWMAXIMIZED:
ShowWindow(Self.Handle, SW_SHOWMAXIMIZED);
The above seems to grab focus (activate).
When you create the top-level window set the extended window style to
WS_EX_NOACTIVATE | WS_EX_APPWINDOW
WS_EX_NOACTIVATE stops the window activating. This also makes it disappear from the taskbar, so you need WS_EX_APPWINDOW to fix that problem.
Call ShowWindow(hWnd, SW_MAXIMIZE) and the window will be maximized but not activated.
You need to be able to activate the window once it is visible, so in the WM_ACTIVATE handler (the irony!) you need to clear the WS_EX_NOACTIVATE flag thus:
case WM_ACTIVATE:
{
DWORD exstyle = GetWindowLong(hWnd, GWL_EXSTYLE);
if (exstyle & WS_EX_NOACTIVATE)
{
SetWindowLong(hWnd, GWL_EXSTYLE, exstyle & ~(DWORD)WS_EX_NOACTIVATE);
}
}
Apologies for the C++. This should be simple to translate into Delphi.
EnumDisplayMonitors API enumerates the monitors and their coordinates on the joint desktop, area into which a particular monitor maps to its specific position.
To find out which monitor is "current" you would want to compare the current window position against monitor coordinates rcMonitor/rcWork. Or, you have MonitorFromPoint and friends to help you.
Once to decided which monitor you want, you can either move your window (MoveWindow, SetWindowPos) to monitor's work area, or use this rectangle in response to WM_GETMINMAXINFO message to send the window to this position as a part of standard maximization.
To add to this, this small C++ application [1, 2, 3] demos the concept mentioned above, shows monitor information, and changes position where the window would be maximized to.
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.
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:
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.