第58回 IShellMenuCallbackにより「お気に入り」をビューで開く (タブブラウザーを作る)

tabbrowser264.gif
前回は「お気に入り」メニューをIShellMenuベースに置き換えた。今回はIShellMenu用のコールバックインターフェースを用意して、選択した「お気に入り」がビューで開くようにする。

IShellMenuCallbackがコールバック用インターフェースで、各種イベント時にIShellMenuCallback::CallbackSMが呼ばれる。ここでは「お気に入り」が選択されたときにそのフルパスを独自メッセージでメインウインドウへ返すようにする。
//■追加
//IEツールバーをホストするためのインターフェース
MIDL_INTERFACE("50AFAFED-815E-4ec4-9A78-231C95958716")
IShellMenuHost : public IUnknown
{
public:
	STDMETHOD (put_IShellFolder)(IShellFolder* pIShellFolder) = 0;
	STDMETHOD (put_NotifyWindow)(HWND hWnd,UINT nMessageID) = 0;
};


//■追加
class	CShellMenuCallback : 
	public CComObjectRootEx<CComSingleThreadModel>
	,public IShellMenuHost
	,public IShellMenuCallback
{
	UINT	_nNotifyMessage;
	HWND	_hWndNotify;

	CComPtr<IShellFolder>	_pIShellFolder;

public:
	BEGIN_COM_MAP(CShellMenuCallback)
		COM_INTERFACE_ENTRY(IShellMenuHost)
		COM_INTERFACE_ENTRY(IShellMenuCallback)
	END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()

	CShellMenuCallback()
	{
		_nNotifyMessage = 0;
		_hWndNotify = NULL;
	}

	//
	STDMETHOD (put_IShellFolder)(IShellFolder* pIShellFolder)
	{
		_pIShellFolder = NULL;
		if(pIShellFolder == NULL)
			return	E_POINTER;

		return	pIShellFolder->QueryInterface(IID_IShellFolder,(void**)&_pIShellFolder);
	}

	STDMETHOD (put_NotifyWindow)(HWND hWnd,UINT nMessageID)
	{
		if(hWnd == NULL || ::IsWindow(hWnd) == FALSE || nMessageID == 0)
			return	E_FAIL;

		_hWndNotify = hWnd;
		_nNotifyMessage = nMessageID;

		return	S_OK;
	}




	HRESULT	OnSMC_INITMENU(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_FALSE;
	}

	//メニューが作られた
	//処理は不要
	HRESULT	OnSMC_CREATE(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_FALSE;
	}

	HRESULT	OnSMC_EXITMENU(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}


	//普通のメニュー項目に関する情報
	HRESULT	OnSMC_GETINFO(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		SMINFO*		pSmInfo = (SMINFO*)lParam;

		if(pSmInfo->dwMask & SMIM_FLAGS)
			pSmInfo->dwFlags |= SMIF_DROPCASCADE | SMIF_TRACKPOPUP;

		return	S_OK;
	}

	//IShellFolder項目に関する情報
	//処理必要なし
	HRESULT	OnSMC_GETSFINFO(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_GETOBJECT(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		REFIID	iid = (REFIID)wParam;
		void**	ppv = (void**)lParam;

		//処理していない
		return	S_FALSE;
	}

	HRESULT	OnSMC_GETSFOBJECT(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		REFIID	iid = (REFIID)wParam;
		void**	ppv = (void**)lParam;

		//処理していない
		return	S_FALSE;
	}

	//選択された項目を開く
	HRESULT	OnSMC_SFEXEC(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		TCHAR	pszFile[MAX_PATH];
		TCHAR	pszPath[MAX_PATH];
		int		nFind;
		CAtlString	strName;

		*pszPath = NULL;
		::SHGetPathFromIDList(psmd->pidlItem,pszFile);
		::SHGetPathFromIDList(psmd->pidlFolder,pszPath);

		strName = pszFile;
		nFind = strName.ReverseFind(_T('\\'));
		if(nFind >= 0)
			strName = strName.Right(strName.GetLength() - nFind);
		strName = pszPath + strName;

		//ウインドウへメッセージを送る
		if(*pszPath && _hWndNotify && ::IsWindow(_hWndNotify) && _nNotifyMessage)
			::SendMessage(_hWndNotify,_nNotifyMessage,(WPARAM)(LPCTSTR)strName,NULL);

		return	S_OK;
	}

	HRESULT	OnSMC_SFSELECTITEM(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_REFRESH(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_DEMOTE(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_PROMOTE(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_DEFAULTICON(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_NEWITEM(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_CHEVRONEXPAND(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_DISPLAYCHEVRONTIP(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_SETSFOBJECT(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_SHCHANGENOTIFY(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_CHEVRONGETTIP(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	On(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}

	HRESULT	OnSMC_SFDDRESTRICTED(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		return	S_OK;
	}




	STDMETHOD(CallbackSM)(LPSMDATA psmd,UINT uMsg,WPARAM wParam,LPARAM lParam)
	{
		switch(uMsg)
		{
		// The callback is called to init a menuband
		case	SMC_INITMENU:
			return	OnSMC_INITMENU(psmd,uMsg,wParam,lParam);

		case	SMC_CREATE:
			return	OnSMC_CREATE(psmd,uMsg,wParam,lParam);

		// The callback is called when menu is collapsing
		case	SMC_EXITMENU:
			return	OnSMC_EXITMENU(psmd,uMsg,wParam,lParam);

		// The callback is called to return DWORD values
		case	SMC_GETINFO:
			return	OnSMC_GETINFO(psmd,uMsg,wParam,lParam);

		// The callback is called to return DWORD values
		case	SMC_GETSFINFO:
			return	OnSMC_GETSFINFO(psmd,uMsg,wParam,lParam);

		// The callback is called to get some object
		case	SMC_GETOBJECT:
			return	OnSMC_GETOBJECT(psmd,uMsg,wParam,lParam);

		// The callback is called to get some object
		case	SMC_GETSFOBJECT:
			return	OnSMC_GETSFOBJECT(psmd,uMsg,wParam,lParam);

		// The callback is called to execute an shell folder item
		case	SMC_SFEXEC:
			return	OnSMC_SFEXEC(psmd,uMsg,wParam,lParam);

		// The callback is called when an item is selected
		case	SMC_SFSELECTITEM:
			return	OnSMC_SFSELECTITEM(psmd,uMsg,wParam,lParam);

		// Menus have completely refreshed. Reset your state.
		case	SMC_REFRESH:
			return	OnSMC_REFRESH(psmd,uMsg,wParam,lParam);

		// Demote an item
		case	SMC_DEMOTE:
			return	OnSMC_DEMOTE(psmd,uMsg,wParam,lParam);

		// Promote an item, wParam = SMINV_* flag
		case	SMC_PROMOTE:
			return	OnSMC_PROMOTE(psmd,uMsg,wParam,lParam);

		// Returns Default icon location in wParam, index in lParam
		case	SMC_DEFAULTICON:
			return	OnSMC_DEFAULTICON(psmd,uMsg,wParam,lParam);

		// Notifies item is not in the order stream.
		case	SMC_NEWITEM:
			return	OnSMC_NEWITEM(psmd,uMsg,wParam,lParam);

		// Notifies of a expansion via the chevron
		case	SMC_CHEVRONEXPAND:
			return	OnSMC_CHEVRONEXPAND(psmd,uMsg,wParam,lParam);

		// S_OK display, S_FALSE not.
		case	SMC_DISPLAYCHEVRONTIP:
			return	OnSMC_DISPLAYCHEVRONTIP(psmd,uMsg,wParam,lParam);

		// Called to save the passed object
		case	SMC_SETSFOBJECT:
			return	OnSMC_SETSFOBJECT(psmd,uMsg,wParam,lParam);

		// Called when a Change notify is received. lParam points to SMCSHCHANGENOTIFYSTRUCT
		case	SMC_SHCHANGENOTIFY:
			return	OnSMC_SHCHANGENOTIFY(psmd,uMsg,wParam,lParam);

		// Called to get the chevron tip text. wParam = Tip title, Lparam = TipText Both MAX_PATH
		case	SMC_CHEVRONGETTIP:
			OnSMC_CHEVRONGETTIP(psmd,uMsg,wParam,lParam);
			break;

		// Called requesting if it's ok to drop. wParam = IDropTarget.
		case	SMC_SFDDRESTRICTED:
			OnSMC_SFDDRESTRICTED(psmd,uMsg,wParam,lParam);
			break;

		default:
			break;
		}

		return	S_FALSE;
	}
};

tabbrowser265.gif そしてメインウインドウで独自メッセージが届いたときにビューでURLが開くようにする。
		NOTIFY_CODE_HANDLER(TBVN_PAGEACTIVATED, OnTabPageActivated)
		MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelectForPopup)
//		MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect)
		MESSAGE_HANDLER(WM_DNP_NAVIGATEFAVORITE, OnDnpNavigateFavorite)	//■追加
		MESSAGE_HANDLER(WM_PARENTNOTIFY, OnParentNotify)
//		COMMAND_RANGE_HANDLER(ID_FAVORITE_FIRST,ID_FAVORITE_LAST,OnFavorite)	//■削除
		CHAIN_MSG_MAP_MEMBER(_cFovMenu)
		CHAIN_MSG_MAP(CUpdateUI<CMainFrame>)
		CHAIN_MSG_MAP(CFrameWindowImpl<CMainFrame>)
	END_MSG_MAP()


	//■追加
	LRESULT OnDnpNavigateFavorite(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		bool		ret;
		CAtlString	strURL;
		LPCTSTR	pszFile = (LPCTSTR)wParam;

		if(pszFile == NULL || *pszFile == NULL)
			return	0;

		ret = GetInternetShortcut(pszFile,strURL);
		if(ret == false)
			return	0;

		//タブとしてURLを開く
		{
			CTabBrowser100View* pView;

			pView = GetActivePageView();
			if(pView == NULL)
			{
				//新しいタブを作成
				pView = CreateNewTab(_T("about:blank"),NULL);
			}

			//URLを開く
			if(pView)
				pView->Navigate(strURL);
		}

		return	0;
	}

tabbrowser266.gif IShellMenu::Initializeで先ほど作成したコールバック用インターフェースを渡す。
	bool	PopupMenu(int nX,int nY)
	{
		LPITEMIDLIST	pidl;

		CComPtr<IShellFolder>		pIShellFolder;

		if(_pIMenuBand)
		{
			//すでにメニューが開いていたら、まずはそのメニューを閉じる

			CComPtr<IOleCommandTarget>	pIOleCommandTarget;

			_pIMenuBand->QueryInterface(&pIOleCommandTarget);
			if(pIOleCommandTarget)
				pIOleCommandTarget->Exec(&CLSID_MenuBand,22,0,NULL,NULL);
		}


		//「お気に入り」フォルダのPIDLとIShellFolderを取得
		{
			CComPtr<IShellFolder>	pIShellFolderDesktop;

			::SHGetDesktopFolder(&pIShellFolderDesktop);
			if(pIShellFolderDesktop == NULL)
				return	false;
			::SHGetSpecialFolderLocation(NULL,CSIDL_FAVORITES,&pidl);
			pIShellFolderDesktop->BindToObject(pidl,NULL,IID_IShellFolder,(void **)&pIShellFolder);
		}



		CComPtr<IDeskBand>	pIDeskBand;

		//PIDLとIShellFolderの示すIShellMenu(IDeskBand)を取得
		{
			HRESULT		hr;
			CRegKey		cRegOrder;
			CComPtr<IShellMenu>	pIShellMenu;
			CComPtr<IDeskBar>	pIDeskBar;
			CComPtr<IMenuPopup>	pIMenuPopup;

			//並び順用レジストリ。必要ならCreateしてもいいが今回は「お気に入り」のみなのでopen
			cRegOrder.Open(HKEY_CURRENT_USER,_T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MenuOrder\\Favorites"));

			::CoCreateInstance(CLSID_MenuBand,NULL,CLSCTX_INPROC_SERVER,IID_IShellMenu,(void**)&pIShellMenu);

			//■処理変更
			if(pIShellMenu)
			{
				CComPtr<IShellMenuHost>			pIShellMenuHost;
				CComPtr<IShellMenuCallback>		pIShellMenuCallback;

				//コールバック生成
				pIShellMenuCallback = new CComObject<CShellMenuCallback>;
				if(pIShellMenuCallback)
					pIShellMenuCallback->QueryInterface(&pIShellMenuHost);
				if(pIShellMenuHost)
				{
					pIShellMenuHost->put_IShellFolder(pIShellFolder);
					pIShellMenuHost->put_NotifyWindow(m_hWnd,WM_DNP_NAVIGATEFAVORITE);
				}

				//IShellMenuの初期化
				pIShellMenu->Initialize(pIShellMenuCallback,-1,ANCESTORDEFAULT,SMINIT_TOPLEVEL | SMINIT_VERTICAL);

				#ifndef	SMSET_USEBKICONEXTRACTION
					#define SMSET_USEBKICONEXTRACTION	0x00000008
				#endif

				//メニューへのフォルダ割り当て
				hr = pIShellMenu->SetShellFolder(pIShellFolder,pidl,cRegOrder.m_hKey,SMSET_BOTTOM | SMSET_USEBKICONEXTRACTION);	//以下も指定したいが値が不明 | SMSET_HASEXPANDABLEFOLDERS)

				if(SUCCEEDED(hr))
					cRegOrder.Detach();	//レジストリのCloseはIShellMenuに任せる
				ILFree(pidl);
				pidl = NULL;
			}

			if(pIShellMenu)
				pIShellMenu->QueryInterface(&pIMenuPopup);
			if(pIMenuPopup)
				pIMenuPopup->QueryInterface(&pIDeskBar);
			if(pIDeskBar)
				pIDeskBar->QueryInterface(&pIDeskBand);
			if(pIDeskBand == NULL)
				return	false;
		}


		//IShellMenu(IDeskBand)をポップアップメニューで表示
		{
			POINTL	ptl;
			GUID	rclsid;
			CComPtr<IUnknown>			pIUnknown;
			CComPtr<IBandSite>			pIBandSite;
			CComPtr<IMenuPopup>			pIMenuPopup;

			//メニュー用のDeskBar「Menu Desk Bar」
			::CLSIDFromString(L"{ECD4FC4F-521C-11D0-B792-00A0C90312E1}",&rclsid);
			::CoCreateInstance(rclsid,NULL,CLSCTX_INPROC_SERVER,IID_IUnknown,(void**)&pIUnknown);

			//メニュー用のBandSite
			::CoCreateInstance(CLSID_MenuBandSite,NULL,CLSCTX_INPROC_SERVER,IID_IBandSite,(void**)&pIBandSite);

			if(pIUnknown)
				pIUnknown->QueryInterface(&pIMenuPopup);	//メニュー用DeskBarだからIMenuPopupを持っている
			if(pIMenuPopup)
				pIMenuPopup->SetClient(pIBandSite);			//DeskBarにIBandSiteを割り当て
			if(pIBandSite)
				pIBandSite->AddBand(pIDeskBand);			//IBandSiteに表示したいIShellMenuを割り当てる

			//位置を指定してメニュー表示
			ptl.x = nX;
			ptl.y = nY;
			if(pIMenuPopup)
				pIMenuPopup->Popup(&ptl,NULL,MPPF_POS_MASK | MPPF_FORCEZORDER | MPPF_RIGHT | MPPF_BOTTOM);
		}


		//メニューの_pIMenuBandを保存
		{
			_pIMenuBand = NULL;
			pIDeskBand->QueryInterface(&_pIMenuBand);
			if(_pIMenuBand == NULL)
				return	false;
		}

		return	true;
	}

tabbrowser267.gif 最後に「stdafx.h」内に独自メッセージを定義しておく。
#define	WM_DNP_NAVIGATEFAVORITE		(WM_APP + 13)	//■追加

tabbrowser268.gif
これで「お気に入り」を選択したときにそのURLがビューで開くようになった。また「お気に入り」の表示順序をドラッグアンドドロップにより変更できるようになった。

次回は少しフォーカス関連のバグ修正をする。

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


カテゴリー「タブブラウザーを作る」 のエントリー