Querying information from an Explorer window

Raymond Chen

Raymond

Sometimes software development is inventing new stuff.
But often, it’s just putting together the stuff you already have.
Today’s puzzle is one of the latter type of problem.

Given a window handle, you can you determine (1) whether it is
an Explorer window, and if so (2) what folder it is viewing, and
(3) what item is currently focused.

This is not an inherently difficult task.
You just have to put together lots of small pieces.

Start with

the ShellWindows object

which represents all the open shell windows.
You can enumerate through them all with
the Item property.
This is rather clumsy from C++ because the ShellWindows object
was designed for use by a scripting language like JScript or Visual Basic.

 IShellWindows *psw;
 if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
                                IID_IShellWindows, (void**)&psw))) {
  VARIANT v;
  V_VT(&v) = VT_I4;
  IDispatch  *pdisp;
  BOOL fFound = FALSE;
  for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
       V_I4(&v)++) {
    ...
    pdisp->Release();
  }
  psw->Release();
 }

From each item, we can ask it for its window handle and see if it’s the one
we want.

   IWebBrowserApp *pwba;
   if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
     HWND hwndWBA;
     if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
       hwndWBA == hwndFind) {
       fFound = TRUE;
       ...
     }
     pwba->Release();
   }

Okay, now that we have found the folder via its IWebBrowserApp,
we need to get to the top shell browser. This is done by
querying for the SID_STopLevelBrowser service and asking for

the IShellBrowser interface
.

       IServiceProvider *psp;
       if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
         IShellBrowser *psb;
         if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
                              IID_IShellBrowser, (void**)&psb))) {
           ...
           psb->Release();
         }
         psp->Release();
       }

From the IShellBrowser, we can ask for the current shell view
via

the QueryActiveShellView method
.

           IShellView *psv;
           if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
             ...
             psv->Release();
           }

Of course, what we really want is

the IFolderView interface
,
which is the automation object that contains all the real goodies.

             IFolderView *pfv;
             if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
                                               (void**)&pfv))) {
               ...
               pfv->Release();
             }

Okay, now we’re golden. What do you want to get from the view?
How about the location of the IShellFolder being viewed.
To do that, we need to use

IPersistFolder2
::GetCurFolder.

The GetFolder method
will give us access to the shell folder,
from which we ask for IPersistFolder2.
(Most of the time you want the IShellFolder interface,
since that’s where most of the cool stuff hangs out.)

               IPersistFolder2 *ppf2;
               if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
                                            (void**)&ppf2))) {
                 LPITEMIDLIST pidlFolder;
                 if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
                   ...
                   CoTaskMemFree(pidlFolder);
                 }
                 ppf2->Release();
               }

Let’s convert that pidl into a path, for display purposes.

                   if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
                     lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
                   }
                   ...

What else can we do with what we’ve got? Oh right, let’s see what the
currently-focused object is.

                   int iFocus;
                   if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
                     ...
                   }

Let’s display the name of the focused item.
To do that we need the item’s pidl and the IShellFolder.
(See, I told you the IShellFolder is where the cool stuff is.)
The item comes from

the Item method
(surprisingly enough).

                     LPITEMIDLIST pidlItem;
                     if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
                       ...
                       CoTaskMemFree(pidlItem);
                     }

(If we had wanted a list of selected items we could have used

the Items method
, passing SVGIO_SELECTION.)

After we get the item’s pidl, we also need the IShellFolder:

                       IShellFolder *psf;
                       if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
                                                          (void**)&psf))) {
                         ...
                         psf->Release();
                       }

Then we put the two together to get the item’s display name,
with the help of

the GetDisplayNameOf method
.

                         STRRET str;
                         if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
                                                   SHGDN_INFOLDER,
                                                   &str))) {
                           ...
                         }

We can use the helper function

StrRetToBuf
to convert the kooky

STRRET structure
into
a boring string buffer.
(The history of the kooky STRRET structure will have to wait for
another day.)

                           StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);

Okay, let’s put this all together.
It looks rather ugly because I put everything into one huge
function instead of breaking them out into subfunctions.
In “real life” I would have broken things up into little helper
functions to make things more manageable.

Start with
the
scratch program
and add this new function:

