{"id":112082,"date":"2026-02-24T07:00:00","date_gmt":"2026-02-24T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112082"},"modified":"2026-02-24T07:01:30","modified_gmt":"2026-02-24T15:01:30","slug":"20260224-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260224-00\/?p=112082","title":{"rendered":"Customizing the ways the dialog manager dismisses itself: Isolating the Close pathway"},"content":{"rendered":"<p>We started by <a title=\"Exploring the signals the dialog manager uses for dismissing a dialog\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260219-00\/?p=112072\"> exploring the signals the dialog manager uses for dismissing a dialog<\/a>. Now we can use that information to customize the dismiss behavior.<\/p>\n<p>Let&#8217;s start with a diagram, because people like diagrams.<\/p>\n<table class=\"cp3\" style=\"border-collapse: separate; text-align: center;\" border=\"0\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>WM_<wbr \/>SYS\u00adCOMMAND<\/tt>\/<br \/>\n<tt>SC_<wbr \/>CLOSE<\/tt><\/td>\n<td>\u2190<\/td>\n<td style=\"border: dashed 1px currentcolor; border-radius: 1ex;\">Close button\/<br \/>\n<kbd>Alt<\/kbd>+<kbd>F4<\/kbd>\/<br \/>\nSystem menu Close<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2193<\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>WM_<wbr \/>CLOSE<\/tt><\/td>\n<td>&nbsp;<\/td>\n<td style=\"border: dashed 1px currentcolor; border-radius: 1ex;\">User hits <kbd>ESC<\/kbd><\/td>\n<\/tr>\n<tr>\n<td>&nbsp;<\/td>\n<td>&nbsp;<\/td>\n<td>\u2193<\/td>\n<td>&nbsp;<\/td>\n<td>\u2193<\/td>\n<\/tr>\n<tr>\n<td style=\"border: dashed 1px currentcolor; border-radius: 1ex;\">User clicks<br \/>\nCancel button<\/td>\n<td>\u2192<\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>WM_<wbr \/>COMMAND<\/tt>\/<br \/>\n<tt>BN_CLICK<\/tt>\/<br \/>\n<tt>IDCANCEL<\/tt><\/td>\n<td>\u2190<\/td>\n<td style=\"border: solid 1px currentcolor;\"><tt>Is\u00adDialog\u00adMessage<\/tt><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>We noted at the end of that article that if you have a button whose ID is <code>IDCANCEL<\/code>, the dialog manager will defer to that button to decide whether to allow <kbd>ESC<\/kbd> key, the Close button, or other nonclient affordances to convert the corresponding action to a simulated click on that button. So the obvious way to take advantage of this is to put a Cancel button on your dialog box, and disable it when you don&#8217;t want the user to dismiss the dialog box with the <code>ESC<\/code> key, the Close button, or other standard affordances.\u00b9<\/p>\n<p>Notice that everything in the diagram funnels into <code>WM_<wbr \/>COMMAND<\/code>\/<wbr \/><code>IDCANCEL<\/code>, even if you don&#8217;t actually have a control whose ID is <code>IDCANCEL<\/code>. If you add a handler to your dialog procedure for <code>WM_<wbr \/>COMMAND<\/code>, then all of the actions will come through that handler, and you can customize the behavior at that point. You could call <code>EndDialog<\/code> to close the dialog or just return without doing anything to keep the dialog open.<\/p>\n<p>Now, if you have no intention of closing the dialog in response to the Close button or the system menu Close command, then you probably shouldn&#8217;t leave them enabled. You can gray those out by doing<\/p>\n<pre>    EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE,\r\n                   MF_BYCOMMAND | MF_DISABLED | MF_GRAYED);\r\n<\/pre>\n<p>Okay, but what if you want to treat the Close button differently?<\/p>\n<p>From the diagram, we see that the Close button goes through a <code>WM_<wbr \/>CLOSE<\/code> phase, so you can handle the <code>WM_<wbr \/>CLOSE<\/code> message in your dialog procedure to do whatever custom Close button behavior you want. If you return <code>TRUE<\/code> from the dialog procedure, then that will mark the message as handled, and further processing will stop. if you return <code>FALSE<\/code>, then the <code>Def\u00adDlg\u00adProc<\/code> will turn the <code>WM_<wbr \/>CLOSE<\/code> into the <code>WM_<wbr \/>COMMAND<\/code> message as before.<\/p>\n<p>If you want to treat the <kbd>ESC<\/kbd> key differently from a click on the Cancel button, you&#8217;ll have to intercept it on the <code>Is\u00adDialog\u00adMessage<\/code> path. We&#8217;ll look at that next time.<\/p>\n<p>\u00b9 Now, you might notice that there is no requirement that the <code>IDCANCEL<\/code> button be in the tab order, or that it even be visible. The dialog manager merely checks whether it is enabled. Therefore, you might be tempted to control these actions by disabling your (invisible, non-tabbable) <code>IDCANCEL<\/code> button. This is sneaky, but it will probably confuse assistive technology tools, and anybody else who inspects the window hierarchy, and besides, it&#8217;s more complicated than the other alternatives presented here.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Intercepting the flow in your message loop.<\/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-112082","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Intercepting the flow in your message loop.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112082","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=112082"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112082\/revisions"}],"predecessor-version":[{"id":112083,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112082\/revisions\/112083"}],"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=112082"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112082"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112082"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}