Customizing the window handle for item enumeration in IShellItem


Some time ago, I showed how to customize the enumeration flags used when enumerating items with IShell­Item. This controls the grfFlags parameter passed to the IShell­Folder::Enum­Objects method, but what about the hwndOwner parmaeter? How to do you customize the window handle?

The window handle for the enumeration comes from the site of the enumerator.

There's no real reason you were expected to know this.

Here's a Little Program that demonstrates. It is basically the program we used last time, but translated from ATL to WRL (because that lets me use the Runtime­Class template.)

#define STRICT
#include <windows.h>
#include <shlobj.h>
#include <shlwapi.h>
#include <knownfolders.h>
#include <wrl/client.h>
#include <wrl/implements.h>
#include <stdio.h>    // Horrors! Mixing stdio and C++!

namespace wrl = Microsoft::WRL;

class COleWindow : public wrl::RuntimeClass<
    wrl::RuntimeClassFlags<wrl::ClassicCom>, IOleWindow>
{
public:
  HRESULT RuntimeClassInitialize(HWND hwnd)
  {
    m_hwnd = hwnd;
    return S_OK;
  }

  STDMETHODIMP GetWindow(_Out_ HWND* phwnd)
  {
    *phwnd = m_hwnd;
    return S_OK;
  }

  STDMETHODIMP ContextSensitiveHelp(BOOL /* fEnterMode */)
  {
    return E_NOTIMPL;
  }

private:
  HWND m_hwnd;
};

The COleWindow class is a simple object which implements the IOleWindow interface. It coughs up the window handle you gave it at initialization.

We can use this object to provide a window for enumeration. Remember that Little Programs do little to no error checking.

int __cdecl wmain(int argc, wchar_t** argv)
{
  CCoInitialize init;

  if (argc < 2) return 0;

  HWND hwnd = CreateWindowW(L"static", L"Title",
      WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
      CW_USEDEFAULT, CW_USEDEFAULT,
      nullptr, nullptr, HINST_THISCOMPONENT, 0);

  wrl::ComPtr<IShellItem> folder;
  SHCreateItemFromParsingName(argv[1], nullptr,
                              IID_PPV_ARGS(&folder));

  wrl::ComPtr<IEnumShellItems> enumerator;
  folder->BindToHandler(nullptr, BHID_EnumItems,
                           IID_PPV_ARGS(&enumerator));

  wrl::ComPtr<IUnknown> site;
  wrl::MakeAndInitialize<COleWindow>(&site, hwnd);
  IUnknown_SetSite(enumerator.Get(), site.Get());

  wrl::ComPtr<IShellItem> item;
  while (enumerator->Next(1, item.ReleaseAndGetAddressOf(),
                             nullptr) == S_OK) {
    PWSTR name;
    item->GetDisplayName(SIGDN_NORMALDISPLAY, &name);
    wprintf(L"%s\n", name);
    CoTaskMemFree(name);
  }

  return 0;
}

First, we create a window so we have something to pass to IShell­Folder::Enum­Objects. In real life, this is the window you want to use for any UI that is displayed as part of the enumeration.

Next, we take the path from the command line and convert it to an IShellItem. This is not new.

Once we have the folder as an IShellItem, we ask for its enumerator. If you wanted to customize the flags passed to the IShell­Folder::Enum­Objects method, here's where you would pass a customizing IBindCtx.

And then the new part: Before calling any enumeration methods, we create a COleWindow object and set it as the enumerator's site. This tells the enumerator where to get its window from.

We have nothing else interesting in our site, but in a real program, your site would probably implement IService­Provider in order to be a full-fledged site chain.

Finally, we use the enumerator in the usual manner and (for demonstration purposes) print out the names of the resulting objects.

Comments (4)

  1. Joshua Schaeffer says:

    I found it easiest to combine everything into a ShellItem class which has member fields for both IShellItem and IShellFolder, and the constructors build whatever you don't have. If you provide a IShellItem it extracts the IShellFolder and PIDL, and vice versa. Then a lot of this special IBindCtx casing goes away and you can interchangeably use whatever interface is easier. You can also add memcmp-sorting and CRC32-hashing on the absolute PIDL for dictionaries/sets/etc. I'm shocked .NET Framework isn't doing this already.

    One problem with EnumObjects() is that many system implementations won't respect child/owner relationships with out-of-process HWNDs. You'll empty the Recycle Bin and the confirmation window will show underneath the Z like it was browser spam or something. Not very good for writing third-party Explorers.

    1. Koro says:

      Funny you say that. An IShellItem is already that: an IShellFolder and a PIDL. No need to wrap the wrapper.

    2. skSdnW says:

      I have seen the same Z-order issue in Explorer with the confirm dialog during file copy operations. (Might only happen with new process Explorer windows?)

      1. Will says:

        The "confirmation dialog behind all other windows" issue is so common on my work PC it doesn't bother me anymore. If it happened every 20th time, it would be a headache, but as it happens almost every time, I know to go looking for the missing window. I guess us cobblers are ok with not having shoes.

        I probably could reinstall Windows to fix this, but that's a major ordeal.

Skip to main content