Jump to content
vshvetsov

MessageBox is hidden by a modal form

Recommended Posts

Hi,

My function Func() calls Application->MessageBox(..). If Func() is called from a member function of a modal form, then sometimes (not always!) the message box appears hidden under the modal form. In this case, the modal form is not active. It looks like my app is stuck. Switching between windows in Windows with Alt-tab helps to see the message.
What could be the reason? How can you ensure that the message box always appears on top of all open windows?

Share this post


Link to post
Posted (edited)
1 hour ago, vshvetsov said:

How can you ensure that the message box always appears on top of all open windows?

You can ensure MessageBox is shown on top of calling window by specifying that window's handle. Application's method tries its best but can fail. If you look at its source, the 1st line is "ActiveWindow := ActiveFormHandle;" which leads to "GetActiveFormHandle" method which turns out to call FOnGetActiveFormHandle event which you can handle manually.

Edited by Fr0sT.Brutal

Share this post


Link to post
9 hours ago, Fr0sT.Brutal said:

Application's method tries its best but can fail. If you look at its source, the 1st line is "ActiveWindow := ActiveFormHandle;"

Which *should* be returning the modal Form's window, if the modal Form has focus at the time TApplication.MessageBox() is called.

 

It should be noted that if no *active Form* window is found, then TApplication.MessageBox() uses the hidden TApplication window, which is what allows the MessageBox dialog to appear behind Form windows.

 

You could alternatively just call the Win32 MessageBox() API directly (or TaskDialog/Indirect() instead), specifying the desired Form window as its owner window.

Share this post


Link to post
13 hours ago, Remy Lebeau said:

Which *should* be returning the modal Form's window, if the modal Form has focus at the time TApplication.MessageBox() is called.

Sure but if the modal form is not focused and OS-topmost (hangs in the background) bad things happen.

13 hours ago, Remy Lebeau said:

You could alternatively just call the Win32 MessageBox() API directly (or TaskDialog/Indirect() instead), specifying the desired Form window as its owner window.

That's what I advised too and what I use widely in my projects. However I found the App's method contains additional stuff dealing with multi-monitor setup and RTL which could be useful. OTOH, it ridiculously still uses PChar's for caption and text forcing a user to add excess casts.

Share this post


Link to post
7 hours ago, Fr0sT.Brutal said:

OTOH, it ridiculously still uses PChar's for caption and text forcing a user to add excess casts.

Agreed. I never understood why they did that in the first place, and why it was never updated to use String parameters instead.

Share this post


Link to post
Posted (edited)
On 6/27/2022 at 8:29 PM, Remy Lebeau said:

You could alternatively just call the Win32 MessageBox() API directly (or TaskDialog/Indirect() instead), specifying the desired Form window as its owner window.

The function Func() belongs to the "kernel" part of my code, it doesn't know anything about forms. In fact, all my kernel functions, when they want to give a message, generate a special MyKernel.OnMessage(string, MB_flags) event. In the "interface" part of the program, a general response to this event is implemented. It simply passes string and flags to an Application->MessageBox. When a OnMessage event is generated by kernel functions, different forms can be active (and are not guaranteed to have focus). To determine an active form I would need to write a complex code. But I just need to display a message on the screen so that it is guaranteed to be visible. It's a pity, if it is not possible. Maybe it will help to explicitly specify the MB_TOPMOST flag in a MessageBox?

Edited by vshvetsov

Share this post


Link to post
On 6/27/2022 at 11:18 AM, Fr0sT.Brutal said:

which turns out to call FOnGetActiveFormHandle event which you can handle manually. 

Do you mean, I may write a ApplicationGetActiveFormHandle function, assign the function to Application->FOnGetActiveFormHandle, and set any form as an active form? Couldn't this mess up the Application object? After all, the object uses GetActiveFormHandle in other situations as well?

Share this post


Link to post
Posted (edited)
5 hours ago, vshvetsov said:

Do you mean, I may write a ApplicationGetActiveFormHandle function, assign the function to Application->FOnGetActiveFormHandle

To the Application->OnGetActiveFormHandle event, not to the Application->FOnGetActiveFormHandle data member. But yes, you get the idea.

Quote

and set any form as an active form?

Not set a Form as active, no.  The RTL is querying you to choose which Form window should be considered "active" at that moment.  So, you would need to enumerate your existing Forms and decide which one you want the MessageBox to be on top of.  But you don't actually make that Form window be focused.  The RTL is merely establishing a Z-Order relationship between the MesageBox and its Owner window.  It is not moving input focus around.

Edited by Remy Lebeau

Share this post


Link to post
Posted (edited)
15 hours ago, vshvetsov said:

Do you mean, I may write a ApplicationGetActiveFormHandle function, assign the function to Application->FOnGetActiveFormHandle, and set any form as an active form? Couldn't this mess up the Application object? After all, the object uses GetActiveFormHandle in other situations as well?

