{"id":9263,"date":"2011-10-26T07:00:00","date_gmt":"2011-10-26T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2011\/10\/26\/how-can-i-get-notified-when-some-other-window-is-destroyed\/"},"modified":"2011-10-26T07:00:00","modified_gmt":"2011-10-26T07:00:00","slug":"how-can-i-get-notified-when-some-other-window-is-destroyed","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20111026-00\/?p=9263","title":{"rendered":"How can I get notified when some other window is destroyed?"},"content":{"rendered":"<p>\nA customer wanted to know whether there was a method\n(other than polling) to monitor another window and find\nout when it gets destroyed.\nThe goal was to automate some operation,\nand one of the steps was to wait until\nsome program closed its XYZ window before moving on to the next step.\nFinding the XYZ window could be done with a <code>Find&shy;Window<\/code>,\nbut since the window belongs to another process, you can&#8217;t subclass it\nto find out when it gets destroyed.\n<\/p>\n<p>\nEnter accessibility.\n<\/p>\n<p>\nThe <code>Set&shy;Win&shy;Event&shy;Hook<\/code> function\nlets you monitor accessibility events,\nand you can do it globally,\nfor a particular process,\nor for a particular thread.\nSince we&#8217;re interested in just one specific window,\nwe can restrict our monitoring to a specific process and thread.\n(You don&#8217;t want to monitor too much or you end up getting\nspammed with notifications you don&#8217;t care about,\nwhich will annoy both you and the end users who are wondering why\nall their CPU is being consumed on pointless activity.)\n<\/p>\n<p>\nLet&#8217;s take our scratch program and have it monitor an arbitrary\nwindow whose name is passed on the command line.\n<\/p>\n<pre>\nHWND g_hwnd; \/* our main window *\/\nHWND g_hwndTarget; \/* the window we are monitoring *\/\nHWINEVENTHOOK g_hweh;\nvoid CALLBACK WinEventProc(\n    HWINEVENTHOOK hWinEventHook,\n    DWORD         event,\n    HWND          hwnd,\n    LONG          idObject,\n    LONG          idChild,\n    DWORD         idEventThread,\n    DWORD         dwmsEventTime)\n{\n if (event == EVENT_OBJECT_DESTROY &amp;&amp;\n     hwnd == g_hwndTarget &amp;&amp;\n     idObject == OBJID_WINDOW &amp;&amp;\n     idChild == INDEXID_CONTAINER) {\n  PostMessage(g_hwnd, WM_CLOSE, 0, 0);\n }\n}\n<\/pre>\n<p>\nThe <code>Win&shy;Event&shy;Hook<\/code> function is where it all happens.\nIf our callback is told that a window was destroyed,\nand the window handle matches the one we are monitoring,\nthen post ourselves a <code>WM_CLOSE<\/code> message,\nwhich will close the window and exit the program.\n<\/p>\n<p>\nThe rest is just scaffolding to get to the point where our\n<code>Win&shy;Event&shy;Hook<\/code> gets called.\n<\/p>\n<pre>\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n <font COLOR=\"blue\">DWORD dwProcessId;\n DWORD dwThreadId = GetWindowThreadProcessId(g_hwndTarget,\n                                            &amp;dwProcessId);\n if (dwThreadId)\n g_hweh = SetWinEventHook(\n     EVENT_OBJECT_DESTROY, EVENT_OBJECT_DESTROY,\n     NULL, WinEventProc,\n     dwProcessId, dwThreadId, WINEVENT_OUTOFCONTEXT);\n return g_hweh != NULL;<\/font>\n}\n<\/pre>\n<p>\nTo register the hook, we obtain the thread ID and process ID\nof the window we are interested in tracking,\nthen use the <code>Set&shy;Win&shy;Event&shy;Hook<\/code> function to register\nour callback function,\nsaying that we want to receive only <code>EVENT_OBJECT_DESTROY<\/code>\nnotifications by passing it as both the <code>event&shy;Min<\/code>\nand <code>event&shy;Max<\/code>.\nWe give it our callback function, and since we ask for\n<code>WIN&shy;EVENT_OUT&shy;OF&shy;CONTEXT<\/code>, we don&#8217;t need to pass\na module handle since we are not requesting injection.\n<\/p>\n<p>\nNotice that we restrict our hook as much as we can.\nWe specify that we care only about one event,\nand we are interested in only one process and only one thread.\nIt&#8217;s generally a good idea to restrict the hook as much as possible.\n<\/p>\n<p>\nOf course, we also have to unregister the hook when we&#8217;re done.\n<\/p>\n<pre>\nvoid\nOnDestroy(HWND hwnd)\n{\n <font COLOR=\"blue\">if (g_hweh) UnhookWinEvent(g_hweh);<\/font>\n PostQuitMessage(0);\n}\n<\/pre>\n<p>\nAnd finally, we use our command line to specify the title of the\nwindow we are monitoring.\n<\/p>\n<pre>\nint WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,\n                   LPSTR lpCmdLine, int nShowCmd)\n{\n ...\n  <font COLOR=\"blue\">g_hwndTarget = FindWindowA(lpCmdLine);<\/font>\n  <font COLOR=\"blue\">g_hwnd =<\/font>\n  hwnd = CreateWindow(\n ...\n}\n<\/pre>\n<p>\nWith the Run dialog open, run this program with the command line\nargument <code>Run<\/code>.\nThe program window opens, and when you click <i>Cancel<\/i> in the\nRun dialog, the program window closes.\nWow that was exciting.\n<\/p>\n<p>\n<b>Bonus chatter<\/b>:\nRemember that\n<a HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2011\/03\/04\/10136703.aspx\">\nthe window manager needs a message pump\nin order to call you back unexpectedly<\/a>.\n<\/p>\n<p>\n<b>Exercise<\/b>:\nSince we registered for only one thing, why did we have to\nperform the tests in <code>Win&shy;Event&shy;Proc<\/code>?\nWhy not just simplify the function to this?\n<\/p>\n<pre>\n<i>void CALLBACK WinEventProc(\n    HWINEVENTHOOK hWinEventHook,\n    DWORD         event,\n    HWND          hwnd,\n    LONG          idObject,\n    LONG          idChild,\n    DWORD         idEventThread,\n    DWORD         dwmsEventTime)\n{\n PostMessage(g_hwnd, WM_CLOSE, 0, 0);\n}<\/i>\n<\/pre>\n<p>\n<b>Exercise<\/b>:\nWith the Run dialog open, run this program with the command line\nargument <code>Run<\/code>.\nNow instead of clicking <i>Cancel<\/i> in the Run dialog,\ntype some garbage into the edit control and then click OK.\nThe Run dialog goes away and an error message appears instead.\nWhy is the scratch program still running?<\/p>\n","protected":false},"excerpt":{"rendered":"<p>A customer wanted to know whether there was a method (other than polling) to monitor another window and find out when it gets destroyed. The goal was to automate some operation, and one of the steps was to wait until some program closed its XYZ window before moving on to the next step. Finding the [&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-9263","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>A customer wanted to know whether there was a method (other than polling) to monitor another window and find out when it gets destroyed. The goal was to automate some operation, and one of the steps was to wait until some program closed its XYZ window before moving on to the next step. Finding the [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/9263","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=9263"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/9263\/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=9263"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=9263"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=9263"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}