フォルダー構造をそのままメニューとして表示する

foldermenu.gif
Internet Explorerの「お気に入り」など情報がそのままフォルダ/ファイルとして保存されているケースがたまにある。ここではファイル/フォルダー構造をそのままメニューとして出力するクラスを作成した。

メニュー作成時にサブフォルダからファイルをすべて読み込む造りだと非常に重くなる。そのためここでは内部でサブメニューが開いたことを検知するウインドウを作成して、サブメニューが開いたらそのサブメニューの分だけファイルを検索するようにした。このため表示する項目数が多くあっても重くなるケースは少ないだろう。

このクラスを派生すればメニュー項目としてユーザーに表示する文字列を自由に変更するなどの処理ができるようにした(細かな制御をするには編集が必要だが)。

サンプルプロジェクトではポップアップメニューとして表示しているが、ウインドウメニューとして利用できる仕組みも用意した。
■使用例
	LRESULT OnButtonClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
	{
		CDnpFolderMenu	cMenu;

		bool		ret;
		RECT		rect;
		POINT		pt;
		CAtlString	strPath;
		CAtlString	strName;

		//ボタンの外形を取得
		GetDlgItem(IDC_BUTTON1).GetWindowRect(&rect);
		pt.x = rect.left;
		pt.y = rect.bottom;

		//メニューを表示
		ret = cMenu.ShowMenu(m_hWnd,_T("C:\\Program Files\\Common Files\\"),pt,TPM_LEFTALIGN,strPath,strName);

		//選択されたアイテムをメッセージボックスで出力
		if(ret)
			MessageBox(strName,strPath);

		return	0;
	}


■「DnpFolderMenu.h」の内容
#pragma	once

#include "atlstr.h"
#include "atlcoll.h"


///
///\brief
///	フォルダーメニュークラス
///
///指定したフォルダー内のファイルとフォルダーをサブフォルダーにまでさかのぼって
///全てメニューとして表示する。
///
///■使用例
/**	\code
	CDnpFolderMenu	cMenu;

	POINT		pt;
	CAtlString	strPath;
	CAtlString	strName;

	::GetCursorPos(&pt);
	cMenu.ShowMenu(m_hWnd,_T("c:\\"),pt,TPM_LEFTALIGN,strPath,strName);

	::MessageBox(NULL,strName,NULL,NULL);
	\endcode
*///
///
class	CDnpFolderMenu
{
protected:


	///
	///\brief
	///		ファイル/フォルダーパス情報クラス
	///
	///配列_acFileListに格納するために定義
	///格納されているファイル(フォルダー)のフルパスは「_strPath + _strFile」になる。
	///つまり_strPathにはフルパスは格納されないことに注意!
	///
	///
	class	CFileData
	{
	public:
		CFileData()
		{
			_bFolder = false;
		}
		CFileData(LPCTSTR pszPath,LPCTSTR pszFile,bool bFolder)
		{
			_strPath = pszPath;
			_strFile = pszFile;
			_bFolder = bFolder;
		}

		CAtlString	_strPath;		//!< パス名(ex.「c:\\windows\\」なら「c:\\」が格納される)
		CAtlString	_strFile;		//!< ファイル名(ex.「c:\\windows\\」なら「windows」が格納される)
		bool		_bFolder;		//!< フォルダーならtrue、ファイルならfalse
	};


	///
	///\brief
	///	メニュー処理関数
	///
	///	メニューメッセージの処理とメニュー生成、フォルダ内列挙関数など内部処理をほぼ全て含む関数
	///
	class CMenuMessageWnd : public CWindowImpl<CMenuMessageWnd>
	{
		CDnpFolderMenu*	_pDnpFolderMenu;	//!< 親クラスのポインタ
	public:

		UINT	_nIDFirst;		//!< メニュー項目に利用するIDの最初の値。デフォルトは1。ウインドウメニュー(TrackPopupMenuを使わないメニュー表示)をするときは設定が必須


		CMenuMessageWnd(CDnpFolderMenu* pMenu)
		{
			_nIDFirst = 1;
			_pDnpFolderMenu = pMenu;
		}

		BEGIN_MSG_MAP(CMenuMessageWnd)
			MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect)			//フォルダを示すメニューが選択されたときの処理
		END_MSG_MAP()

	protected:



		///
		/// \brief
		///メニューハンドル(HMENU)とフォルダパスを関連付けるmap
		///サブメニューを選択したときにそれがどこのフォルダなのかを識別するために利用
		///
		CAtlMap<HMENU,CAtlString>	_mapFolderPath;



