Losing modality of ShowMessage window - delphi

I use TAcroPDF activeX control to print pdf documents. Application prints page, but if it needs to print 2 pages, it prints the first page, then shows message dialog, which asks user to turn the paper and then it prints the second page. The concept is pretty straightforward, but for unknown cause it fails to run normally. It really prints the first page, but then blips ShowMessage window (hides instantly) and the application goes seemingly on. I can then recover lost Showmessage window hidden behind the main application window, but (what is strange) I can easily change focus between showmessage window and main application. It looks like the program runs in other thread, because of ActiveX??? When I click on "OK" button, the code after ShowMessage('Please, turn the page'); is executed. Can anybody tell me whats is going on? Here is example:
procedure TForm1.btn1Click(Sender: TObject);
begin
acrpdf1.src := Getcurrentdir + '\temp.pdf';
acrpdf1.PrintAll;
ShowMessage('Please, turn the page'); //it does not hover over main window, it hides behind
Form1.Color:=clRed; //it is executed after closing Showmessage window
end;

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.

How to force user to click a button in only one active window and not be able to click things in other windows?

I have a small question.
I think there must be a easy way to do it but I just can't find the keyword, so please teach me.
Here is the question:
For example, default ShowMessage pops a window with 'OK' button, then program stops temporarily before you click 'OK'.
This makes sense, but the problem is, you can still interact with other objects before you click 'OK'.
It certainly causes some problems like this:
var
Count: Integer; //initial it to 1
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage(IntToStr(Count));
Inc(Count);
end;
By not closing the message box, program always stops at same place and the variable remains the same.
How do I lock the popping window so you can't interact with other objects?(System forces you to turn your focus back to the popping window if you do so)
Is it possible to achieve this with ShowMessage?
It would be helpful if I can get some tips.
Thanks a lot.
Edit:
Using the unit QDialogs.ShowMessage version causes a non-modal problem. Beware of those units you are not familiar lol.
ShowMessage shows a modal dialog. In other words, it disables its owning window while the dialog shows. And so you cannot interact with the other windows in your app. ShowMessage already does what you want.
If you find that you can interact with other windows in your application whilst the dialog is showing, then you must have got the window ownership wrong.

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:

Delphi TPageControl not responding to clicks on tabs

I have an app with a TPageControl on the main form. The pagecontrol has several tabs. The app can be minimized to a tray icon. Sometimes after running minimized for a while, when I restore the main window (via a right-mouse click on the tray icon), the tab that was last displayed is displayed, but I can't select any other tabs!
If I click on another tab, the appearance changes so that tab then appears to be the active one (i.e the tab itself moves to the front of the row of tabs), but the body of the tab remains as it was. I also have menu items and shortcut keys to select the other tabs and they behave the same. If I type Alt-O (options) the options tab at the top becomes active but I can't see what is on the body of that tab - I still see the other tab's contents.
I have verified that focus moves off the first tab when I click on another tab and moves back when I click on that tab.
I haven't yet established if the behaviour is confined to a particular tab as it takes a while for it to happen.
Any ideas?
Update
Interesting note. I have established that the problem occurs under these circumstances. The app is started, then minimized to the tray. An alert condition is detected, pops up a window and restores the main window (this is intended behaviour of the app). It is at this point the fault is observed - i.e. I cant see the other tabs when I click on them.
Start app. Tab 1 is displayed
Minimize app. to tray
Wait for popup to show, main form is restored
Click on Tab 2 FAULT OBSERVED (Tab 2 body does not display)
Put breakpoint in TWinControl.CreateHandle
Click on Tab 3 - breaks
Run - does not show Tab 3 body
Click on Tab 1 - does not break
Click on Tab 3 - does not break
Click on Tab 4 - breaks
Run - does not show Tab 4 body
Click on Tab 1, 2, 3, 4 - does not break
So it seems the tabs are creating their handles the first time they are clicked on, and from that point on they think they exist, but they don't show. If the popup is disabled the fault is not observed. The popup is triggered from an Application.OnIdle task.
Another update: Some progress. After poking around on the web I made some changes.
I removed the following code:
procedure RestoreMainWindow ;
begin
MainForm.WindowState := wsNormal ;
MainForm.visible := true ;
Application.Restore ;
Application.BringToFront ;
ShowWindow (Application.Handle, SW_SHOW) ; { show the taskbar button }
end ;
and replaced it with:
procedure RestoreMainWindow ;
begin
MainForm.Show () ;
MainForm.WindowState := wsNormal ;
Application.BringToFront () ;
ShowWindow (Application.Handle, SW_SHOW) ; { show the taskbar button }
end ;
I removed:
procedure TTADMainForm.SendToTray (Sender: TObject) ;
begin
MainForm.visible := false ;
ShowWindow (Application.Handle, SW_HIDE) ; { hide the taskbar button }
end ;
...
Application.OnMinimize := SendToTray ;
and replaced it with:
procedure TTADMainForm.ApplicationEvents1Minimize(Sender: TObject) ;
begin
Hide();
WindowState := wsMinimized ;
TrayIcon1.Visible := True;
end ;
and the problem seems to have gone. HOWEVER. Now I can minimize the app after startup, the popup occurs and shows modally, the main form shows, all the tabs display and work. BUT. I can't minimize the form again. The OnMinimize handler doesn't get triggered after the first time. Grrrrr.
I still can't fathom why it works now, which is a little worrying. And how do I get it to minimize again??
Working entirely from 5 years ago memory, but here goes:
TPageControl uses a different window handle for each page within it. The tab bar is its own window handle, and the TPageControl is responsible for listening to tab changes and making the corresponding hide/show of pages. So, when you click on a tab and the tab jumps to the front of the pack, the TPageControl is supposed to hide the current page window and show the page window corresponding to the selected tab.
Normally, VCL controls don't create their window handle until it is actually needed - when it's actually shown, for example. This reduces window handle consumption. Critically important in Windows 3.1 and Win95, but not so critical in today's NT based 32 bit OS's.
To minimize resource load and startup time, TPageControl doesn't create window handles for all its hidden pages when the control is created. The page window handles will be created when they are first shown.
There are a few possibilities for why the page is not being drawn when the tab is clicked:
Exhausting the GDI window handle pool. Extremely unlikely unless you're on a 16 bit Windows OS. (Win 3.1 or Win95)
Memory leak that causes your app to spill into the swap file and thrash the hard disk. The app will grind to a near halt and look like it's frozen, with burps of UI activity every now and then.
Window handles being created on a background thread that has no message loop. Are you doing anything in background threads? Touching a VCL control in a background thread can cause the window handle to be created prematurely, and the window handle will be bound to the thread it was created on. If that thread has no message loop, then that window handle will never receive any messages, so it will never draw itself on screen.
No. 3 is your most likely culprit. So, what are you doing in that background thread? ;>

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