Click here to Skip to main content
15,881,882 members
Articles / Desktop Programming / ATL

The Mini Shell Extension Framework – Part III

Rate me:
Please Sign up or sign in to vote.
4.96/5 (11 votes)
18 Sep 200516 min read 139.6K   1.4K   46  
Discussion of a small C++ framework to create Windows shell extensions (IShellFolderImpl).
//
// (C) Copyright by Victor Derks <vba64@xs4all.nl>
//
// See README.TXT for the details of the software licence.
//
#pragma once


#include <map>
#include <vector>
#include <algorithm>
#include "updateregistry.h"
#include "strutil.h"
#include "catchhandler.h"
#include "shelluuids.h"


namespace MSF
{

// Note: Due to a bug in explorer it will not release the interface if the
//       folder is the 'desktop'. The workaround is to return 0 columns in that case.
//       This will prevent the resource leak. The drawback is that there are no
//       columns available if the 'desktop' folder is viewed in 'detailed' mode.

template <typename T, bool t_bEnableDesktopBugWorkAround = true>
class ATL_NO_VTABLE IColumnProviderImpl : public IColumnProvider
{
public:
	typedef std::map<CStringW, std::vector<CString> > mapCacheInfo;

	struct CCompareShColumnId
	{
		bool operator()(const SHCOLUMNID& left, const SHCOLUMNID& right) const
		{
			return memcmp(&left, &right, sizeof(SHCOLUMNID)) < 0;
		}
	};

	typedef std::map<SHCOLUMNID, unsigned int, CCompareShColumnId> mapColumnIdToIndex;

	template <bool t_bEnableDesktopBugWorkAround>
	class CDesktopBugWorkAround
	{
	public:
		void Initialize(const wchar_t* /*wszFolder*/)
		{
		}

		bool operator()() const throw()
		{
			return false;
		}
	};

	template <>
	class CDesktopBugWorkAround<true>
	{
	public:

		static CStringW GetFolderPath(int nFolder)
		{
			TCHAR tszFolderPath[MAX_PATH];

			if (SHGetSpecialFolderPath(NULL, tszFolderPath, nFolder, false))
			{
				return CStringW(CT2W(tszFolderPath));
			}
			else
			{
				return CStringW();
			}
		}


		// Purpose: returns true if the wszFolder equals the 'all users' or user
		//          desktop folder. 
		static bool IsDesktopPath(const wchar_t* wszFolder)
		{
			return GetFolderPath(CSIDL_COMMON_DESKTOPDIRECTORY) == wszFolder ||
				GetFolderPath(CSIDL_DESKTOPDIRECTORY) == wszFolder;
		}


		void Initialize(const wchar_t* wszFolder)
		{
			m_bHideDesktopColumns = IsDesktopPath(wszFolder);
		}


		bool operator()() const throw()
		{
			return m_bHideDesktopColumns;
		}

	private:

		bool m_bHideDesktopColumns;
	};


	static HRESULT WINAPI UpdateRegistry(UINT nResId, BOOL bRegister, const wchar_t* wszDescription) throw()
	{
		return UpdateRegistryFromResource(nResId, bRegister, wszDescription, T::GetObjectCLSID());
	}


	IColumnProviderImpl() :
		m_pCachedInfo(NULL)
	{
		ATLTRACE2(atlTraceCOM, 0, _T("IColumnProviderImpl::IColumnProviderImpl (instance=%p)\n"), this);
	}


	~IColumnProviderImpl() throw()
	{
		ATLTRACE2(atlTraceCOM, 0, _T("IColumnProviderImpl::~IColumnProviderImpl (instance=%p)\n"), this);
	}


	// IColumnProvider
	STDMETHOD(Initialize)(const SHCOLUMNINIT* psci)
	{
		try
		{
			ATLTRACE2(atlTraceCOM, 0,
				_T("IColumnProviderImpl::Initialize, i=%p, tid=%d, dwFlags=%d, wszFolder=%s\n"),
				this, GetCurrentThreadId(), psci->dwFlags, psci->wszFolder);

			// Clear internal caching variables.
			m_pCachedInfo = NULL;
			m_strCachedFilename.Empty();
			m_mapCacheInfo.clear();

			m_desktopbugworkaround.Initialize(psci->wszFolder);

			m_columninfos.clear();
			m_columnidtoindex.clear();

			// Note: OnInitialize needs to be implemented by the derived class.
			static_cast<T*>(this)->OnInitialize(psci->wszFolder);

			return S_OK;
		}
		MSF_COM_CATCH_HANDLER()
	}