Remy's right. You can query current modal forms via looping through Screen.Forms and checking FormState=fsModal to show a message box on top of that form. Or you can try to show it with custom form that would have its own representation in taskbar like described here

Edited by Fr0sT.Brutal

Share this post


Link to post

This should just work. The real question is why your modal message box is not on top. You should try to work that out.

Share this post


Link to post
2 hours ago, David Heffernan said:

This should just work. The real question is why your modal message box is not on top. You should try to work that out.

Yesterday with a Delphi 11 application I had a situation when a modal formed showed normally on top of the main form. Opened another application and when moved back to the first application the modal form was behind the main windows. It was not possible to move the modal form forward.

 

When before calling showmodal set the  popupparent to be the main form the application behaved as should.

Share this post


Link to post
16 minutes ago, Lajos Juhász said:

Yesterday with a Delphi 11 application I had a situation when a modal formed showed normally on top of the main form. Opened another application and when moved back to the first application the modal form was behind the main windows. It was not possible to move the modal form forward.

 

When before calling showmodal set the  popupparent to be the main form the application behaved as should.

I don't experience this in my program. Can it be reproduced?

Share this post


Link to post
5 minutes ago, David Heffernan said:

I don't experience this in my program. Can it be reproduced?

Not with a new empty application and two forms. 

Share this post


Link to post
3 minutes ago, Lajos Juhász said:

Not with a new empty application and two forms. 

This is it then isn't it.  What's in the real program that causes the misbehaviour?

Share this post


Link to post
On 6/30/2022 at 12:41 PM, David Heffernan said:

This should just work. The real question is why your modal message box is not on top. You should try to work that out.

1) I show the form with ShowModal().

2) I press the button "Execute".

3) In a ButtonClick event handler I call Func().

4) Func performs long mathematical calculation.

5) During the calculation Func regularily calls Application->ProcessMessages().

6) During the calculation I often swith to other applications on my comp and do smth else.

7) At the end of calculation Func() calls Application->MessageBox().

I don't know, what other details could be important for the description of the situation.

Share this post


Link to post
Posted (edited)
5 hours ago, vshvetsov said:

3) In a ButtonClick event handler I call Func().

4) Func performs long mathematical calculation.

5) During the calculation Func regularily calls Application->ProcessMessages().

That is not a good design.  You should move the long calculation to a separate worker thread instead, and then exit the OnClick handler.  Have the thread post updates to the UI thread if needed. And you can disable your UI while the thread is running, and then re-enable it when the thread is finished.  Never block the UI thread, and avoid using Application->ProcessMessages() whenever possible, it causes more problems than it solves.  Let the UI thread run normally.

Quote

6) During the calculation I often swith to other applications on my comp and do smth else.

7) At the end of calculation Func() calls Application->MessageBox().

By doing that switching, the modal Form likely lost input focus and wasn't the "active" Form anymore at the time when MessageBox() was called, which likely ended up with the MessageBox dialog becoming owned by either another Form's window, or the hidden TApplication window.  That would account for why the MessageBox dialog was allowed to go behind your modal Form.  The modal Form's window needs to be the owner of the MessageBox dialog in order for the dialog to stay on top of that Form.  Which means, you need to either call the Win32 MessageBox() (or TaskDialog/Indirect()) function directly so you can explicitly pass in the desired owner window, or else use the TApplication/Events::OnGetActiveFormHandle event to provide the desired owner window even if the Form is not "active".

Edited by Remy Lebeau
  • Like 1

Share this post


Link to post

BTW, don't forget that any form's window could be recreated at run-time thus changing its handle

Share this post


Link to post
10 hours ago, Fr0sT.Brutal said:

BTW, don't forget that any form's window could be recreated at run-time thus changing its handle

Sure, if you use the Form's HWND for sending/posting updates.  A better option would be to use a persistent HWND that doesn't get recreated, such as the TApplication::Handle, or the result of calling AllocateHWnd(). Otherwise, don't even use an HWND at all, for instance use TThread::Synchronize() or TThread::Queue() instead.

Share this post


Link to post

Thanks a lot for the discussion. I think that the problem really comes from calls to ProcessMessages inside OnClick. I needed to switch to using threads for a very long time, but I still can’t choose the time. Does anyone know a good text about thread programming for beginners? I need
1) performing calculations with displaying progress in the window and the possibility of interruption,
2) parallelization.
Of course, there are many texts, but I will be grateful for a link to a good one.

Share this post


Link to post

You could follow the example included with Delphi, %samples%\Object Pascal\RTL\Parallel Library . It is a good example of a thread updating the UI and when to call synchronize. Parallel for is covered in the example.

 

Review / debug / step through that and you will know where to focus your reading and searches.

  • Thanks 1

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×