		///
		///\brief
		///	メニュー選択時処理
		///
		///	フォルダを示すメニューが選択されたとき、その子フォルダ内を調べてフォルダとファイル名を追加する。
		///
		LRESULT OnMenuSelect(UINT /*uMsg*/, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
		{
			bHandled = FALSE;

			if(HIWORD(wParam) & MF_POPUP)		//ポップアップメニューが選択されたときのみ処理する
			{
				HMENU	hMenu;
				CAtlMap<HMENU,CAtlString>::CPair*	pPair;

				hMenu = ::GetSubMenu((HMENU)lParam,LOWORD(wParam));		//選択されたサブメニューを取得
				if(hMenu && ::GetMenuItemCount(hMenu) == 0)
				{
					pPair = _mapFolderPath.Lookup(hMenu);				//メニューハンドルから関連するフォルダー名を取得
					if(pPair)
					{
						bHandled = TRUE;
						AddPathToMenu(hMenu,pPair->m_value);			//フォルダ内のファイルやフォルダをメニューに追加
					}
				}
			}

			return	0;
		}


		///
		///\brief
		///ファイル・フォルダ情報格納配列
		///メニューに表示しているフォルダやファイルの全情報を格納する
		///メニュー登録時のIDはこの配列への「インデックス+_nIDFirst」とする。
		///
		CAtlArray<CFileData>	_acFileList;



		///
		///\brief
		///	SortFileListArray() 用の比較関数
		///
		static	int	SortFileListArray_(void * lpParam, const void * lpData1,const void* lpData2)
		{
			INT_PTR		nData1;
			INT_PTR		nData2;

			nData1 = *(INT_PTR*)lpData1;
			nData2 = *(INT_PTR*)lpData2;

			CAtlArray<CFileData>*	lpastrFile;

			lpastrFile = (CAtlArray<CFileData>*) lpParam;

			return	::_tcscmp(lpastrFile->GetAt(nData1)._strFile,lpastrFile->GetAt(nData2)._strFile);
		}

		///
		///\brief
		///	文字列の並べ替え関数
		///
		///		CCAtlArray<CFileData>を昇順に並べ替える
		///
		bool	SortFileListArray(CAtlArray<CFileData>* pacFileList,INT_PTR nBefore,INT_PTR nAfter)
		{
			INT_PTR			i;
			INT_PTR			nCount;
			INT_PTR*		lpnIndex;
			CAtlArray<CFileData>	acTmp;

			if(pacFileList == NULL)
				return	false;

			nCount = nAfter - nBefore;
			if(nCount <= 1 || nBefore >= nAfter)
				return	true;

			lpnIndex = new INT_PTR[nCount + 1];
			if(lpnIndex == NULL)
				return	false;

			for(i = 0; i < nCount; i++)
			{
				lpnIndex[i] = i;
				acTmp.Add((*pacFileList)[i + nBefore]);
			}

			::qsort_s(lpnIndex,nCount,sizeof(INT_PTR),SortFileListArray_,&acTmp);

			pacFileList->SetCount(nBefore);
			for(i = 0; i < nCount; i++)
				pacFileList->Add(acTmp[lpnIndex[i]]);

			delete	lpnIndex;

			return	true;
		}


		///
		///\brief
		///	ファイル/フォルダの列挙
		///
		///指定したフォルダ内のファイルなどを列挙します。
		///列挙結果はpacFileListに追加します。
		///	pszFolder内のフォルダとファイルを列挙してpacFileList配列へ追加する
		///
		bool	GetFileList(LPCTSTR pszFolder,CAtlArray<CFileData>* pacFileList)
		{
			BOOL			bFind;
			HANDLE			hFind;
			WIN32_FIND_DATA	FindFileData;
			CAtlString		strFolder;

			if(pacFileList == NULL || pszFolder == NULL || _tcslen(pszFolder) == 0)
				return	false;

			//pacFileList->RemoveAll();				//削除したらダメ!

			strFolder = pszFolder;
			if(strFolder.Right(1) != _T('\\') && strFolder.Right(1) != _T('/'))
				strFolder += _T("\\");

			hFind = ::FindFirstFile(strFolder + _T("*.*"),&FindFileData);
			if(hFind == INVALID_HANDLE_VALUE)
				return	false;

			INT_PTR	nBefore;
			INT_PTR	nAfter;

			nBefore = pacFileList->GetCount();

			bFind = TRUE;
			while(bFind)
			{
				if(FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					if(_tcsncmp(FindFileData.cFileName,_T("."),1) != 0 && _tcsncmp(FindFileData.cFileName,_T(".."),2) != 0 )
						pacFileList->Add(CFileData(strFolder,FindFileData.cFileName,true));
				}
				else
					pacFileList->Add(CFileData(strFolder,FindFileData.cFileName,false));

				bFind = ::FindNextFile(hFind,&FindFileData);
			}
			::FindClose(hFind);

			nAfter = pacFileList->GetCount();


			bool	ret;

			//親クラスで処理を試みてから、このクラス内のソートを利用する
			ret = false;
			if(_pDnpFolderMenu)
				ret = _pDnpFolderMenu->SortFileListArray(pszFolder,pacFileList,nBefore,nAfter);
			if(ret == false)
				SortFileListArray(pacFileList,nBefore,nAfter);

			return	true;
		}


		///
		///\brief
		///	メニュー情報の作成
		///
		///	HMENUにpszPathに存在するファイルやフォルダを追加する
		///
		bool	AddPathToMenu(HMENU hMenu,LPCTSTR pszPath)
		{
			bool	ret;
			INT_PTR	i;
			INT_PTR	nIndex;

			nIndex = _acFileList.GetCount();
			GetFileList(pszPath,&_acFileList);			//ファイルの列挙:_acFileListに追加する形で列挙

			for(i = nIndex; i < _acFileList.GetCount(); i++)
			{
				if(_acFileList[i]._bFolder)
				{
					HMENU	hMenuSub;

					hMenuSub = ::CreatePopupMenu();
					if(hMenuSub)
					{
						_mapFolderPath.SetAt(hMenuSub,_acFileList[i]._strPath + _acFileList[i]._strFile + _T("\\"));

						CAtlString	strString;

						ret = TranslateMenuString(_acFileList[i]._strPath,_acFileList[i]._strFile,strString,true);
						if(ret)
							::AppendMenu(hMenu, MF_ENABLED | MF_POPUP,(UINT_PTR)hMenuSub,strString);
					}
					ATLASSERT(hMenuSub);
				}
				else
				{
					CAtlString	strString;

					ret = TranslateMenuString(_acFileList[i]._strPath,_acFileList[i]._strFile,strString,false);

					//IDをi +iとしてメニューに登録
					if(ret)
						::AppendMenu(hMenu, MF_ENABLED | MF_STRING ,i + _nIDFirst,strString);
				}
			}

			return	true;
		}


		///
		///\brief
		///	メニューに表示する文字列の取得
		///
		///ファイル名とパス(フォルダー名とパス)を参考にメニューにする文字列を決める。
		///派生クラスで表示名を変更可能
		///
		bool	TranslateMenuString(LPCTSTR pszPath,LPCTSTR pszFileName,CAtlString& strMenuString,bool bFolder)
		{
			if(_pDnpFolderMenu)
				return	_pDnpFolderMenu->TranslateMenuString(pszPath,pszFileName,strMenuString,bFolder);

			strMenuString = pszFileName;
			return	true;
		}



	public:

		///
		///\brief
		///	TrackPopupMenuの返値を示すIDから選択されたフォルダ・ファイルを取得する
		///
		///	失敗時はfalseが返る。かならず処理すること!
		///
		bool	GetPath(UINT_PTR nID,CAtlString& strPath,CAtlString& strName,bool& bFolder)
		{
			strPath = _T("");
			strName = _T("");
			bFolder = false;

			if(nID < _nIDFirst)
				return	false;

			nID -= _nIDFirst;
			if(nID >= (UINT_PTR)_acFileList.GetCount())
				return	false;

			bFolder = _acFileList[nID]._bFolder;
			strPath = _acFileList[nID]._strPath;
			strName = _acFileList[nID]._strFile;

			return	true;
		}



		///
		///\brief
		///	ルートフォルダの登録
		///
		///TrackPopupMenuを使わずにウインドウメニューにより表示するときのみ利用される
		///
		bool	AddRootPathMenu(LPCTSTR pszFolder,HMENU hMenu)
		{
			if(pszFolder == NULL || *pszFolder == NULL || hMenu == NULL)
				return	false;

			return	AddPathToMenu(hMenu,pszFolder);
		}



		///
		///\brief
		///	メニューの表示
		///
		///	pszFolder内の全サブフォルダおよびファイルを表示する。
		///	選択結果はstrPathとstrNameに返る。
		///
		///	ptはメニュー表示位置、uFlagsはTrackPopupMenuに与えるフラグを示す。
		///
		bool	ShowMenu(HWND hParentWnd,LPCTSTR pszFolder,POINT pt,UINT uFlags,CAtlString& strPath,CAtlString& strName)
		{
			bool		ret;
			bool		bFolder;
			UINT_PTR	nID;
			HMENU		hMenu;

			Create(hParentWnd);				//メッセージ処理用ウインドウ作成
			if(IsWindow() == FALSE)
				return	false;

			ret = true;
			hMenu = ::CreatePopupMenu();	//メニュー作成
			if(hMenu == NULL)
				ret = false;

			if(ret)
				ret = AddPathToMenu(hMenu,pszFolder);		//メニューに一番上の階層のフォルダ内容を追加する
			if(ret)
				nID = (UINT_PTR)::TrackPopupMenu(hMenu,uFlags | TPM_RETURNCMD,pt.x,pt.y,NULL,m_hWnd,NULL);		//メニューの表示
			if(ret)
				ret = GetPath(nID,strPath,strName,bFolder);		//選択されたフォルダ・メニューの取得

			_mapFolderPath.RemoveAll();						//マップ情報の削除

			::DestroyMenu(hMenu);							//メニューハンドルの開放
			DestroyWindow();								//メッセージ処理用ウインドウの破壊

			return	ret;
		}

	};





