Modality, part 9: Setting the correct owner for modal UI, practical exam


Here's a question that came from a customer. You can answer it yourself based on what you know about modal UI. (If you're kind of rusty on the topic, you can catch up here.) I've left in some irrelevant details just to make things interesting.

Our program launches a helper program to display an Aero Wizard to guide the user through submitting a service request.

theWiz.DoModal(hwndMainFrame);

There are no EnableWindow calls leading up to or returning from this call. the DoModal handles things nicely.

When the user clicks "Cancel" to cancel the service request, we use a TaskDialog so we can get the new look for our confirmation message box. The task dialog setup goes like this:

TASKDIALOGCONFIG config = { 0 };
config.cbSize = sizeof(config);
config.hwndParent = hwndMainFrame;

When the user clicks "Yes" to cancel, then another window instead of our frame becomes active.

On a hunch, I replaced the task dialog with a Win32 message box

MessageBox(hwndMainFrame, ...);

and bingo, we get the correct behavior. When our wizard exits, the main frame receives focus.

I believe that the "automatic" modal behavior that comes with DoModal() that takes care of disabling and reenabling the main frame is somehow getting short-circuited by using TaskDialog from inside our PSM_QUERYCANCEL message handler.

Right now, we've switched to MessageBox, but we would much prefer to use the task dialog.

Although it's not common, it is legal to have a window's parent or owner belongs to another thread or process. But it definitely makes things a bit more tricky to manage because it attaches the input queues of the two threads, and you now have two threads coöperating to manage a single window hierarchy.

Is the cross-process window hierarchy a contributing factor to the problem, or is it just a red herring?

