If you are targeting Windows 8 or higher,
you can
use
(STR_
to customize how shell items are enumerated.
But what if you need to run on older systems, too?
In that case, you will need to drop to the lower-level
IShellFolder::
function,
like we did before,
and then reconstructe shell items from the low-level
IShellFolder
and
ITEMID_
.
(Note that the term "low-level" is used here only in a relative
sense; it's lower level than IShellItem
.)
We can wrap that inside a helper class.
#define UNICODE #define _UNICODE #define STRICT #define STRICT_TYPED_ITEMIDS #include <windows.h> #include <shlobj.h> #include <atlbase.h> CComModule _Module; #include <atlcom.h> #include <atlalloc.h> #include <stdio.h> #include <tchar.h> class CEnumItemsWithSHCONTF : public CComObjectRoot, public IEnumShellItems { public: BEGIN_COM_MAP(CEnumItemsWithSHCONTF) COM_INTERFACE_ENTRY(IEnumShellItems) END_COM_MAP() static HRESULT Create(HWND hwnd, SHCONTF shcontf, IShellItem *psiFolder, REFIID riid, void **ppv); STDMETHOD(Next)(ULONG celt, IShellItem **ppsi, ULONG *pceltFetched); STDMETHOD(Skip)(ULONG celt); STDMETHOD(Reset)(); STDMETHOD(Clone)(IEnumShellItems **ppesiClone); private: static HRESULT CreateRef1(CComObject<CEnumItemsWithSHCONTF> **ppObj); HRESULT Initialize(HWND hwnd, SHCONTF shcontf, IShellItem *psiFolder); HRESULT CloneFrom(CEnumItemsWithSHCONTF *pSource); private: CComPtr<IShellFolder> m_spsfParent; CComPtr<IEnumIDList> m_speidl; }; HRESULT CEnumItemsWithSHCONTF::CreateRef1( CComObject<CEnumItemsWithSHCONTF> **ppObj) { CComObject<CEnumItemsWithSHCONTF> *pObj; HRESULT hr = CComObject<CEnumItemsWithSHCONTF>:: CreateInstance(&pObj); *ppObj = CComPtr<CComObject<CEnumItemsWithSHCONTF> >(pObj).Detach(); return hr; } HRESULT CEnumItemsWithSHCONTF::Initialize( HWND hwnd, SHCONTF shcontf, IShellItem *psiFolder) { HRESULT hr = psiFolder->BindToHandler( nullptr, BHID_SFObject, IID_PPV_ARGS(&m_spsfParent)); if (SUCCEEDED(hr)) { hr = m_spsfParent->EnumObjects(hwnd, shcontf, &m_speidl); } return hr; } HRESULT CEnumItemsWithSHCONTF::CloneFrom( CEnumItemsWithSHCONTF *pSource) { HRESULT hr = pSource->m_speidl->Clone(&m_speidl); if (SUCCEEDED(hr)) { m_spsfParent = pSource->m_spsfParent; } return hr; } HRESULT CEnumItemsWithSHCONTF::Create( HWND hwnd, SHCONTF shcontf, IShellItem *psiFolder, REFIID riid, void **ppv) { *ppv = nullptr; CComPtr<CComObject<CEnumItemsWithSHCONTF>> spObj; HRESULT hr = CreateRef1(&spObj); if (SUCCEEDED(hr)) { hr = spObj->Initialize(hwnd, shcontf, psiFolder); if (SUCCEEDED(hr)) { hr = spObj->QueryInterface(riid, ppv); } } return hr; } HRESULT CEnumItemsWithSHCONTF::Next( ULONG celt, IShellItem **ppsi, ULONG *pceltFetched) { if (celt != 1 && pceltFetched == nullptr) { return E_INVALIDARG; } for (ULONG i = 0; i < celt; i++) ppsi[i] = nullptr; ULONG celtFetched = 0; HRESULT hr = S_OK; while (hr == S_OK && celtFetched < celt) { CComHeapPtr<ITEMID_CHILD> spidlChild; hr = m_speidl->Next(1, &spidlChild, nullptr); if (hr == S_OK) { hr = SHCreateItemWithParent(nullptr, m_spsfParent, spidlChild, IID_PPV_ARGS(&ppsi[celtFetched])); if (SUCCEEDED(hr)) celtFetched++; } } if (pceltFetched != nullptr) *pceltFetched = celtFetched; if (SUCCEEDED(hr)) { hr = (celtFetched == celt) ? S_OK : S_FALSE; } return hr; } HRESULT CEnumItemsWithSHCONTF::Skip(ULONG celt) { return m_speidl->Skip(celt); } HRESULT CEnumItemsWithSHCONTF::Reset() { return m_speidl->Reset(); } HRESULT CEnumItemsWithSHCONTF::Clone( IEnumShellItems **ppesiClone) { *ppesiClone = nullptr; CComPtr<CComObject<CEnumItemsWithSHCONTF>> spClone; HRESULT hr = CreateRef1(&spClone); if (SUCCEEDED(hr)) { hr = spClone->CloneFrom(this); if (SUCCEEDED(hr)) { *ppesiClone = spClone.Detach(); } } return hr; }
The CEnumItemsWithSHCONTF
class
does the work of enumerating child items the old-fashioned way,
then constructing shell items from the result.
Most of this code is boilerplate
(including the
part to
avoid having a live COM object with reference count zero).
The object has two members, the source folder from which the items
are being enumerated and the low-level enumerator itself.
We initialize the object by asking for the low-level
IEnumIDList
handler and calling
IEnumIDList::
with the
specific flags we want.
When it is time to generate items, we ask the inner enumerator
for the next ID list, and construct a shell item around it
by comining the ID list with the parent folder.
The rest is bookkeeping:
We keep track of the number of elements fetched so far
in order to return it to the caller if requested,
and also in order to decide
what the return value should be.
If all items were retrieved successfully, then return
S_OK
.
If we ran out of items, then return
S_FALSE
.
If something went wrong, we return the error code,
possibly with partially-fetched results.
The other enumerator operations like Reset
and
Clone
are delegated to the inner enumerator.
Cloning is a little tricky because we also have to clone ourselves!
Now we can adapt
our program from last time
to use this class instead of
BHID_
.
int __cdecl wmain(int argc, wchar_t **argv) { CCoInitialize init; if (argc < 2) return 0; CComPtr<IShellItem> spsiFolder; SHCreateItemFromParsingName(argv[1], nullptr, IID_PPV_ARGS(&spsiFolder)); CComPtr<IEnumShellItems> spesi; CEnumItemsWithSHCONTF::Create(nullptr, SHCONTF_FOLDERS, spsiFolder, IID_PPV_ARGS(&spesi)); for (CComPtr<IShellItem> spsi; spesi->Next(1, &spsi, nullptr) == S_OK; spsi.Release()) { PrintDisplayName(spsi, SIGDN_NORMALDISPLAY, L"Display Name"); PrintDisplayName(spsi, SIGDN_FILESYSPATH, L"File system path"); wprintf(L"\n"); } return 0; }