	CMenuMessageWnd	_wndMenuMessage;		//!<	メニュー処理用ウインドウ




	///
	///\brief
	///	メニューに表示する文字列の設定
	///
	///ファイル名とパス(フォルダー名とパス)を参考にメニューとしてユーザーに表示する文字列を決める。
	///仮想関数。派生クラスで表示名を変更可能
	///
	///falseを返すとそのアイテムは表示しない
	///
	virtual	bool	TranslateMenuString(LPCTSTR pszPath,LPCTSTR pszFileName,CAtlString& strMenuString,bool bFolder)
	{
		strMenuString = pszFileName;		//ファイル名をそのままメニューの表示名として使う
		return	true;
	}


	///
	///\brief
	///	メニューに表示する文字列の並べ替え
	///
	///\retval	true	並べ替え成功(実際の並べ替え処理をせずにtrueを返したら、配列に入っていた順序で表示される)
	///\retval	false	この関数で並べ替えはしなかった(デフォルトの処理(アルファベット順)を行う)
	///
	///並べ替える範囲は(*pacFileList)[nBefore]~(*pacFileList)[nAfter - 1]
	///並べ替える要素数はnCount = nAfter - nBefore;
	///
	///for(i = 0; i < nCount; i++)
	///{
	///	(*pacFileList)[i + nBefore];
	///}
	///の範囲を並べ替える
	///
	virtual	bool	SortFileListArray(LPCTSTR pszFolder,CAtlArray<CFileData>* pacFileList,INT_PTR nBefore,INT_PTR nAfter)
	{
		return	false;
	}



public:

