{"id":26783,"date":"2007-05-21T10:00:00","date_gmt":"2007-05-21T10:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2007\/05\/21\/dont-be-helpless-i-dont-know-anything-about-mfc-modal-loops-but-unlike-some-people-im-not-afraid-to-find-out\/"},"modified":"2007-05-21T10:00:00","modified_gmt":"2007-05-21T10:00:00","slug":"dont-be-helpless-i-dont-know-anything-about-mfc-modal-loops-but-unlike-some-people-im-not-afraid-to-find-out","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20070521-00\/?p=26783","title":{"rendered":"Don&#039;t be helpless: I don&#039;t know anything about MFC modal loops, but unlike some people, I&#039;m not afraid to find out"},"content":{"rendered":"<p>\n<a HREF=\"http:\/\/blogs.msdn.com\/oldnewthing\/pages\/407234.aspx#430296\">\nCommenter Tom Grelinger asks via the Suggestion Box<\/a>:\n<\/p>\n<blockquote CLASS=\"q\"><p>\nIf I have a modal CDialog that is visible and usable to the user.\nLet&#8217;s say I receive an event somewhere else in the program\nand I call DestroyWindow on the modal CDialog from within the event.\nI notice that the OnDestroy is called on the CDialog,\nbut DoModal never exits until a WM_QUIT is posted to the modal&#8217;s message pump.\nWhat are the pitfalls to this?\nUnfortunately, there is really no way to avoid this situation.\n<\/p><\/blockquote>\n<p>\nI&#8217;m not sure what the question is, actually.\nThe question as stated is &#8220;What are the pitfalls to this?&#8221;\nbut he answered that in his own question:\nThe pitfall is that &#8220;DoModal never exits until a <code>WM_QUIT<\/code> is\nposted to the modal dialog&#8217;s message pump.&#8221;\n<\/p>\n<p>\nI&#8217;m going to assume that the question really is,\n&#8220;Why doesn&#8217;t destroying the window work?&#8221;\nwith the follow-up question,\n&#8220;What is the correct way to dismiss a modal dialog?&#8221;\n<\/p>\n<p>\nThe first problem with this question is that it assumes that I know\nwhat a <code>CDialog<\/code> is.\nFrom its name, I&#8217;m going to assume that this\nis an MFC class for managing a dialog box.\nBut you don&#8217;t even have to know that to answer the first\nreformulated question operating only from Win32 principles:\n<code>DestroyWindow<\/code> is not how you exit a modal dialog.\nYou exit a modal dialog with <code>EndDialog<\/code>.\nThe <code>DestroyWindow<\/code> technique is for <i>modeless<\/i>\ndialogs.\n<\/p>\n<p>\nBut let&#8217;s look at the question another way,\nwhich is my point for today:\nYou have the MFC source code.\nDon&#8217;t be afraid to read it.\nEspecially since I don&#8217;t use MFC personally;\nI don&#8217;t even know the basic principles of application design with MFC.\nI work in straight Win32.\nAs a result,\nI don&#8217;t know the answer off the top of my\nhead, but fifteen minutes reading the MFC source code quickly reveals\nthe reason why destroying the window doesn&#8217;t work.\n<\/p>\n<p>\nWatch me as I go and find out the answer.\nIt&#8217;s nothing you can&#8217;t already do yourself.\n<\/p>\n<p>\nThe\n<code>CDialog::DoModal<\/code> method\ncalls <code>CWnd::RunModalLoop<\/code> to run the dialog loop.\nIf you look at <code>CWnd::RunModalLoop<\/code>,\nyou can see the conditions under which it will exit the modal loop.\nHere&#8217;s the code with irrelevant details deleted.\n(They&#8217;re irrelevant because they have nothing to do with how the\nmodal loop exits.)\n<\/p>\n<pre>\nint CWnd::RunModalLoop(DWORD dwFlags)\n{\n    ... preparatory work ...\n    \/\/ acquire and dispatch messages until the modal state is done\n    for (;;)\n    {\n        ... code that doesn't break out of the loop ...\n        \/\/ phase2: pump messages while available\n        do\n        {\n            \/\/ pump message, but quit on WM_QUIT\n            if (!AfxGetThread()-&gt;PumpMessage())\n            {\n                AfxPostQuitMessage(0);\n                return -1;\n            }\n            ... other code that doesn't break out of the loop ...\n            if (!ContinueModal())\n                goto ExitModal;\n            ... other code that doesn't break the loop ...\n        }  while (::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))\n    }\nExitModal:\n    m_nFlags &amp;= ~(WF_MODALLOOP|WF_CONTINUEMODAL);\n    return m_nModalResult;\n}\n<\/pre>\n<p>\nThere are only two ways out of this loop.\nThe first is the receipt of a <code>WM_QUIT<\/code> message.\nThe second is if <code>CWnd::ContinueModal<\/code> decides that\nthe modal loop is finished.\nThe commenter already mentioned the quit message aspect to the\nmodal loop, so that just leaves <code>CWnd::ContinueModal<\/code>.\n<\/p>\n<p>\nThe <code>CWnd::ContinueModal<\/code> method is very simple:\n<\/p>\n<pre>\nBOOL CWnd::ContinueModal()\n{\n    return m_nFlags &amp; WF_CONTINUEMODAL;\n}\n<\/pre>\n<p>\nTherefore, the only other way the loop can exit is if somebody\nclears the <code>WF_CONTINUEMODAL<\/code> flag.\nA little grepping shows that there are only three places where this\nflag is cleared.\nOne is in <code>CPropertyPage<\/code>, which is a derived class\nof <code>CDialog<\/code> and therefore isn&#8217;t relevant here.\n(I&#8217;ll ignore <code>CPropertyPage<\/code> in future searches.)\nThe second is in the line above right after the label\n<code>ExitModal<\/code>.\nAnd the third is this method:\n<\/p>\n<pre>\nvoid CWnd::EndModalLoop(int nResult)\n{\n    \/\/ this result will be returned from CWnd::RunModalLoop\n    m_nModalResult = nResult;\n    \/\/ make sure a message goes through to exit the modal loop\n    if (m_nFlags &amp; WF_CONTINUEMODAL)\n    {\n        m_nFlags &amp;= ~WF_CONTINUEMODAL;\n        PostMessage(WM_NULL);\n    }\n}\n<\/pre>\n<p>\nThis method is called in only one place:\n<\/p>\n<pre>\nvoid CDialog::EndDialog(int nResult)\n{\n    if (m_nFlags &amp; (WF_MODALLOOP|WF_CONTINUEMODAL))\n        EndModalLoop(nResult);\n    ::EndDialog(m_hWnd, nResult);\n}\n<\/pre>\n<p>\nFollowing the money one last step,\nthe <code>CDialog::EndDialog<\/code> method is called\nfrom four places in <code>CDialog<\/code>.\nIt&#8217;s called from <code>CDialog::HandleInitDialog<\/code> and\n<code>CDialog::InitDialog<\/cODE> if some catastrophic error\noccurs during dialog initialization.\nAnd it's called from <code>CDialog::OnOK<\/code>\nand <code>CDialog::OnCancel<\/code> in response to the\nuser clicking the OK or Cancel buttons.\n<\/p>\n<p>\nNotice that the <code>CDialog::EndDialog<\/code> method is not\ncalled when somebody forcibly destroys the dialog from\nthe outside.\n<\/p>\n<p>\nThat's why destroying the dialog window doesn't break the modal loop.\nIf you want to break out of the modal loop, your only choices are\nto post a quit message or call <code>CWnd::EndModalLoop<\/code>,\neither directly or indirectly (via <code>CDialog::EndDialog<\/code>,\nfor example).\n<\/p>\n<p>\nNotice that the MFC modal loop obeys the convention on quit messages\nby re-posting the quit message when it breaks out of the modal loop.\n(Though it really should have posted the <code>wParam<\/code> from\nthe quit message rather than just posting zero.)\n<\/p>\n<p>\nThe workaround therefore is not to destroy the dialog with\n<code>DestroyWindow<\/code> (something you should have known\nnot to do <i>a priori<\/i> since that's not how you exit\nmodal dialog boxes) but rather by calling\n<code>CDialog::EndDialog<\/code>, passing a result code that\nlets the caller of <code>CDialog::DoModal<\/code> know that\nthe dialog box exited under unusual circumstances.\n<\/p>\n<p>\nThis took me fifteen minutes to research and a little over an hour to\nwrite up.\nAll this work to answer a question that you should have been able\nto answer yourself with a little elbow grease.\nYou're a smart person.\nHave confidence in yourself.\nYou can do it.\nI know you can.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Commenter Tom Grelinger asks via the Suggestion Box: If I have a modal CDialog that is visible and usable to the user. Let&#8217;s say I receive an event somewhere else in the program and I call DestroyWindow on the modal CDialog from within the event. I notice that the OnDestroy is called on the CDialog, [&hellip;]<\/p>\n","protected":false},"author":1069,"featured_media":111744,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[25],"class_list":["post-26783","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Commenter Tom Grelinger asks via the Suggestion Box: If I have a modal CDialog that is visible and usable to the user. Let&#8217;s say I receive an event somewhere else in the program and I call DestroyWindow on the modal CDialog from within the event. I notice that the OnDestroy is called on the CDialog, [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/26783","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/users\/1069"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/comments?post=26783"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/26783\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media\/111744"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/media?parent=26783"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=26783"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=26783"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}