{"id":111817,"date":"2025-11-27T07:00:00","date_gmt":"2025-11-27T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=111817"},"modified":"2025-11-27T15:11:27","modified_gmt":"2025-11-27T23:11:27","slug":"20251127-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20251127-00\/?p=111817","title":{"rendered":"Message-only windows are for messaging, not as a convenient victim for hosting UI"},"content":{"rendered":"<p>A customer wanted to show a dialog box, but they didn&#8217;t want it to be attached to any of their existing windows because it was a general dialog that applied to all of their windows. So they had a great idea: Their program already had a message-only window, so they figured, &#8220;Hey, I already have this garbage window I can use as the owner of some UI.&#8221;<\/p>\n<p>They used their message-only window as the owner of their dialog box, and the dialog showed up on the screen. But wait, they did a closer reading of the message-only window documentation, and it said<\/p>\n<blockquote class=\"q\"><p>To create a message-only window, specify the HWND_MESSAGE constant or a handle to an existing message-only window in the <i>hWndParent<\/i> parameter of the <b>CreateWindowEx<\/b> function.<\/p><\/blockquote>\n<p>Since their existing message-only window is the owner of the dialog box (passed in the overloaded <code>hwndParent<\/code> parameter), the expectation is that the dialog box would itself be a message-only window and therefore not appear. Yet it appeared?<\/p>\n<p>The documentation for message-only windows is poorly-worded enough to be incorrect.<\/p>\n<p>The correct wording is that a message-only window is one whose parent or owner is <code>HWND_MESSAGE<\/code>.<\/p>\n<p>Children of message-only windows are technically not message-only windows, although a lot of message-only behaviors apply to them simply because they are a child of a non-visible parent window: They inherit the non-visibility from their parent, just like any other child window. And child windows are not enumerated by <code>FindWindow<\/code>, nor do they receive broadcast messages.<\/p>\n<p>(If I&#8217;m lucky, the documentation correction will be published before this article goes out.)<\/p>\n<p>But whether the precise definition of a message-only window is correct, it shouldn&#8217;t matter whether the mechanism works, because you shouldn&#8217;t do it.<\/p>\n<p>Message-only windows are windows that are <a title=\"What kind of messages can a message-only window receive?\" href=\"https:\/\/devblogs.microsoft.com\/oldnewthing\/20171218-00\/?p=97595\"> intended to receive only messages sent or posted directly to them<\/a>. You use them when you want to use the message queue as a communication channel rather than for hosting UI.<\/p>\n<p>So don&#8217;t host UI on a message-only window. That&#8217;s not what it&#8217;s for. If you want the dialog box to be independent of other top-level windows, you can use <code>NULL<\/code> as the owner, in which case it appears on the taskbar and in the <kbd>Alt<\/kbd>+<kbd>Tab<\/kbd> list. If you want to hide the dialog from the taskbar and the <kbd>Alt<\/kbd>+<kbd>Tab<\/kbd> list, then create your own hidden window (not message-only) and use that hidden window as the owner.\u00b9<\/p>\n<p><b>Bonus chatter<\/b>: If you use <code>HWND_<wbr \/>MESSAGE<\/code> as the owner window for functions like <code>Dialog\u00adBox<\/code> or <code>Message\u00adBox<\/code>, you&#8217;d think, &#8220;Oh, well, the dialog box will just become one big message-only window. The user can&#8217;t see it, but I can still manipulate it programmatically.&#8221; And then you find that, no, the <code>Dialog\u00adBox<\/code> and <code>Message\u00adBox<\/code> functions simply fail when given <code>HWND_<wbr \/>MESSAGE<\/code> as an owner. That&#8217;s because those functions, as part of their parameter validation, do an <code>IsWindow(hwndOwner)<\/code>, and <code>IsWindow(HWND_<wbr \/>MESSAGE)<\/code> is <code>FALSE<\/code> because and <code>HWND_<wbr \/>MESSAGE<\/code> is not a real window; it&#8217;s a sentinel value for <code>Create\u00adWindow<\/code> and <code>Find\u00adWindow\u00adEx<\/code>. As far as <code>Dialog\u00adBox<\/code> and <code>Message\u00adBox<\/code> are concerned, <code>HWND_<wbr \/>MESSAGE<\/code> is an invalid window, so passing it as <code>hwndOwner<\/code> fails with &#8220;invalid parameter&#8221;.<\/p>\n<p><b>Bonus bonus chatter<\/b>: Sometimes we see programs that build an entire universe inside a message-only window, and then use the <code>Set\u00adParent<\/code> trick to convert the message-only window into a non-message-only window, as if to say, &#8220;Ta-da! Here I am!&#8221; Please don&#8217;t do that. Building your universe probably involves sending internal messages (for example, child windows may send <code>WM_<wbr \/>PARENT\u00adNOTIFY<\/code> messages to their parent), which is sort of what message-only windows <i>don&#8217;t<\/i> want. &#8220;Don&#8217;t bother me with your own weird messages. I care only about my own messages.&#8221; Just keep your top-level window hidden and then show it when you&#8217;re ready.<\/p>\n<p>\u00b9 For keyboard accessibility, your options are either to put the dialog box in the <kbd>Alt<\/kbd>+<kbd>Tab<\/kbd> list, or to hide it from the <kbd>Alt<\/kbd>+<kbd>Tab<\/kbd> list but provide some other keyboard mechanism to get focus back to that dialog box. For example, you might say, &#8220;Go back to whatever you did to trigger the dialog box in the first place, and trigger it again.&#8221; This alternate solution is the mechanism that Windows 95&#8217;s Explorer used to put keyboard focus back onto a property sheet: Go back to the original thing and ask for Properties again. (Today, Explorer just puts the property sheet in the <kbd>Alt<\/kbd>+<kbd>Tab<\/kbd> list.)<\/p>\n","protected":false},"excerpt":{"rendered":"<p>If you want to host UI, use a real window (possibly hidden).<\/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-111817","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>If you want to host UI, use a real window (possibly hidden).<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111817","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=111817"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/111817\/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=111817"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=111817"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=111817"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}