	CDnpFolderMenu() :
		_wndMenuMessage(this)
	{
	}

	BEGIN_MSG_MAP(CDnpFolderMenu)
		CHAIN_MSG_MAP_MEMBER(_wndMenuMessage)
	END_MSG_MAP()


	///
	///\brief
	///	ウインドウメニュー表示用関数
	///
	///TrackPopupMenuを使わずにウインドウメニューなどに表示するときに使う。
	///必ずCHAIN_MSG_MAPによりこのクラス内のメッセージマップが処理されるようにすること。
	///1度だけ呼べばあとはメッセージマップで処理する
	///
	///
	///\param[in]	pszFolder		メニュー表示時のルートとなるフォルダ
	///
	///\param	hMenu				pszFolder内のファイルを表示するメニューハンドル
	///
	///\param[in]	nIDFirst		メニュー項目のIDとして使う最初の値。1以上の値を設定する。
	///
	///
	bool	ShowMenu(LPCTSTR pszFolder,HMENU hMenu,UINT nIDFirst)
	{
		if(nIDFirst == 0)
			return	false;

		_wndMenuMessage._nIDFirst = nIDFirst;
		return	_wndMenuMessage.AddRootPathMenu(pszFolder,hMenu);
	}


	///
	///\brief
	///		選択されたメニューIDから選択された項目を取得
	///
	/// ShowMenu(LPCTSTR,HMENU,UINT) と対で利用する関数
	///
	///\param[in]	nID			選択されたメニューID
	///
	///\param[out]	strFolder	選択項目のフォルダー名「c:\\windows\\」など
	///
	///\param[out]	strFile		選択されたファイル名「cmd.exe」など
	///
	///選択項目のフルパスは「strFolder + strFile」により得られる。
	///
	bool	GetPathFromID(UINT nID,CAtlString& strFolder,CAtlString& strFile)
	{
		bool	bFolder;
		return	_wndMenuMessage.GetPath(nID,strFolder,strFile,bFolder);
	}




	///
	///\brief
	///	ポップアップメニューの表示
	///
	///	pszFolder内の全サブフォルダおよびファイルを表示する。
	///	選択結果はstrPathとstrNameに返る。
	///
	///	ptはメニュー表示位置、uFlagsはTrackPopupMenuに与えるフラグを示す。
	/// 強制的にTPM_RETURNCMDが付加されて、strPathとstrNameに結果が返る
	///
	bool	ShowMenu(HWND hParentWnd,LPCTSTR pszFolder,POINT pt,UINT uFlags,CAtlString& strPath,CAtlString& strName)
	{
		return	_wndMenuMessage.ShowMenu(hParentWnd,pszFolder,pt,uFlags,strPath,strName);
	}
};

プロジェクトファイルをダウンロード


カテゴリー「ファイル・フォルダ」 のエントリー