{"id":112074,"date":"2026-02-20T07:00:00","date_gmt":"2026-02-20T15:00:00","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/oldnewthing\/?p=112074"},"modified":"2026-02-20T08:28:31","modified_gmt":"2026-02-20T16:28:31","slug":"20260220-00","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20260220-00\/?p=112074","title":{"rendered":"Customizing the ways the dialog manager dismisses itself: Detecting the ESC key, first (failed) attempt"},"content":{"rendered":"<p>Suppose you want to distinguish between dismissing a dialog by pressing <kbd>ESC<\/kbd> and dismissing a dialog by clicking the Close button. One suggestion I saw was to call <code>Get\u00adAsync\u00adKey\u00adState(<wbr \/>VK_<wbr \/>ESCAPE)<\/code> to check whether the <kbd>ESC<\/kbd> is down.<\/p>\n<p>In general, any time you see <code>Get\u00adAsync\u00adKey\u00adState<\/code>, you should be suspicious, since <code>Get\u00adAsync\u00adKey\u00adState<\/code> checks the state of the keyboard at the moment it is called, which might not be relevant to your window if it asynchronously lost keyboard focus, and which (from the point of view of your program) might even be from the future.<\/p>\n<p>Recall that the system maintains two types of keyboard states. One is the <i>synchronous<\/i> keyboard state, which represents the state of the keyboard as far as your program is aware. If your program received a <code>WM_<wbr \/>KEYDOWN<\/code> for the space bar, then <code>Get\u00adKey\u00adState<\/code> will report that the space bar is pressed. Even if the user releases the space bar, <code>Get\u00adKey\u00adState<\/code> will continue to report that the space bar is pressed until the program receives a <code>WM_<wbr \/>KEYUP<\/code> for the space bar.<\/p>\n<p>The idea here is that your program is processing an input stream, and due to the nature of multitasking and the physics of time, it&#8217;s possible that your program is catching up to input that actually occurred some time ago. For example, maybe the user typed ahead into the program while it was unresponsive, and now that the program has become responsive again, it is playing catch-up with all the typing that occurred 30 seconds ago.<\/p>\n<p>If the program receives, say, a press of the <kbd>F2<\/kbd> key and wants to know whether to do the <kbd>Ctrl<\/kbd>+<kbd>F2<\/kbd> hotkey, it doesn&#8217;t know want to know whether the <kbd>Ctrl<\/kbd> key is down at the moment it is processing its input backlog. It wants to know whether the <kbd>Ctrl<\/kbd> key was down at the time the <kbd>F2<\/kbd> was pressed.<\/p>\n<table style=\"border-collapse: collapse;\" border=\"1\" cellspacing=\"0\" cellpadding=\"3\">\n<tbody>\n<tr>\n<th>Time<\/th>\n<th>Event<\/th>\n<\/tr>\n<tr>\n<td>1<\/td>\n<td>User presses <kbd>Ctrl<\/kbd><\/td>\n<\/tr>\n<tr>\n<td>2<\/td>\n<td>User presses <kbd>F2<\/kbd><\/td>\n<\/tr>\n<tr>\n<td>3<\/td>\n<td>User releases <kbd>F2<\/kbd><\/td>\n<\/tr>\n<tr>\n<td>4<\/td>\n<td>User releases <kbd>Ctrl<\/kbd><\/td>\n<\/tr>\n<tr>\n<td>5<\/td>\n<td>Program receives <kbd>Ctrl<\/kbd> down<\/td>\n<\/tr>\n<tr>\n<td>6<\/td>\n<td>Program receives <kbd>F2<\/kbd> down<\/td>\n<\/tr>\n<tr>\n<td valign=\"top\">7<\/td>\n<td>Program uses <code>Get\u00adAsync\u00adKey\u00adState<\/code> to ask if <kbd>Ctrl<\/kbd> is down<br \/>\nAnswer: No<br \/>\nProgram does <kbd>F2<\/kbd> action instead of <kbd>Ctrl<\/kbd>+<kbd>F2<\/kbd><\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>When the program asks via <code>Get\u00adAsync\u00adKey\u00adState<\/code> whether the <kbd>Ctrl<\/kbd> key is down, the answer is &#8220;No, it&#8217;s not down. It was released at some future point in time you haven&#8217;t learned about yet.&#8221; And so the program does its <kbd>F2<\/kbd> action instead of <kbd>Ctrl<\/kbd>+<kbd>F2<\/kbd>, and you get a bug report that goes like &#8220;When the program is under heavy load, the <kbd>Ctrl<\/kbd>+<kbd>F2<\/kbd> hotkey doesn&#8217;t work.&#8221;<\/p>\n<p>Suppose your program wants to discard changes when the user dismisses the dialog with <kbd>ESC<\/kbd> but wants to save changes when the user dismisses the dialog with the Close button. Checking the asynchronous state of the <kbd>ESC<\/kbd> key will tell you whether the <kbd>ESC<\/kbd> is down right now, but not whether the <kbd>ESC<\/kbd> was down at the time the system generated the <code>IDCANCEL<\/code>. You&#8217;re going to get bugs like, &#8220;When the program is under heavy load, pressing <kbd>ESC<\/kbd> to dismiss the dialog sometimes saves the changes instead of discarding them.&#8221;<\/p>\n<p>Of course, the bug report probably isn&#8217;t going to be so kind as to mention that the program was under heavy load or that the <kbd>ESC<\/kbd> accidentally saved the changes. It&#8217;ll probably just say &#8220;Sometimes I see changes that I&#8217;m sure I had discarded.&#8221; And you&#8217;ll have to figure out what the necessary conditions are for that bug to manifest itself.<\/p>\n<p><b>Bonus chatter<\/b>: I don&#8217;t know why people love to use <code>Get\u00adAsync\u00adKey\u00adState<\/code> so much. It has a longer, more cumbersome name than the largely-ignored <code>Get\u00adKey\u00adState<\/code> function. Maybe people think that the longer, more cumbersome name means that it&#8217;s somehow &#8220;more fancy&#8221;.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Sniffing the asynchronous keyboard state.<\/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-112074","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Sniffing the asynchronous keyboard state.<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112074","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=112074"}],"version-history":[{"count":1,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112074\/revisions"}],"predecessor-version":[{"id":112075,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/112074\/revisions\/112075"}],"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=112074"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=112074"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=112074"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}