#include <shlobj.h>
#include <exdisp.h>
TCHAR g_szPath[MAX_PATH];
TCHAR g_szItem[MAX_PATH];
void CALLBACK RecalcText(HWND hwnd, UINT, UINT_PTR, DWORD)
{
 HWND hwndFind = GetForegroundWindow();
 g_szPath[0] = TEXT('\0');
 g_szItem[0] = TEXT('\0');
 IShellWindows *psw;
 if (SUCCEEDED(CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_ALL,
                                IID_IShellWindows, (void**)&psw))) {
  VARIANT v;
  V_VT(&v) = VT_I4;
  IDispatch  *pdisp;
  BOOL fFound = FALSE;
  for (V_I4(&v) = 0; !fFound && psw->Item(v, &pdisp) == S_OK;
       V_I4(&v)++) {
   IWebBrowserApp *pwba;
   if (SUCCEEDED(pdisp->QueryInterface(IID_IWebBrowserApp, (void**)&pwba))) {
     HWND hwndWBA;
     if (SUCCEEDED(pwba->get_HWND((LONG_PTR*)&hwndWBA)) &&
       hwndWBA == hwndFind) {
       fFound = TRUE;
       IServiceProvider *psp;
       if (SUCCEEDED(pwba->QueryInterface(IID_IServiceProvider, (void**)&psp))) {
         IShellBrowser *psb;
         if (SUCCEEDED(psp->QueryService(SID_STopLevelBrowser,
                              IID_IShellBrowser, (void**)&psb))) {
           IShellView *psv;
           if (SUCCEEDED(psb->QueryActiveShellView(&psv))) {
             IFolderView *pfv;
             if (SUCCEEDED(psv->QueryInterface(IID_IFolderView,
                                               (void**)&pfv))) {
               IPersistFolder2 *ppf2;
               if (SUCCEEDED(pfv->GetFolder(IID_IPersistFolder2,
                                            (void**)&ppf2))) {
                 LPITEMIDLIST pidlFolder;
                 if (SUCCEEDED(ppf2->GetCurFolder(&pidlFolder))) {
                   if (!SHGetPathFromIDList(pidlFolder, g_szPath)) {
                     lstrcpyn(g_szPath, TEXT("<not a directory>"), MAX_PATH);
                   }
                   int iFocus;
                   if (SUCCEEDED(pfv->GetFocusedItem(&iFocus))) {
                     LPITEMIDLIST pidlItem;
                     if (SUCCEEDED(pfv->Item(iFocus, &pidlItem))) {
                       IShellFolder *psf;
                       if (SUCCEEDED(ppf2->QueryInterface(IID_IShellFolder,
                                                          (void**)&psf))) {
                         STRRET str;
                         if (SUCCEEDED(psf->GetDisplayNameOf(pidlItem,
                                                   SHGDN_INFOLDER,
                                                   &str))) {
                           StrRetToBuf(&str, pidlItem, g_szItem, MAX_PATH);
                         }
                         psf->Release();
                       }
                       CoTaskMemFree(pidlItem);
                     }
                   }
                   CoTaskMemFree(pidlFolder);
                 }
                 ppf2->Release();
               }
               pfv->Release();
             }
             psv->Release();
           }
           psb->Release();
         }
         psp->Release();
       }
     }
     pwba->Release();
   }
    pdisp->Release();
  }
  psw->Release();
 }
 InvalidateRect(hwnd, NULL, TRUE);
}

Now all we have to do is call this function periodically
and print the results.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
    SetTimer(hwnd, 1, 1000, RecalcText);
    return TRUE;
}
void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
  TextOut(pps->hdc, 0, 0, g_szPath, lstrlen(g_szPath));
  TextOut(pps->hdc, 0, 20, g_szItem, lstrlen(g_szItem));
}

We’re ready to roll. Run this program and set it to the side.
Then launch an Explorer window and watch the program track the folder
you’re in and what item you have focused.

Okay, so I hope I made my point:
Often, the pieces you need are already there; you just have to
figure out how to put them together. Notice that each of the
pieces is in itself not very big. You just had to recognize
that they could be put together in an interesting way.

Exercise: Change this program so it takes the folder and
switches it to details view.

[Raymond is currently on vacation; this message was pre-recorded.]

Raymond Chen
Raymond Chen

Follow Raymond   

0 comments

Comments are closed.