Comments (22)
  1. Anonymous says:

    Why TaskDialogIndirect? And also where is the hInst for the TASKDIALOGCONFIG…

  2. Anonymous says:

    From what I understand of the description of the problem, I think the TaskDialog should have used the Aero Wizard as its parent. This is assuming the cancel handling is done in the Wizard, prior to closing it. (The description of the workflow strongly implies that the confirmation is done before closing the wizard.)

    I don't understand why MessageBox() works properly. Maybe it automatically detects that the parent is disabled and search for its active child?

  3. doug.kavendek says:

    I'm assuming that the call to the wizard's DoModal() with hwndMainFrame as a parameter is setting hwndMainFrame as the owner of the modal wizard (if that's not the case, then never mind this whole thing).  The TaskDialog is setting hwndMainFrame as its parent.  This means that there's two different dialogs that have the same owner, unless I'm getting myself confused by the differences between parent and owner.  When the TaskDialog closes, it may re-enable and switch back to its owner, which is supposed to still be disabled and waiting on the wizard.  That seems like it's guaranteed to lead to strange behavior.

  4. Anonymous says:

    I have done cross-process owner/owned windows. It does work from native code, but managed code seemt to not like it.

    I'm guessing that corss-process is a red herring here.

  5. Anonymous says:

    I, too, would like clarification on whether the task dialog is shown prior to dismissing the wizard.  If so, then the task dialog's parent should be the wizard, not the original program.  Then the task dialog should return activation to the wizard which would in turn return it to the main window.

    But I'm not sure why the message box works when the task dialog doesn't.

  6. Anonymous says:

    What's with the umlaut on the second 'o' in 'cooperating' (last sentence of second-to-last paragraph)?  Is that a hint of some sort?  :-)

    [That's not an umlaut; it's a diaeresis. Coöperate is even called out by Wikipedia as an example. -Raymond]
  7. Anonymous says:

    @SMW

    Raymond is just trying to set a new trend, that's second time he writes cooperates this way this week…

    Well of course if this entry is several years old as many are, then it's just a coincidence both mistakes happen the same week!

    Or maybe Raymond is using his psychic powers to also write his comments several years before the story is published.

    Or maybe we are ignorant youngins and Raymond is fan of this blog: http://thedutras.blogspot.com/

  8. Anonymous says:

    Well… Raymond has answered in the time it took me to write the post, that's what I get for being slow :)

    Now I'm slow AND an ignorant youngin…

  9. Anonymous says:

    The Wikipedia entry also states that its use is uncommon in English, stating that it is optional.  I also don't read The New Yorker or the MIT Technology Review.  And since I have a German background and took German language classes way back in high school I naturally think it's an umlaut.  But then in English that would have been written differently.

    [When I was a kid, we were taught to spell with the diaeresis and to put the apostrophe in Hallowe'en. Now get off my lawn. -Raymond]
  10. Anonymous says:

    that's second time he writes co√∂perates this way this week…

    FTFY ;-)

    Regarding the actual question, in my own code, it would most likely be that I forgot to pass config along to code that was expecting it, and MessageBox would work because it didn't need the config.

  11. Anonymous says:

    Raymond may be channeling the New Yorker magazine which sticks to the diaresis on cooperate and coordinate, even though most magazines and newspapers have dropped the use.  I didn't think the English language had more than twenty-six letters, with no diacritical marks or accents — that's what I learned in elementary school.  But there's amoeba, and diarrhoea, where oe is combined into one letter in older writings.

  12. Anonymous says:

    Another process has the active Window and wants to give it away to another window?

    I think calling AllowSetForegroundWindow should do the trick.

    Maybe MessageBox handles this case to be backwards compatible to applications that don't know about the above.

  13. Anonymous says:

    Mmmm. I'm reading this in a tabbed IE frame. There are three tabs and four IE processes running, making me wonder if this is a case of a window's parent or owner belonging to another process.

  14. Anonymous says:

    The correct modal UI is none.

  15. Anonymous says:

    Totally unrelated, but I just went to a search engine to try to find one of your articles for a co-worker. I didn't narrow to search to "blogs.msdn.com", however. The first hit in the results was an article on my *own* web site that happens to mention *another* article of yours. I think I'm going to mention you in all of my articles now. ;)

  16. kinokijuf says:

    @Paul M. Parks: What post?

  17. Anonymous says:

    My guess: The TaskDialog is trying to set an owner window which is still disabled by the wizard. Maybe TaskDialog skips the EnableWindow/SetFocus calls for such a window while MessageBox doesn't.

  18. Anonymous says:

    Please can we learn to co-operate with Raymond? Thanks.

    I remember a bug in Windows 3.1 where the last active popup didn't get updated for a background window with a nested dialog. In particular we used an app framework whose default print preview was in a popup and you could trigger the bug by switching away from the app while the print progress dialog (which closed automatically when it had finished printing) was active. In Windows 95 the bug was alleviated by setting the last active popup to NULL, and the bug was completely fixed in at least Windows 2000 (it may have been fixed in prior Windows NT versions but I didn't test it there.)

  19. Anonymous says:

    Cross-process is a red herring; the wrong parent was used.

    DerekYu: while I agree in principle, how would you have solved the problem at hand without a modal confirmation message?

  20. Anonymous says:

    As presented, this problem makes no sense. MessageBox and TaskDialog both implement a a modal popup. With the same parent window being specified, and with both being called from the PSM_QUERYCANCEL message the behavior of the application should be the same. Unless TaskDialog and MessageBox have internal differences in how they implement their modality. Cross process is also a red herring as, again, both examples are doing the same cross process things the same way. Which means there is some undisclosed piece of information that makes one of the scenarios not modal, or it was actually the Butler, in the pantry, with the serving fork.

  21. Anonymous says:

    Yeah, I think the "irrelevant details" only wound up making the whole exercise more confusing, maybe that's the point since this is a real world situation? It seems the obvious correct answer is that the incorrect window is specified for hwndParent, as many have pointed out; however, that simple answer is now completely overshadowed by the "irrelevant details" on why there's a difference between TaskDialog and MessageBox (I'm leaning towards Pierre's theory), and how the use of incorrect hwndParent ultimately leads to the wrong window becoming active (perhaps a surface symptom of a more serious issue triggered by hwndMainFrame being re-enabled prematurely?)

    And it's amusing that the whole *topic* itself was for a short time sidetracked by the irrelevant topic of diaeresis. Yeah, this blog entry has become more interesting all right, for better or for worse. :p

    [Yup, real-life debugging is full of irrelevant details. (And missing details too!) -Raymond]
  22. Anonymous says:

    Raymond seems to answer this in his linked series (#5):

    blogs.msdn.com/…/379635.aspx

    "If you are displaying a modal dialog from another modal dialog, it is important to pass the correct window as the owner for the second dialog."

Comments are closed.