
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);
}
};
プロジェクトファイルをダウンロード