{"id":4093,"date":"2013-06-13T07:00:00","date_gmt":"2013-06-13T07:00:00","guid":{"rendered":"https:\/\/blogs.msdn.microsoft.com\/oldnewthing\/2013\/06\/13\/a-big-little-program-monitoring-internet-explorer-and-explorer-windows-part-2-tracking-navigations\/"},"modified":"2013-06-13T07:00:00","modified_gmt":"2013-06-13T07:00:00","slug":"a-big-little-program-monitoring-internet-explorer-and-explorer-windows-part-2-tracking-navigations","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/oldnewthing\/20130613-00\/?p=4093","title":{"rendered":"A big little program: Monitoring Internet Explorer and Explorer windows, part 2: Tracking navigations"},"content":{"rendered":"<p><P>\nOkay, it&#8217;s been a while since we\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/06\/10\/10424662.aspx\">\nset aside our Little Program<\/A>\nto learn a bit about\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/06\/11\/10424940.aspx\">\nconnection points<\/A>\nand\n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/06\/12\/10425215.aspx\">\nusing dispatch interfaces as connection point interfaces<\/A>.\nNow we can put that knowledge to use.\n<\/P>\n<P>\nInternet Explorer and Explorer windows fire a group of events known as\n<A HREF=\"http:\/\/msdn.microsoft.com\/library\/aa768309\">\n<CODE>DWeb&shy;Browser&shy;Events<\/CODE><\/A>,\nso we just need to listen on those events to follow\nthe window as it navigates around.\n<\/P>\n<P>\nTake our \n<A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2003\/07\/23\/54576.aspx\">\nscratch program<\/A>\nand make these changes:\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/07\/15\/184076.aspx\">UNICODE<\/A>\n#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2004\/02\/12\/71851.aspx\">_UNICODE<\/A><\/FONT>\n#define STRICT\n<FONT COLOR=\"blue\">#define <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/01\/24\/10387757.aspx\">STRICT_TYPED_ITEMIDS<\/A><\/FONT>\n#include &lt;windows.h&gt;\n#include &lt;windowsx.h&gt;\n#include &lt;ole2.h&gt;\n#include &lt;commctrl.h&gt;\n#include &lt;shlwapi.h&gt;<\/p>\n<p><FONT COLOR=\"blue\">#include &lt;shlobj.h&gt;\n#include &lt;atlbase.h&gt;\n#include &lt;atlalloc.h&gt;\n#include &lt;exdisp.h&gt;\n#include &lt;exdispid.h&gt;<\/FONT><\/p>\n<p>&#8230;\n\/\/ <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/06\/12\/10425215.aspx\">DispInterfaceBase<\/A> incorporated by reference<\/p>\n<p><FONT COLOR=\"blue\">void UpdateText(HWND hwnd, PCWSTR pszText);<\/p>\n<p>class CWebBrowserEventsSink :\n    public CDispInterfaceBase&lt;DWebBrowserEvents&gt;<\/p>\n<p>public:\n CWebBrowserEventsSink(HWND hwnd) : m_hwnd(hwnd) { }<\/p>\n<p> IFACEMETHODIMP SimpleInvoke(\n    DISPID dispid, DISPPARAMS *pdispparams, VARIANT *pvarResult)\n {\n  switch (dispid) {\n  case DISPID_NAVIGATECOMPLETE:\n   UpdateText(m_hwnd, pdispparams-&gt;rgvarg[0].bstrVal);\n   break;<\/p>\n<p>  case DISPID_QUIT:\n   UpdateText(m_hwnd, L&#8221;&lt;exited&gt;&#8221;);\n   Disconnect();\n   break;\n  }\n  return S_OK;\n };<\/p>\n<p>private:\n HWND m_hwnd;\n};<\/FONT>\n<\/PRE>\n<P>\nOur event sink class listens for\n<CODE>DISPID_NAVIGATE&shy;COMPLETE<\/CODE>\nand\n<CODE>DISPID_QUIT<\/CODE>\nand updates the text with the new navigation location\nor the string <CODE>L&#8221;&lt;exited&gt;&#8221;<\/CODE> if the\nwindow exited.\nIn the exit case, we also disconnect from the connection\npoint to break the circular reference.\n<\/P>\n<P>\nThe IDL file for <CODE>Navigate&shy;Complete<\/CODE> says\n<\/P>\n<PRE>\n[id(DISPID_NAVIGATECOMPLETE), helpstring(&#8220;&#8230;&#8221;)]\nvoid NavigateComplete([in] BSTR URL );\n<\/PRE>\n<P>\nTherefore, we know that the URL parameter arrives as\na <CODE>VT_BSTR<\/CODE> in position zero,\nso we can access it as\n<CODE>pdispparams-&gt;rgvarg[0].bstrVal<\/CODE>.\n<\/P>\n<P>\nThat class is basically the guts of the program.\n<A HREf=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2003\/11\/12\/55659.aspx\">\nThe rest is scaffolding<\/A>.\nLike hooking up this guy to a listview item\nso it can report its findings somewhere.\n<\/P>\n<PRE>\nstruct ItemInfo\n{\n ItemInfo(HWND hwnd, IDispatch *pdisp)\n  : hwnd(hwnd) {\n  spSink.Attach(new(std::nothrow) CWebBrowsrEventsSink(hwnd));\n  if (spSink) spSink-&gt;Connect(pdisp);\n }\n ~ItemInfo() { if (spSink) spSink-&gt;Disconnect(); }<\/p>\n<p> HWND hwnd;\n CComPtr&lt;CWebBrowserEventsSink&gt; spSink;\n};<\/p>\n<p>ItemInfo *GetItemByIndex(int iItem)\n{\n LVITEM item;\n item.mask = LVIF_PARAM;\n item.iItem = iItem;\n item.iSubItem = 0;\n item.lParam = 0;\n ListView_GetItem(g_hwndChild, &amp;item);\n return reinterpret_cast&lt;ItemInfo *&gt;(item.lParam);\n}<\/p>\n<p>ItemInfo *GetItemByWindow(HWND hwnd, int *piItem)\n{\n int iItem = ListView_GetItemCount(g_hwndChild);\n while (&#8211;iItem &gt;= 0) {\n  ItemInfo *pii = GetItemByIndex(iItem);\n  if (pii-&gt;hwnd == hwnd) {\n   if (piItem) *piItem = iItem;\n   return pii;\n  }\n }\n return nullptr;\n}<\/p>\n<p>void UpdateText(HWND hwnd, PCWSTR pszText)\n{\n int iItem;\n if (GetItemByWindow(hwnd, &amp;iItem)) {\n  ListView_SetItemText(g_hwndChild, iItem, 0,\n                       const_cast&lt;PWSTR&gt;(pszText));\n }\n}\n<\/PRE>\n<P>\nAttached to each listview item is an\n<CODE>Item&shy;Info<\/CODE> structure\nwhich remembers the browser window it is associated with\nand the event sink that is listening for events.\n<\/P>\n<PRE>\n\/\/ GetLocationFromView, GetLocationFromBrowser, and GetBrowserInfo\n\/\/ <A HREF=\"http:\/\/blogs.msdn.com\/b\/oldnewthing\/archive\/2013\/06\/10\/10424662.aspx\">incorporated by reference<\/A><\/p>\n<p>CComPtr&lt;IShellWindows&gt; g_spWindows;<\/p>\n<p>\/\/ rename DumpWindows to BuildWindowList\nHRESULT <FONT COLOR=\"blue\">BuildWindowList<\/FONT>()\n{\n CComPtr&lt;IUnknown&gt; spunkEnum;\n HRESULT hr = g_spWindows-&gt;_NewEnum(&amp;spunkEnum);\n if (FAILED(hr)) return hr;<\/p>\n<p> CComQIPtr&lt;IEnumVARIANT&gt; spev(spunkEnum);\n for (CComVariant svar;\n      spev-&gt;Next(1, &amp;svar, nullptr) == S_OK;\n      svar.Clear()) {\n  if (svar.vt != VT_DISPATCH) continue;<\/p>\n<p>  HWND hwnd;\n  CComHeapPtr&lt;WCHAR&gt; spszLocation;\n  if (FAILED(GetBrowserInfo(svar.pdispVal,\n             &amp;hwnd, &amp;spszLocation))) continue;<\/p>\n<p>  <FONT COLOR=\"blue\">ItemInfo *pii =\n            new(std::nothrow) ItemInfo(hwnd, svar.pdispVal);\n  if (!pii) continue;<\/p>\n<p>  LVITEM item;\n  item.mask = LVIF_TEXT | LVIF_PARAM;\n  item.iItem = MAXLONG;\n  item.iSubItem = 0;\n  item.pszText = spszLocation;\n  item.lParam = reinterpret_cast&lt;LPARAM&gt;(pii);\n  int iItem = ListView_InsertItem(g_hwndChild, &amp;item);\n  if (iItem &lt; 0) delete pii;\n }\n return S_OK;\n}<\/FONT>\n<\/PRE>\n<P>\nTo build the window list, we enumerate\nthe contents of the <CODE>IShell&shy;Windows<\/CODE>.\nFor each window, we get its window handle\nand current location\nand create a listview item for it.\nThe reference data for the listview item is the\n<CODE>Item&shy;Info<\/CODE>.\n<\/P>\n<PRE>\nBOOL\nOnCreate(HWND hwnd, LPCREATESTRUCT lpcs)\n{\n <FONT COLOR=\"blue\">g_hwndChild = CreateWindow(WC_LISTVIEW, 0,\n    LVS_LIST | WS_CHILD | WS_VISIBLE |\n    WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0,\n    hwnd, (HMENU)1, g_hinst, 0);\n g_spWindows.CoCreateInstance(CLSID_ShellWindows);\n BuildWindowList();<\/FONT>\n return TRUE;\n}\n<\/PRE>\n<P>\nOur creation function creates a child listview\nand fills it with stuff.\n<\/P>\n<P>\nAnd of course we clean up our objects when the items\nare deleted and when the window is destroyed.\n<\/P>\n<PRE>\n<FONT COLOR=\"blue\">LRESULT OnNotify(HWND hwnd, int idFrom, NMHDR *pnm)\n{\n switch (idFrom) {\n case 1:\n  switch (pnm-&gt;code) {\n  case LVN_DELETEITEM:\n   {\n    auto pnmlv = CONTAINING_RECORD(pnm, NMLISTVIEW, hdr);\n    delete reinterpret_cast&lt;ItemInfo *&gt;(pnmlv-&gt;lParam);\n   }\n   break;\n  }\n }\n return 0;\n}<\/FONT><\/p>\n<p>void OnDestroy(HWND hwnd)\n{\n <FONT COLOR=\"blue\">g_spWindows.Release();<\/FONT>\n PostQuitMessage(0);\n}<\/p>\n<p> <FONT COLOR=\"blue\">HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);<\/FONT>\n<\/PRE>\n<P>\nAnd there we have it, a program that displays all the\nInternet Explorer and Explorer windows and updates\ntheir locations as you navigate.\n<\/P>\n<P>\nNote, however, that our program doesn&#8217;t notice when\nnew windows are created.\nWe&#8217;ll hook that up next time.\n<\/P><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Okay, it&#8217;s been a while since we set aside our Little Program to learn a bit about connection points and using dispatch interfaces as connection point interfaces. Now we can put that knowledge to use. Internet Explorer and Explorer windows fire a group of events known as DWeb&shy;Browser&shy;Events, so we just need to listen on [&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-4093","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-oldnewthing","tag-code"],"acf":[],"blog_post_summary":"<p>Okay, it&#8217;s been a while since we set aside our Little Program to learn a bit about connection points and using dispatch interfaces as connection point interfaces. Now we can put that knowledge to use. Internet Explorer and Explorer windows fire a group of events known as DWeb&shy;Browser&shy;Events, so we just need to listen on [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4093","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=4093"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/posts\/4093\/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=4093"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/categories?post=4093"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/oldnewthing\/wp-json\/wp\/v2\/tags?post=4093"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}