Click here to Skip to main content
15,881,248 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.
//
#include "stdafx.h"
#include "../include/shellfolderimpl.h"
#include "../include/browserframeoptionsimpl.h"
#include "../include/itemnamelimitsimpl.h"
#include "../include/strutil.h"
#include "../include/queryinfo.h"
#include "../include/cfhdrop.h"
#include "../include/menu.h"
#include "shellfolderclsid.h"
#include "shellfolderviewcb.h"
#include "shellfolderdataobject.h"
#include "enumidlist.h"
#include "vvvitem.h"
#include "vvvfile.h"
#include "columns.h"
#include "vvvpropertysheet.h"
#include "resource.h"


// Defines for the item context menu.
const UINT ID_DFM_CMD_OPEN = 0;


class ATL_NO_VTABLE CShellFolder :
	public CComObjectRootEx<CComSingleThreadModel>,
	public CComCoClass<CShellFolder, &__uuidof(CShellFolder)>,
	public IShellFolderImpl<CShellFolder, CVVVItem>,
	public IBrowserFrameOptionsImpl,
	public IItemNameLimitsImpl<CShellFolder, CVVVItem>
{
public:
	static HRESULT WINAPI UpdateRegistry(BOOL bRegister) throw()
	{
		UINT nResId = IsShell5OrHigher() ? IDR_SHELLFOLDER : IDR_SHELLFOLDER_WIN98;
		return IShellFolderImpl<CShellFolder, CVVVItem>::UpdateRegistry(
			nResId, bRegister,
			L"Sample ShellExtension ShellFolder", wszVVVExtension, IDS_SHELLFOLDER_TYPE);
	}


	DECLARE_PROTECT_FINAL_CONSTRUCT()

	BEGIN_COM_MAP(CShellFolder)
		COM_INTERFACE_ENTRY2(IPersist, IPersistFolder2)
		COM_INTERFACE_ENTRY(IPersistFolder)
		COM_INTERFACE_ENTRY(IPersistFolder2)
		COM_INTERFACE_ENTRY(IPersistIDList)
		COM_INTERFACE_ENTRY(IShellFolder)  // included for backwards (win9x) compatiblity.
		COM_INTERFACE_ENTRY(IShellFolder2)
		COM_INTERFACE_ENTRY(IShellDetails) // included for backwards (win9x) compatiblity.
		COM_INTERFACE_ENTRY(IBrowserFrameOptions)
		COM_INTERFACE_ENTRY(IShellIcon)
		COM_INTERFACE_ENTRY(IItemNameLimits)
		COM_INTERFACE_ENTRY(IDropTarget)   // enable drag and drop support.
	END_COM_MAP()


	CShellFolder()
	{
		// Register the columns the folder supports in 'detailed' mode.
		RegisterColumn(IDS_SHELLEXT_NAME, LVCFMT_LEFT);
		RegisterColumn(IDS_SHELLEXT_SIZE, LVCFMT_RIGHT);
	}


	// Purpose: called by MSF when the shellfolder needs to show a subfolder.
	void InitializeSubFolder(const CVVVItems& items)
	{
		m_strSubFolder.Empty();

		for (CVVVItems::const_iterator it = items.begin(); it != items.end(); ++it)
		{
			if (!m_strSubFolder.IsEmpty())
			{
				m_strSubFolder += _T("\\");
			}

			m_strSubFolder += ToString(it->GetID());
		}
	}


	// Purpose: Create the shellfolderviewcb that will be used to catch callback events
	//          generated by the system folder view.
	CComPtr<IShellFolderViewCB> CreateShellFolderViewCB()
	{
		return CShellFolderViewCB::CreateInstance(GetRootFolder());
	}


	// Purpose: called by MSF/shell when a number of items are selected and a IDataObject
	//          that contains the items is required.
	CComPtr<IDataObject> CreateDataObject(const ITEMIDLIST* pidlFolder, UINT cidl, const ITEMIDLIST** ppidl)
	{
		return CShellFolderDataObject::CreateInstance(pidlFolder, cidl, ppidl, this);
	}


	// Purpose: called by MSF/shell when it want the current list of 
	//          all items  The shell will walk all IDs and then release the enum.
	CComPtr<IEnumIDList> CreateEnumIDList(HWND /*hwnd*/, DWORD grfFlags)
	{
		auto_ptr<CVVVItems> qitems = CVVVFile(GetPathFolderFile(), m_strSubFolder).GetItems(grfFlags);
		return CEnumIDList::CreateInstance(GetUnknown(), qitems);
	}


	// Purpose: called by MSF when there is no global settings for all items.
	SFGAOF GetAttributeOf(unsigned int cidl, const CVVVItem& item, SFGAOF /*sfgofMask*/) const
	{
		return item.GetAttributeOf(cidl == 1, IsReadOnly(GetPathFolderFile()));
	}


	// Purpose: called by the default context menu. Gives an option to merge
	//          extra commands into the menu.
	HRESULT OnDfmMergeContextMenu(IDataObject* pdataobject, UINT /*uFlags*/, QCMINFO& qcminfo)
	{
		CCfShellIdList cfshellidlist(pdataobject);

		if (cfshellidlist.GetItemCount() == 1)
		{
			// Add 'open' if only 1 item is selected.
			CMenu menu(true);
			menu.AddDefaultItem(ID_DFM_CMD_OPEN, _T("&Open"));
			MergeMenus(qcminfo, menu);

			// Note: XP will automatic make first menu item the default.
			//       Win98, ME and 2k don't do this, so must add as default item.
		}

		return S_OK;
	}


	// Purpose: Called to get the help string for added menu items.
	CString OnDfmGetHelpText(unsigned short nCmdId)
	{
		return LoadString(IDS_SHELLFOLDER_DFM_HELP_BASE + nCmdId);
	}


	HRESULT OnDfmInvokeAddedCommand(HWND hwnd, IDataObject* pdataobject, int nId)
	{
		switch (nId)
		{
			case ID_DFM_CMD_OPEN:
				OnOpen(hwnd, pdataobject);
				break;

			default:
				ATLASSERT(false); // unknown command id detected.
				break;
		}

		return S_OK;
	}


	// Purpose: handle 'open' by showing the name of the selected item.
	void OnOpen(HWND hwnd, IDataObject* pdataobject)
	{
		CVVVItems items;

		RetrieveItems(pdataobject, items);
		ATLASSERT(items.size() == 1);

		if (items[0].IsFolder())
		{
			CPidl pidlFolder(items[0].CreateShellItemIdList());

			GetShellBrowser().BrowseObject(pidlFolder, SBSP_DEFBROWSER | SBSP_RELATIVE);
		}
		else
		{
			CString strMessage = _T("Open on: ") + items[0].GetName();
			IsolationAwareMessageBox(hwnd, strMessage, _T("Open"), MB_OK | MB_ICONQUESTION);
		}
	}


	// Purpose: Called by the shell/MSF when an item must be renamed.
	void OnSetNameOf(HWND /*hwnd*/, CVVVItem& item, const TCHAR* szNewName, SHGDNF shgndf)
	{
		RaiseExceptionIf(shgndf != SHGDN_NORMAL && shgndf != SHGDN_INFOLDER); // not supported 'name'.

		item.SetDisplayName(szNewName, shgndf);

		CVVVFile(GetPathFolderFile(), m_strSubFolder).SetItem(item);
	}


	// Purpose: handles the 'properties request.
	//          The property sheet/page allows the user to change 
	//          the name and size of an item.
	long OnProperties(HWND hwnd, CVVVItems& items)
	{
		ATLASSERT(items.size() == 1);
		CVVVItem& item = items[0];

		long wEventId;
		if (CVVVPropertySheet(item, this).DoModal(hwnd, wEventId) > 0 && wEventId != 0)
		{
			CVVVFile vvvfile(GetPathFolderFile(), m_strSubFolder);
			vvvfile.SetItem(item);
		}

		return wEventId;
	}


	// Purpose: Called by MSF/shell when items must be deleted.
	long OnDelete(HWND hwnd, CVVVItems& items)
	{
		if (hwnd != NULL && !UserConfirmsFileDelete(hwnd, items))
			return 0; // user wants to abort the file deletion process.

		CVVVFile(GetPathFolderFile(), m_strSubFolder).DeleteItems(items);

		return SHCNE_DELETE;
	}


	// Purpose: called by the standard MSF drag handler during drag operations.
	bool IsSupportedClipboardFormat(IDataObject* pdataobject)
	{
		return CCfHDrop::IsFormat(pdataobject);
	}


	// Purpose: called when items are pasted or droped on the shellfolder.
	DWORD AddItemsFromDataObject(DWORD dwEffect, IDataObject* pdataobject)
	{
		CCfHDrop cfhdrop(pdataobject);

		unsigned int nFiles = cfhdrop.GetFileCount();
		for (unsigned int i = 0; i < nFiles; ++i)
		{
			AddItem(cfhdrop.GetFile(i));
		}

		// The VVV sample cannot use optimized move. Just return dwEffect as passed.
		return dwEffect;
	}


	void OnError(HRESULT hr, HWND hwnd, EErrorContext /*errorcontext*/)
	{
		CString strMsg = LoadString(IDS_SHELLFOLDER_CANNOT_PERFORM) + FormatLastError(static_cast<DWORD>(hr));

		IsolationAwareMessageBox(hwnd, strMsg,
			LoadString(IDS_SHELLEXT_ERROR_CAPTION), MB_OK | MB_ICONERROR);
	}

private:

	// Purpose: Ask the user if he is really sure about the file delete action.
	//          Deleted files cannot be restored from the recycle bin.
	bool UserConfirmsFileDelete(HWND hwnd, const CVVVItems& items)
	{
		CString strMessage;
		UINT    nCaptionResId;

		if (items.size() == 1)
		{
			strMessage.FormatMessage(IDS_SHELLFOLDER_DELETE, items[0].GetDisplayName().GetString());
			nCaptionResId = IDS_SHELLFOLDER_FILE_DELETE_CAPTION;
		}
		else
		{
			strMessage.FormatMessage(IDS_SHELLFOLDER_MULTIPLE_DELETE,
				ToString(static_cast<unsigned int>(items.size())).GetString());
			nCaptionResId = IDS_SHELLFOLDER_FILES_DELETE_CAPTION;
		}

		return IsolationAwareMessageBox(hwnd, strMessage,
			LoadString(nCaptionResId), MB_YESNO | MB_ICONQUESTION) == IDYES;
	}


	void AddItem(const CString& strFile)
	{
		const auto_ptr<CVVVItem> qitem(CVVVFile(GetPathFolderFile(), m_strSubFolder).AddItem(strFile));

		ReportAddItem(*qitem);
	}


	bool IsReadOnly(const CString& strFileName) const
	{
		DWORD dwAttributes = GetFileAttributes(strFileName);
		return (dwAttributes & FILE_ATTRIBUTE_READONLY) != 0;
	}


	// Member variables
	CString m_strSubFolder;
};


OBJECT_ENTRY_AUTO(__uuidof(CShellFolder), CShellFolder)

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