	// Purpose: GetColumnInfo is called by the shell to retrieve the column names.
	// By calling this repeatedly the shell can detect how many columns there are.
	STDMETHOD(GetColumnInfo)(DWORD dwIndex, SHCOLUMNINFO* psci)
	{
		ATLTRACE2(atlTraceCOM, 0,
			_T("IColumnProviderImpl::GetColumnInfo, i=%p, tid=%d, dwIndex=%d\n"),
			this, GetCurrentThreadId(), dwIndex);

		if (m_desktopbugworkaround() || dwIndex >= m_columninfos.size())
			return S_FALSE; // tell the shell there are no more columns.

		*psci = m_columninfos[dwIndex];
		return S_OK;
	}


	STDMETHOD(GetItemData)(const SHCOLUMNID* pscid, const SHCOLUMNDATA* pscd, VARIANT* pvarData)
	{
		ATLTRACE2(atlTraceCOM, 0, _T("IColumnProviderImpl::GetItemData, i=%p, tid=%d, f=%d, c=%d, file=%s\n"),
			this, GetCurrentThreadId(), pscd->dwFlags, pscid->pid, CW2T(pscd->wszFile));

		try
		{
			if (!IsSupportedItem(*pscd))
			{
				VariantInit(pvarData); // must initialise out args as we return a success code.
				return S_FALSE;
			}

			const wchar_t* wszFilename = PathFindFileNameW(pscd->wszFile);

			bool bFlushCache = IsBitSet(pscd->dwFlags, SHCDF_UPDATEITEM);
			const std::vector<CString>* pCachedInfo;

			if (bFlushCache)
			{
				pCachedInfo = NULL;
			}
			else
			{
				// User of this interface (explorer.exe) are expected to ask 
				// all active item data per file.
				pCachedInfo = FindInLastUsedCache(wszFilename);
			}

			if (pCachedInfo == NULL)
			{
				pCachedInfo = GetAndCacheFileInfo(wszFilename, pscd->wszFile, bFlushCache);
			}

			pvarData->bstrVal = (*pCachedInfo)[GetIndex(*pscid)].AllocSysString();
			pvarData->vt = VT_BSTR;

			return S_OK;
		}
		MSF_COM_CATCH_HANDLER()
	}

protected:

	// Purpose: provides a default GUID for the columns.
	const GUID& GetStandardFormatIdentifier() const
	{
		return T::GetObjectCLSID();
	}
	

	void RegisterColumn(const GUID& fmtid, DWORD pid, const wchar_t* wszTitle, UINT cChars, DWORD fmt = LVCFMT_LEFT, DWORD csFlags = SHCOLSTATE_TYPE_STR)
	{
		SHCOLUMNID columnid;

		columnid.pid   = pid;
		columnid.fmtid = fmtid;

		SHCOLUMNINFO columninfo;

		columninfo.scid = columnid;

		ATLASSERT(wcslen(wszTitle) < MAX_COLUMN_NAME_LEN && "wszTitle is too long");
		wcscpy(columninfo.wszTitle, wszTitle);
		columninfo.cChars = cChars;
		columninfo.fmt = fmt;
		columninfo.csFlags = csFlags | SHCOLSTATE_EXTENDED | SHCOLSTATE_SECONDARYUI;
		// Note: VT_LPSTR/VT_BSTR works ok. Other types seems to have issues with sorting.
		columninfo.vt = VT_BSTR;
		columninfo.wszDescription[0] = 0; // not used by the shell.

		m_columninfos.push_back(columninfo);
		m_columnidtoindex[columnid] = static_cast<unsigned int>(m_columninfos.size() - 1);
	}


	void RegisterColumn(const GUID& fmtid, DWORD pid, UINT nResourceID, UINT cChars, DWORD fmt = LVCFMT_LEFT, DWORD csFlags = SHCOLSTATE_TYPE_STR)
	{
		RegisterColumn(pid, fmtid, LoadStringW(nResourceID), cChars, fmt, csFlags);
	}


	void RegisterColumn(const wchar_t* wszTitle, UINT cChars, DWORD fmt = LVCFMT_LEFT, DWORD csFlags = SHCOLSTATE_TYPE_STR)
	{
		RegisterColumn(GetStandardFormatIdentifier(), static_cast<DWORD>(m_columninfos.size()),
			wszTitle, cChars, fmt, csFlags);
	}


	void RegisterColumn(UINT nResourceID, UINT cChars, DWORD fmt = LVCFMT_LEFT, DWORD csFlags = SHCOLSTATE_TYPE_STR)
	{
		RegisterColumn(LoadStringW(nResourceID), cChars, fmt, csFlags);
	}


	void RegisterExtension(CStringW strExtension)
	{
		m_extensions.push_back(strExtension.MakeLower());
	}


	bool IsSupportedItem(const SHCOLUMNDATA& scd) const
	{
		// Only support online files.
		if (scd.dwFileAttributes & static_cast<const T*>(this)->GetFileAttributeMask())
			return false;

		// Check file extension.
		return IsSupportedFileType(scd.pwszExt);
	}


	bool IsSupportedFileType(CStringW strExtension) const
	{
		strExtension.MakeLower();

		std::vector<CStringW>::const_iterator result =
			std::find(m_extensions.begin(), m_extensions.end(), strExtension);

		return result != m_extensions.end();
	}


	unsigned int GetIndex(const SHCOLUMNID& scid) const
	{
		mapColumnIdToIndex::const_iterator it = m_columnidtoindex.find(scid);
		RaiseExceptionIf(it == m_columnidtoindex.end());

		return it->second;
	}

	
	DWORD GetFileAttributeMask() const
	{
		return FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_OFFLINE;
	}


private:

	const std::vector<CString>* GetAndCacheFileInfo(const CStringW& strFilename, const wchar_t* wszFile, bool bFlushCache)
	{
		const std::vector<CString>* pCachedInfo;

		if (bFlushCache)
		{
			pCachedInfo = NULL;
		}
		else
		{
			pCachedInfo = FindInCache(strFilename);
		}

		if (pCachedInfo == NULL)
		{
			// New file info must be stored in the cache.
			vector<CString> strColumnInfos;

			// Note: GetAllColumnInfo must be implemented by the derived class.
			static_cast<T*>(this)->GetAllColumnInfo(CString(CW2CT(wszFile)), strColumnInfos);

			ATLASSERT(strColumnInfos.size() == m_columninfos.size() && "Missing info");

			pCachedInfo = InsertInCache(strFilename, strColumnInfos);
		}

		return InsertInLastUsedCache(strFilename, pCachedInfo);
	}


	const std::vector<CString>* FindInCache(const CStringW& strFilename) const
	{
		mapCacheInfo::const_iterator it = m_mapCacheInfo.find(strFilename);
		if (it != m_mapCacheInfo.end())
		{
			return &(*it).second;
		}
		else
		{
			return NULL;
		}
	}


	const std::vector<CString>* InsertInCache(const CStringW& strFilename, const std::vector<CString>& strColumnInfos)
	{
		return &(m_mapCacheInfo[strFilename] = strColumnInfos);
	}


	const std::vector<CString>* InsertInLastUsedCache(const CStringW& strFilename, const std::vector<CString>* pCachedInfo)
	{
		m_strCachedFilename = strFilename;
		m_pCachedInfo = pCachedInfo;

		return m_pCachedInfo;
	}


	const std::vector<CString>* FindInLastUsedCache(const wchar_t* wszFilename) const
	{
		if (m_pCachedInfo == NULL)
			return NULL; // last used cache is empty.

		if (m_strCachedFilename != wszFilename)
			return NULL; // last used cache was for a different file.
		
		return m_pCachedInfo;
	}

	// Member variables.
	std::vector<SHCOLUMNINFO>   m_columninfos;
	std::vector<CStringW>       m_extensions;
	mapColumnIdToIndex          m_columnidtoindex;

	CStringW                    m_strCachedFilename;
	const std::vector<CString>* m_pCachedInfo;
	mapCacheInfo                m_mapCacheInfo;

	CDesktopBugWorkAround<t_bEnableDesktopBugWorkAround> m_desktopbugworkaround;
};


} // namespace MSF

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior) Hitachi High-Tech Analytical Science
Netherlands Netherlands
Victor lives in Nijmegen, the oldest city in The Netherlands.
He studied Applied Physics in Delft and works Hitachi High-Tech Analytical Science.

Comments and Discussions