« 2009年03月 | メイン | 2010年05月 »

前の10件 1  2  3  4  5  6  7

2009年04月 記事一覧

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

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);
	}
};

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

第10回 「お気に入り」メニューを表示する (タブブラウザーを作る)

tabbrowser40.gif
次はタブブラウザーに「お気に入り」の選択機能を実装する。一般的に出回っているタブブラウザーの中には独自形式の「お気に入り」を実装しているものもある。独自形式の方が機能追加などが柔軟にでき便利だ。
しかしここでは多機能で一般的に使えるタブブラウザーの開発を目指しているわけでもないので独自形式は避け、Internet Explorerの「お気に入り」を利用する。

前回までに作成した作成したプロジェクトで「リソース」ウインドウを開き、ウインドウフレーム用メニューに「お気に入り」とその下に「dummy」の項目を追加する。
「dummy」のIDは「ID_FAVORITE_DUMMY」とした。この項目は「お気に入り」メニューの場所を特定するために利用するだけで実際にメニューを表示するときには削除する。

tabbrowser41.gif
メニューリソースの編集が終わったら一度保存するために「ファイル」メニューの「すべてを保存」を選択する。

tabbrowser42.gif
次にソリューションウインドウにある「resource.h」をダブルクリックして開く。このときに警告が出るが、「OK」ボタンを押し警告は無視する。そしてここに2行追加する。

「お気に入り」として表示するメニューの数はユーザーによって異なるため動的にメニュー項目を追加する必要がある。ここではID_FAVORITE_FIRSTから始まるIDを「お気に入り」用として使うことにした。
#define ID_FAVORITE_FIRST               34000			//お気に入りは全部で2万アイテムまでサポート
#define ID_FAVORITE_LAST                54000			//

tabbrowser43.gif
次にソリューションウインドウの「MainFrm.ht」を開き、お気に入りメニュー用のクラスを追加する。

Internet Explorerの「お気に入り」はユーザーフォルダの中にインターネットショートカットの「ファイル」として保存されている。ここでは「フォルダー構造をそのままメニューとして表示する」で作成したCDnpFolderMenuクラス派生でお気に入りを表示する。
#include "DnpFolderMenu.h"					//■追加

#include "shlwapi.h"						//■追加
#pragma	comment(lib,"shlwapi.lib")			//■追加

//■追加
//「お気に入り」メニュークラス
class	CFavoritesMenu	: public CDnpFolderMenu
{
public:
	BEGIN_MSG_MAP(CFavoritesMenu)
		CHAIN_MSG_MAP(CDnpFolderMenu)
	END_MSG_MAP()


protected:

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

		//ファイルはインターネットショートカット(拡張子が.url)なら拡張子を除いたファイル名を表示
		//それ以外の拡張子のファイルはfalseを返して非表示にする

		LPTSTR	pszExt;

		strMenuString = pszFileName;
		pszExt = ::PathFindExtension(strMenuString.GetBuffer(0));		//拡張子の開始ポインタ取得
		if(pszExt == NULL)
			return	false;

		if(::_tcsicmp(pszExt,_T(".url")) != 0)
			return	false;		//拡張子が.urlではなかった

		*pszExt = NULL;			//拡張子を消す

		return	true;
	}
};

tabbrowser44.gif
さらにメッセージハンドラの処理などを追加する。

メニューが開いたことを知らせる「WM_MENUSELECT」を検出して開いたメニュー項目の最初に「ID_FAVORITE_DUMMY」があれば「お気に入り」用のメニューと判断して処理している。

ユーザーによって異なる「お気に入り」フォルダの取得には「OSに関係なく特殊フォルダのパスを取得する」で作成したSafeSHGetSpecialFolderPathを利用した。
	BEGIN_MSG_MAP(CMainFrame)
		MESSAGE_HANDLER(WM_DNP_CHANGEFOCUS, OnDnpChangeFocus)
		NOTIFY_CODE_HANDLER(CBEN_ENDEDIT, OnAddressbarEnter)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
		COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
		COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
		COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
		COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE, OnWindowClose)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE_ALL, OnWindowCloseAll)
		COMMAND_RANGE_HANDLER(ID_WINDOW_TABFIRST, ID_WINDOW_TABLAST, OnWindowActivate)
		COMMAND_ID_HANDLER(ID_IE_BACK, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_NEXT, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_STOP, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_REFRESH, OnIECommand)
		NOTIFY_CODE_HANDLER(TBVN_CONTEXTMENU, OnTabContextMenu)
		MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect)						//■追加
		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()


	CFavoritesMenu	_cFovMenu;			//■追加 「お気に入り」メニュー用クラス

	//■追加
	//メニューを開いたときに呼ばれる処理
	//「お気に入り」が最初に開いたときだけ、ウインドウメニューと_cFovMenuを関連付ける
	LRESULT OnMenuSelect(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		int		nMenuIndex = LOWORD(wParam);
		int		nFlag = HIWORD(wParam);
		BOOL	ret;
		HMENU	hMenu = (HMENU)lParam;
		HMENU	hSubMenu;
		TCHAR	pszPath[MAX_PATH + 100];

		bHandled = FALSE;
		if(!(nFlag & MF_POPUP))
			return	0;

		hSubMenu = ::GetSubMenu(hMenu,nMenuIndex);
		if(hSubMenu == NULL)
			return	0;

		//「お気に入り」のメニューが開いたかどうかをチェック
		//「お気に入り」は一つ目のメニューをID_FAVORITE_DUMMYとした
		if(::GetMenuItemCount(hSubMenu) == 0 || ::GetMenuItemID(hSubMenu,0) != ID_FAVORITE_DUMMY)
			return	0;

		//「お気に入り」フォルダ取得
		ret = SafeSHGetSpecialFolderPath(m_hWnd,pszPath,CSIDL_FAVORITES,FALSE);
		if(ret == FALSE)
			return	0;

		//もうダミー項目は必要ないから削除
		::RemoveMenu(hSubMenu,ID_FAVORITE_DUMMY,MF_BYCOMMAND);

		//ウインドウメニューへ「お気に入り」を関連付ける
		_cFovMenu.ShowMenu(pszPath,hSubMenu,ID_FAVORITE_FIRST);

		return	0;
	}

tabbrowser45.gif 次に「お気に入り」の中の項目が選択されたときの処理などを実装する。 本来であれば選択された「お気に入り」をタブの中で開く処理を行うが、ここでは選択された「お気に入り」をメッセージボックスに表示するのみにしている。
	//■追加
	//「お気に入り」が選択されたときの処理
	LRESULT OnFavorite(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
	{
		CAtlString	strFolder;
		CAtlString	strFile;

		_cFovMenu.GetPathFromID(wID,strFolder,strFile);

		//選択されたお気に入りのファイル名をメッセージボックスで表示
		MessageBox(strFile);

		return	0;
	}

	//■追加
	//	SHGetSpecialFolderPathの全プラットフォーム対応バージョン
	//
	//「OSに関係なく特殊フォルダのパスを取得する」
	//http://www.usefullcode.net/2006/12/os.html
	//
	BOOL	SafeSHGetSpecialFolderPath(HWND hwndOwner,LPTSTR lpszPath,int nFolder,BOOL fCreate)
	{
		BOOL	(CALLBACK* g_pfnSHGetSpecialFolderPath)(HWND,LPTSTR,int,BOOL);
		HRESULT	(CALLBACK* g_pfnSHGetFolderPath)(HWND,int,HANDLE,DWORD,LPTSTR);

		int			i;
		BOOL		ret;
		HRESULT		hr;
		HMODULE		hDLL;
		TCHAR		pszDllFile[][15] = {_T("shfolder.dll"),_T("shell32.dll")};

		if(lpszPath == NULL)
			return	FALSE;
		*lpszPath = NULL;

		ret = FALSE;
		for(i = 0; i < 2; i++)
		{
			hDLL = ::LoadLibrary(pszDllFile[i]);
			if(hDLL == NULL)
				continue;

			#ifdef UNICODE
				(*(FARPROC*)&g_pfnSHGetSpecialFolderPath = ::GetProcAddress(hDLL,"SHGetSpecialFolderPathW"));
				(*(FARPROC*)&g_pfnSHGetFolderPath = ::GetProcAddress(hDLL,"SHGetFolderPathW"));
			#else
				(*(FARPROC*)&g_pfnSHGetSpecialFolderPath = ::GetProcAddress(hDLL,"SHGetSpecialFolderPathA"));
				(*(FARPROC*)&g_pfnSHGetFolderPath = ::GetProcAddress(hDLL,"SHGetFolderPathA"));
			#endif

			if(g_pfnSHGetSpecialFolderPath)
				ret = g_pfnSHGetSpecialFolderPath(hwndOwner,lpszPath,nFolder,fCreate);
			if(ret == FALSE && g_pfnSHGetFolderPath)
			{
				hr = g_pfnSHGetFolderPath(hwndOwner,nFolder | (fCreate ? CSIDL_FLAG_CREATE : 0),NULL,SHGFP_TYPE_DEFAULT,lpszPath);
				ret = (SUCCEEDED(hr)) ? TRUE : FALSE;
			}

			::FreeLibrary(hDLL);

			if(ret)
				return	TRUE;
		}

		return	FALSE;
	}

tabbrowser46.gif
さらにプロジェクトに「DnpFolderMenu.h」を追加する。

「プロジェクト」メニューから「既存項目に追加」を選択する。



※「DnpFolderMenu.h」はこのホームページの中の「フォルダー構造をそのままメニューとして表示する」からダウンロードできます。またダウンロードできるプロジェクト内にも同梱しています

tabbrowser47.gif
そして「DnpFolderMenu.h」を選択して「追加」ボタンを押す。

tabbrowser48.gif
これでビルド/実行するとInternet Explorerで登録した「お気に入り」がメニューとして表示された。

この「お気に入り」は表示順序がInternet Explorerとは異なり、アイコンも表示されないし、追加や整理する機能もない。それらは今後少しづつ実装する。


次回は選択された「お気に入り」を実際にタブとして開く動作を追加する。

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

第11回 選択された「お気に入り」のURLをタブで開く (タブブラウザーを作る)

tabbrowser49.gif
前回はInternet Explore用に登録されている「お気に入り」をメニューとして表示する処理を実装した。今回は選択された「お気に入り」のインターネットショートカットからURLを取り出して実際に「タブ」として開く処理を追加する。

前回までに作成したプロジェクトでソリューションウインドウにある「MainFrm.h」をダブルクリックして開く。そしてここにインターネットショートカットを扱うためのヘッダーファイルを追加する。
#include "intshcut.h"			//■追加

tabbrowser50.gif
次に同じ「MainFrm.h」内の前回作成した「お気に入り」が選択されたときの処理内容を書き換える。また、インターネットショートカットファイルからURLを取り出すための関数も追加する。

今回の処理ではアクティブなタブがある場合はそのタブで、なければ新しくタブを作成してURLを開くようにした。
	//■処理内容変更
	//「お気に入り」が選択されたときの処理
	LRESULT OnFavorite(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
	{
		bool		ret;
		CAtlString	strFolder;
		CAtlString	strFile;
		CAtlString	strURL;

		//選択されたお気に入りのファイル名を取得
		ret = _cFovMenu.GetPathFromID(wID,strFolder,strFile);
		if(ret == false)
		{
			bHandled = FALSE;
			return	0;
		}

		ret = GetInternetShortcut(strFolder + strFile,strURL);
		if(ret == false)
			return	0;

		//タブとしてURLを開く
		{
			int		nPage;
			CTabBrowser100View* pView = NULL;

			nPage = m_view.GetActivePage();
			if(nPage >= 0)
			{
				//現在のタブからビューを取得
				pView = (CTabBrowser100View*)m_view.GetPageData(nPage);
			}

			if(pView == NULL)
			{
				//新しいタブを作成
				pView = new CTabBrowser100View(&m_view);	//タブビューのポインタを渡しておく
				pView->Create(m_view, rcDefault, _T("about:blank"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
				m_view.AddPage(pView->m_hWnd, _T("Document"),-1,pView);
			}

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

		return	0;
	}



	//■追加
	//インターネットショートカットからそれが示すURLを取得する
	bool	GetInternetShortcut(LPCTSTR pszFile,CAtlString& strURL)
	{
		HRESULT	hr;
		LPWSTR	pwszURL;

		CComPtr<IMalloc>					pIMalloc;
		CComPtr<IPersistFile>				pIPersistFile;
		CComPtr<IUniformResourceLocator>	pIUniformResourceLocator;

		strURL = _T("");
		::CoCreateInstance(CLSID_InternetShortcut,NULL,CLSCTX_INPROC_SERVER,IID_IUniformResourceLocator,(void**)&pIUniformResourceLocator);
		if(pIUniformResourceLocator == NULL)
			return	false;

		pIUniformResourceLocator->QueryInterface(IID_IPersistFile,(void**)&pIPersistFile);
		if(pIPersistFile == NULL)
			return	false;

		hr = pIPersistFile->Load(CComBSTR(pszFile),STGM_READ | STGM_SHARE_DENY_NONE);
		if(FAILED(hr))
			return	false;

		hr = pIUniformResourceLocator->GetURL(&pwszURL);
		if(FAILED(hr))
			return	false;

		strURL = pwszURL;

		hr = ::SHGetMalloc(&pIMalloc); 
		if(SUCCEEDED(hr))
			pIMalloc->Free(pwszURL);

		return	true;
	}

tabbrowser51.gif
これでビルド/実行すると、選択した「お気に入り」がタブとして開くようになった。

4月1日から作り始めたWTLのタブブラウザー。まだ思いついて2日だがだんだんブラウザーっぽい機能が増えてきてそれらしくなってきた。それに比例してソースコードの方はスパゲティ状でタ重複コードがどんどん増えてきた。そろそろさすがに整理しないとまずい気もするが、、、まだ放置して次回は「お気に入りに追加」機能を実装する。

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

第12回 「お気に入りに追加」機能を実装する (タブブラウザーを作る)

tabbrowser52.gif
前回までにInternet Explorerに登録されている「お気に入り」が使えるようになった。今回は現在開いているタブのホームページを「お気に入り」として追加する処理を実装する。

前回までに作成したプロジェクトで「リソース」ウインドウを開き、「お気に入り」メニューに「お気に入りに追加」を作る。IDは「ID_IE_ADDFAVORITE」とした。

さらに今回は実装しないが、今後のために「お気に入りの整理」(ID_IE_ORGANIZEFAVORITE)も作成しておいた。

tabbrowser53.gif
次に「ソリューション」ウインドウで「MainFrm.h」を開き、先ほど作成した「お気に入りに追加」メニューが選択されたときの処理を追加する。

ここではタブがあるときに、そのタブのビューに対してAddFavorite()(まだ実装されていない関数)を呼ぶようにした。
	BEGIN_MSG_MAP(CMainFrame)
		MESSAGE_HANDLER(WM_DNP_CHANGEFOCUS, OnDnpChangeFocus)
		NOTIFY_CODE_HANDLER(CBEN_ENDEDIT, OnAddressbarEnter)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
		COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
		COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
		COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
		COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE, OnWindowClose)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE_ALL, OnWindowCloseAll)
		COMMAND_RANGE_HANDLER(ID_WINDOW_TABFIRST, ID_WINDOW_TABLAST, OnWindowActivate)
		COMMAND_ID_HANDLER(ID_IE_BACK, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_NEXT, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_STOP, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_REFRESH, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_ADDFAVORITE, OnIEAddFavorite)		//■追加
		NOTIFY_CODE_HANDLER(TBVN_CONTEXTMENU, OnTabContextMenu)
		MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect)
		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 OnIEAddFavorite(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
	{
		int		nPage;
		CTabBrowser100View* pView = NULL;

		nPage = m_view.GetActivePage();
		if(nPage < 0)
			return	0;
		pView = (CTabBrowser100View*)m_view.GetPageData(nPage);
		if(pView == NULL)
			return	0;

		pView->AddFavorite();

		return	0;
	}

tabbrowser54.gif
次に「TabBrowser100View.h」を開き、先ほど「MainFrm.h」から呼ばれるように設定したAddFavorite()を実装する。

「お気に入りに追加」という処理を独自に実装しても構わない。しかしここではせっかくなのでInternet Explorerに備わっている機能を呼びだす形で処理する。IEコントロールのIWebBrowser2からIServiceProviderを取り出しIServiceProvider::QueryService()でIShellBrowserを取得、そこからIOleCommandTarget::Execを使ってSBCMDID_ADDTOFAVORITESを実行することで標準のUIが開いている。
	//■追加
	//「お気に入りに追加」UIを開く
	bool	AddFavorite(void)
	{
		HRESULT	hr;
		CComPtr<IShellBrowser>		pIShellBrowser;

		#ifndef	SBCMDID_ADDTOFAVORITES
			#define	SBCMDID_ADDTOFAVORITES	8
		#endif

		if(_pIWebBrowser2 == NULL)
			return	false;

		hr = IUnknown_QueryService(_pIWebBrowser2,SID_STopLevelBrowser,IID_IShellBrowser,(void**)&pIShellBrowser);
		if(SUCCEEDED(hr))
			hr = IUnknown_Exec(pIShellBrowser,&CGID_Explorer,SBCMDID_ADDTOFAVORITES,OLECMDEXECOPT_PROMPTUSER,NULL,NULL);

		return	SUCCEEDED(hr) ? true : false;
	}


	//■追加
	//IOleCommandTarget::Execの実行
	HRESULT	IUnknown_Exec(IUnknown* pUnk,const GUID *pguidCmdGroup,DWORD nCmdID,DWORD nCmdExecOpt,VARIANTARG *pvaIn,VARIANTARG *pvaOut)
	{
		CComPtr<IOleCommandTarget>	pIOleCommandTarget;

		if(pUnk == NULL)
			return	E_FAIL;

		pUnk->QueryInterface(IID_IOleCommandTarget,(void**)&pIOleCommandTarget);
		if(pIOleCommandTarget == NULL)
			return	E_FAIL;

		return	pIOleCommandTarget->Exec(pguidCmdGroup,nCmdID,nCmdExecOpt,pvaIn,pvaOut);
	}

tabbrowser55.gif
これでビルド/実行すると「お気に入りに追加」メニューにより追加できるようになった。本当はタブを開いていない時や「about:blank」を開いているときはメニューを無効にするなどの処理が欲しいところだがとりあえず保留しておく。

次回は「お気に入りの整理」機能を実装する。

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

2009年04月03日

第13回 「お気に入りの整理」機能を実装する (タブブラウザーを作る)

tabbrowser56.gif
前回は「お気に入りに追加」機能を実装した。今回は「お気に入りの整理」機能を追加する。

前回までに作成したプロジェクトの「MainFrm.h」を開く、そして前回追加しておいた「お気に入りの整理」メニューが選択されたときの処理を実装する。

「お気に入りの整理」は「お気に入りの追加」と同じようにInternet Explorerが標準で備えている機能を呼びだすことで実現する。ただし前回の「お気に入りの追加」とは異なりCOMインターフェース経由ではなく、shdocvw.dllをLoadLibraryしてDoOrganizeFavDlgWなどを呼びだすことにより開く。
	BEGIN_MSG_MAP(CMainFrame)
		MESSAGE_HANDLER(WM_DNP_CHANGEFOCUS, OnDnpChangeFocus)
		NOTIFY_CODE_HANDLER(CBEN_ENDEDIT, OnAddressbarEnter)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
		COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
		COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
		COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
		COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE, OnWindowClose)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE_ALL, OnWindowCloseAll)
		COMMAND_RANGE_HANDLER(ID_WINDOW_TABFIRST, ID_WINDOW_TABLAST, OnWindowActivate)
		COMMAND_ID_HANDLER(ID_IE_BACK, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_NEXT, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_STOP, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_REFRESH, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_ADDFAVORITE, OnIEAddFavorite)
		COMMAND_ID_HANDLER(ID_IE_ORGANIZE_FAVORITE, OnIEOrganizeFavorite)	//■追加
		NOTIFY_CODE_HANDLER(TBVN_CONTEXTMENU, OnTabContextMenu)
		MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect)
		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 OnIEOrganizeFavorite(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
	{
		DoOrganizeFavDlg(m_hWnd);
		return	0;
	}


	//■追加
	//「お気に入りの整理」ウインドウを開く
	//pszFolder==NULLなら「お気に入り」がルートで開く
	bool	DoOrganizeFavDlg(HWND hWnd,LPTSTR pszFolder=NULL)
	{
		BOOL (WINAPI* pfnDoOrganizeFavDlg)(HWND hWnd,LPTSTR pszFolder);

		BOOL	ret;
		HMODULE	hDLL;

		ret = FALSE;
		hDLL = ::LoadLibrary(_T("shdocvw.dll"));
		if(hDLL == NULL)
			return	false;
	#ifdef _UNICODE
		(FARPROC&)pfnDoOrganizeFavDlg = ::GetProcAddress(hDLL,"DoOrganizeFavDlgW");
	#else
		(FARPROC&)pfnDoOrganizeFavDlg = ::GetProcAddress(hDLL,"DoOrganizeFavDlg");
	#endif
		if(pfnDoOrganizeFavDlg)
			ret = pfnDoOrganizeFavDlg(hWnd,pszFolder);

		::FreeLibrary(hDLL);

		return	ret ? true : false;
	}

tabbrowser57.gif
これでビルド/実行すると「お気に入りの整理」機能が使えるようになった。
この「お気に入りの整理」用のDoOrganizeFavDlg()は引数に任意のフォルダを与えるとそこをルートとしたウインドウが開く。そのため自作の各種アプリケーションでも再利用可能だ。

次回は「お気に入り」メニューにアイコンを表示できるようにする。

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

第14回 「お気に入り」メニューにアイコンを表示する (タブブラウザーを作る)

tabbrowser58.gif
前回までに「お気に入り」メニューが少しずつ使えるようになってきた。しかしWindows標準のメニューのままで見た目がイマイチだった。

今回は「お気に入り」のインターネットショートカットからアイコンを取得して、メニューに表示する。

前回までに作成したプロジェクトを開き、「MainFrm.h」のお気に入りメニュー用クラスにメニューアイテムが追加されたときに呼ばれる処理を追加する。Internet Explorerの「お気に入り」メニューはオーナードローで作成され、アイコンはおそらくマルチスレッドで順次読み込み表示をかけているのだと思う。
しかし、今回はメニューアイテム追加時にアイコンを設定するようにした。結果として最初にメニューを開くときにアイコンを読み込む処理が発生するため、「お気に入り」メニューが重くなる。そのためこれはちょっと改悪な実装だ。

※実は今回仮想関数OnAppendedMenuが呼び出されるようにCDnpFolderMenuを修正している。その部分はこのページには載せていないです。ここに示したソースコードを正常に動かすにはCDnpFolderMenuを今回のサンプルプロジェクトのものと差し替えてください。
//「お気に入り」メニュークラス
class	CFavoritesMenu	: public CDnpFolderMenu
{
public:
	BEGIN_MSG_MAP(CFavoritesMenu)
		CHAIN_MSG_MAP(CDnpFolderMenu)
	END_MSG_MAP()

	//■追加
	~CFavoritesMenu()
	{
		size_t	i;
		size_t	nSize;

		//メニューで利用したHBITMAPを削除
		nSize = _ahBitmap.GetCount();
		for(i = 0; i < nSize; i++)
			::DeleteObject(_ahBitmap[i]);
		_ahBitmap.RemoveAll();
	}

protected:

	CAtlArray<HBITMAP>	_ahBitmap;	//■追加 メニューで利用したHBITMAPを保持

	//■追加
	//「ファイル」の項目が追加された
	virtual	bool	OnAppendedMenu(INT_PTR nIndex,CAtlArray<CFileData>& acFileList,HMENU hMenu,UINT nID)
	{
		HBITMAP	hBitmap;

		//ビットマップを取得
		hBitmap = GetHBitmapFromFile(acFileList[nIndex]._strPath + acFileList[nIndex]._strFile);
		if(hBitmap == NULL)
			return	false;

		//ビットマップをメニュー項目に割り当てる
		::SetMenuItemBitmaps(hMenu,nID,MF_BYCOMMAND,hBitmap,hBitmap);
		_ahBitmap.Add(hBitmap);

		return	true;
	}

	//■追加
	//「フォルダ」の項目が追加された
	virtual	bool	OnAppendedMenu(INT_PTR nIndex,CAtlArray<CFileData>& acFileList,HMENU hMenu,HMENU hSubMenu)
	{
		HBITMAP		hBitmap;

		//ビットマップを取得
		hBitmap = GetHBitmapFromFile(acFileList[nIndex]._strPath + acFileList[nIndex]._strFile);
		if(hBitmap == NULL)
			return	false;

		//ビットマップをメニュー項目に割り当てる
		::SetMenuItemBitmaps(hMenu,::GetMenuItemCount(hMenu) - 1,MF_BYPOSITION,hBitmap,hBitmap);
		_ahBitmap.Add(hBitmap);

		return	true;
	}

	//■追加
	//ファイル名からアイコンのビットマップ取得
	HBITMAP	GetHBitmapFromFile(LPCTSTR pszFile)
	{
		HBITMAP		hBitmap;
		CAtlString	strFolder;
		CAtlString	strFile;
		SHFILEINFO	sShFileInfo;
		HICON		hIcon;
		ICONINFO	sIconInfo;
		HIMAGELIST	hImageList;

		//パスからアイコン取得
		hImageList = (HIMAGELIST)::SHGetFileInfo(pszFile,0,&sShFileInfo,sizeof(SHFILEINFO),SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
		hIcon = ::ImageList_GetIcon(hImageList,sShFileInfo.iIcon,ILD_NORMAL);
		::ImageList_Destroy(hImageList);
		if(hIcon == NULL)
			return	NULL;

		//アイコンからビットマップ取得
		::ZeroMemory(&sIconInfo,sizeof(ICONINFO));
		::GetIconInfo(hIcon,&sIconInfo);
		hBitmap = sIconInfo.hbmColor;
		::DeleteObject(sIconInfo.hbmMask);
		::DestroyIcon(hIcon);
		if(hBitmap == NULL)
			return	NULL;

		return	hBitmap;
	}

tabbrowser59.gif
これでビルド/実行すると「お気に入り」メニューにアイコンが表示された。まだ簡易的な実装なため、アイコンの余白部分が黒くなっていたり、先に述べたように重かったりする。しかしとりあえずこのまま保留する。

次回はタブに表示している現在のページ名をもう少しきちんとしたタイミングで実装されるように修正する。

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

第15回 ちゃんとしたタイミングで「タブ」にホームページ名を表示する (タブブラウザーを作る)

tabbrowser60.gif
前回までの作業で「タブ」の部分にそのとき開いているホームページの名前が表示されていた。しかし表示するタイミングがDWebBrowserEvents2 :: DocumentCompleteが届いたとき、、、つまり読み込みが完全に終わったときだったためなかなかタイトルが表示されないという問題があった。今回はタイミングをDWebBrowserEvents2 :: TitleChangeが届いたときにする。

また、まだしばらく利用することはないがDIID_DWebBrowserEvents2でサポートされているいくつかのイベントの受信処理を追加しておく。
	BEGIN_SINK_MAP(CTabBrowser100View)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW2, OnNewWindow2)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete)
		//■以下のSINK_ENTRY_EX追加
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_STATUSTEXTCHANGE, OnStatusTextChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_DOWNLOADCOMPLETE, OnDownloadComplete)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_COMMANDSTATECHANGE, OnCommandStateChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_DOWNLOADBEGIN, OnDownloadBegin)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_PROGRESSCHANGE, OnProgressChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_PROPERTYCHANGE, OnPropertyChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_TITLECHANGE, OnTitleChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NAVIGATECOMPLETE2, OnNavigateComplete2)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NAVIGATEERROR, OnNavigateError)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_PRIVACYIMPACTEDSTATECHANGE, OnPrivacyImpactedStateChange)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_NEWWINDOW3, OnNewWindow3)
		SINK_ENTRY_EX(SINKID_EVENTS, DIID_DWebBrowserEvents2, DISPID_SETSECURELOCKICON, OnSetSecureLockIcon)
	END_SINK_MAP()

	//■追加ここから↓の関数いっぱい

	void __stdcall OnStatusTextChange(BSTR bstrText)
	{
	}

	void __stdcall OnDownloadComplete()
	{
	}

	void __stdcall OnCommandStateChange(long lCommand,VARIANT_BOOL vbEnable)
	{
	}

	void __stdcall OnDownloadBegin(void)
	{
	}

	void __stdcall OnProgressChange(long lProgress,long lProgressMax)
	{
	}

	void __stdcall OnPropertyChange(BSTR bstrProperty)
	{
	}

	void __stdcall OnTitleChange(BSTR bstrTitle)
	{
		//タブに表示されている名前を変更
		if(_pTabView == NULL || _pIWebBrowser2 == NULL)
			return;

		int			nPage;
		CComBSTR	bstr;

		nPage = _pTabView->PageIndexFromHwnd(m_hWnd);			//このウインドウに関連づいているタブのインデックス取得
		_pTabView->SetPageTitle(nPage,(CAtlString)bstrTitle);	//タブの名前を変更
	}

	void __stdcall OnNavigateComplete2(IDispatch *pDisp,VARIANT *vURL)
	{
	}

	void __stdcall OnNavigateError(IDispatch *pDisp,VARIANT *vURL,VARIANT *vTargetFrameName,VARIANT *vStatusCode,VARIANT_BOOL *vbCancel)
	{
	}

	void __stdcall OnPrivacyImpactedStateChange(VARIANT_BOOL vbPrivacyImpacted)
	{
	}

	void __stdcall OnNewWindow3(IDispatch **ppDisp,VARIANT_BOOL *vbCancel,DWORD dwFlags,BSTR bstrUrlContext,BSTR bstrUrl)
	{
	}

	//MSDNにはVARIANTとあるが、longとの間違え
	void __stdcall OnSetSecureLockIcon(long lSecureLockIcon)
	{
	}

	//■追加ここまで↑
	////////////////////////


	//HTMLページが読み終わったときに呼ばれる処理
	void	_stdcall OnDocumentComplete(IDispatch* pDisp, VARIANT* vURL)
	{
		//■内部処理を削除
	}

tabbrowser61.gif
これで「タブ」に変なタイミングでホームページ名が表示されることがなくなった。

次回は「タブ」にアイコンを表示する。

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

第16回 「タブ」にアイコンを表示する (タブブラウザーを作る)

tabbrowser62.gif
今回は「タブ」にそのとき開いているホームページのアイコンを表示する。
WTLのCTabViewはCImageList(HIMAGELIST)を渡すことで画像を表示できるようにできている。その機能を利用して実装する。

前回までに作成したプロジェクトを開き、「MainFrm.h」にCImageListの作成/割り当て/破棄処理を追加する。
	LRESULT OnCreate(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& /*bHandled*/)
	{
		// コマンドバー ウィンドウの作成
		HWND hWndCmdBar = m_CmdBar.Create(m_hWnd, rcDefault, NULL, ATL_SIMPLE_CMDBAR_PANE_STYLE);
		// メニューのアタッチ
		m_CmdBar.AttachMenu(GetMenu());
		// コマンドバー画像の読み込み
		m_CmdBar.LoadImages(IDR_MAINFRAME);
		// 以前のメニューの削除
		SetMenu(NULL);

		HWND hWndToolBar = CreateSimpleToolBarCtrl(m_hWnd, IDR_MAINFRAME, FALSE, ATL_SIMPLE_TOOLBAR_PANE_STYLE);

		CreateSimpleReBar(ATL_SIMPLE_REBAR_NOBORDER_STYLE);
		AddSimpleReBarBand(hWndCmdBar);
		AddSimpleReBarBand(hWndToolBar, NULL, TRUE);

		_wndAddressBar.Create(m_hWnd,CRect(0,0,200,200),0,WS_CHILD | WS_VISIBLE | CBS_DROPDOWN);
		AddSimpleReBarBand(_wndAddressBar);

		CreateSimpleStatusBar();

		m_hWndClient = m_view.Create(m_hWnd, rcDefault, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, WS_EX_CLIENTEDGE);

		UIAddToolBar(hWndToolBar);
		UISetCheck(ID_VIEW_TOOLBAR, 1);
		UISetCheck(ID_VIEW_STATUS_BAR, 1);

		// メッセージ フィルターおよび画面更新用のオブジェクト登録
		CMessageLoop* pLoop = _Module.GetMessageLoop();
		ATLASSERT(pLoop != NULL);
		pLoop->AddMessageFilter(this);
		pLoop->AddIdleHandler(this);

		CMenuHandle menuMain = m_CmdBar.GetMenu();
		m_view.SetWindowMenu(menuMain.GetSubMenu(WINDOW_MENU_POSITION));

		//■2行追加 
		_cTabImageList.Create(16,16,ILC_COLOR | ILC_MASK,1,10);
		m_view.SetImageList(_cTabImageList);

		return 0;
	}


	CImageList	_cTabImageList;		//■追加 タブに表示するアイコン用イメージリスト


	LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled)
	{
		_cTabImageList.Destroy();		//■追加

		// メッセージ フィルターおよび画面更新用のオブジェクト登録解除
		CMessageLoop* pLoop = _Module.GetMessageLoop();
		ATLASSERT(pLoop != NULL);
		pLoop->RemoveMessageFilter(this);
		pLoop->RemoveIdleHandler(this);

		bHandled = FALSE;
		return 1;
	}

tabbrowser63.gif 次にビュー側で開いているホームページのアイコンを取得してタブに割り当てる処理を実装する。 まずはそのためのヘッダーファイルを追加する。
#include "intshcut.h"		//■追加

tabbrowser64.gif そして実際の処理を追加する。ここでは(かえって複雑になっているかもしれないが)簡単にするため、開いているURLのショートカットをテンポラリフォルダに一度作成し、そのショートカットからアイコンを取り出すという方法を採用した。
	//HTMLページが読み終わったときに呼ばれる処理
	void	_stdcall OnDocumentComplete(IDispatch* pDisp, VARIANT* vURL)
	{
		//■処理内容実装
		if(pDisp != _pIWebBrowser2)
			return;			//フレームなどの読み込み終了は無視

		HICON		hIcon;
		CAtlString	strFile;

		//テンポラリファイルパス取得
		{
			DWORD	dwRet;
			TCHAR	pszPath[MAX_PATH + 1024];
			TCHAR	pszFile[MAX_PATH + 1024];

			dwRet = ::GetTempPath(MAX_PATH + 1024,pszPath);				//テンポラリフォルダ取得
			if(dwRet)
				dwRet = ::GetTempFileName(pszPath,NULL,0,pszFile);		//テンポラリファイル名自動生成
			if(dwRet)
				strFile = pszFile;
			if(strFile == _T(""))
				return;
		}

		//インターネットショートカットファイル作成
		{
			bool	ret;

			strFile += _T(".url");
			ret = CreateInternetShortcut((CAtlString)vURL->bstrVal,strFile);
			if(ret == false)
				return;

			//インターネットショートカットにアイコンが適用されるのを待つ(//■■これだとダメ?要調査)
			::SHChangeNotify(SHCNE_CREATE,SHCNF_PATH | SHCNF_FLUSH,(LPCTSTR)strFile,NULL);
		}

		//ファイルからHICONを取得
		{
			SHFILEINFO	sShFileInfo;
			HIMAGELIST	hImageList;

			//パスからアイコン取得
			hImageList = (HIMAGELIST)::SHGetFileInfo(strFile,0,&sShFileInfo,sizeof(SHFILEINFO),SHGFI_SYSICONINDEX | SHGFI_SMALLICON);
			hIcon = ::ImageList_GetIcon(hImageList,sShFileInfo.iIcon,ILD_NORMAL);
			::ImageList_Destroy(hImageList);
		}
		::remove((CAtlStringA)strFile);		//ファイル削除
		if(hIcon == NULL)
			return;

		//タブにHICONを割り当てる
		{
			int			nPage;
			int			nImage;
			CImageList	cImageList;

			nPage = _pTabView->PageIndexFromHwnd(m_hWnd);
			cImageList = _pTabView->GetImageList();			//タブのイメージリスト取得

			nImage = _pTabView->GetPageImage(nPage);		//現在のイメージ番号取得
			if(nImage < 0)
			{
				//タブに画像がなければ新しく追加して使う
				nImage = cImageList.AddIcon(hIcon);
			}
			else
			{
				//タブに画像があればそれと置き換える
				cImageList.ReplaceIcon(nImage,hIcon);
			}
			_pTabView->SetPageImage(nPage,nImage);		//nImageが変わらなくても再描画のためにセットしなおす
		}
		::DestroyIcon(hIcon);
	}


	//■追加
	//インターネットショートカットの作成
	//pszFileNameは必ず拡張子を.urlにすること!
	bool	CreateInternetShortcut(LPCTSTR pszURL, LPCTSTR pszFileName)
	{
		HRESULT	hr;

		CComPtr<IUniformResourceLocator>	pIUniformResourceLocator;
		CComPtr<IPersistFile>				pIPersistFile;

		::CoCreateInstance(CLSID_InternetShortcut, NULL, CLSCTX_INPROC_SERVER,IID_IUniformResourceLocator,(void**)&pIUniformResourceLocator);
		if(pIUniformResourceLocator == NULL)
			return	false;

		pIUniformResourceLocator->QueryInterface(IID_IPersistFile,(void**)&pIPersistFile);
		if(pIPersistFile == NULL)
			return	false;

		hr = pIUniformResourceLocator->SetURL((CAtlStringW)pszURL,0);

		if(SUCCEEDED(hr))
			hr = pIPersistFile->Save((CAtlStringW)pszFileName,TRUE);

		return	SUCCEEDED(hr) ? true : false;
	}

tabbrowser65.gif
これでタブにアイコンが表示されるようになった。しかしアイコンの設定がOnDocumentComplete内だけのためほかのページを開いている最中にも前のホームページのアイコンが表示されていたり、きちんとアイコンが取得できないホームページがあったりする。それらの点は今後修正することにして今は保留しておく。

さすがにここまで来るとソースコードが分かりにくくなってきたので、次回は重い腰を上げてソースコードの整理をする。

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

第17回 ソースコードを少し整理する (タブブラウザーを作る)

前回までにATL/WTLのアプリケーションウイザードのタブ型ビューMDIをひな形にタブブラウザーを作ってきた。だんだんと機能も増えて少しずつ形になってきた。

しかし機能が増えるに従ってソースコードがごちゃごちゃとしてきて大変なことになっている。作り上げる目標もなく、思いつきのままに実装をしているため初期に実装したものの今では不要なものまででてきている。

今回は前回までに作成したソースコードを少し整理する。



stdafx.cppへの追い出し

ソースコードの中に「インターネットショートカットを作る」「インターネットショートカットからURLを取り出す」というような汎用的に使える関数がいくつかある。それらをコピペでstdafx.cppへ移動する。

これらは一度作ったら編集する可能性も少ないので関数宣言をstdafx.hに置いた。
#include "atlmisc.h"
#include "atlstr.h"

bool	CreateInternetShortcut(LPCTSTR pszURL, LPCTSTR pszFileName);
HRESULT	IUnknown_Exec(IUnknown* pUnk,const GUID *pguidCmdGroup,DWORD nCmdID,DWORD nCmdExecOpt,VARIANTARG *pvaIn,VARIANTARG *pvaOut);
BOOL	SafeSHGetSpecialFolderPath(HWND hwndOwner,LPTSTR lpszPath,int nFolder,BOOL fCreate);
bool	GetInternetShortcut(LPCTSTR pszFile,CAtlString& strURL);
HBITMAP	GetHBitmapFromFile(LPCTSTR pszFile);
bool	DoOrganizeFavDlg(HWND hWnd,LPTSTR pszFolder=NULL);






重複コードの関数化(その1)ページビューの取得

MainFrm.h内では、
{
	int		nPage;
	CTabBrowser100View*	pView = NULL;

	nPage = m_view.GetActivePage();
	if(nPage >= 0)
		pView = (CTabBrowser100View*)m_view.GetPageData(nPage);
	if(pView)
	{
		....
	}
}
というようにビュークラスへのポインタを取得する処理がいたるところに散在している。

同じようなソースをその都度書いていると、処理を変更したいときに変更漏れが生じたり、処理そのものを間違えてバグを生み出したりする。もちろん重複コードがたくさんあるとそれだけでソースコードの可読性も落ちる。

そのためこの部分を、、、
	//指定されたタブのビュークラスを取得
	CTabBrowser100View*	GetPageView(int nPage)
	{
		if(nPage < 0)
			return	NULL;
		return	(CTabBrowser100View*)m_view.GetPageData(nPage);
	}

	//アクティブページのビュークラスを取得
	CTabBrowser100View*	GetActivePageView(void)
	{
		return	GetPageView(m_view.GetActivePage());
	}
という2つの関数を用意して、
{
	CTabBrowser100View*	pView;

	pView = GetActivePageView();
	if(pView)
	{
		....
	}
}
と簡潔に書けるようにする。これで重複するソースコードをいくらか削除できる。






重複コードの関数化(その2)タブの新規作成

タブが開いていない状態で「お気に入り」が選択されたとき、リンクを「新しいウインドウで開く」ときなどでタブを作る処理は
	pView = new CTabBrowser100View(&m_view);	//タブビューのポインタを渡しておく
	pView->Create(m_view, rcDefault, _T("about:blank"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
	m_view.AddPage(pView->m_hWnd, _T("Document"),-1,pView);
というようなコードをその都度書いてきた。これを関数化して重複コードを減らす。
	//タブの新規作成
	//nPosはタブを追加する場所。nPos<0(もしくはnPos=m_view.GetPageCount())で一番後ろ、
	//そのほかの数値はCTabView::InsertPageにそのまま渡す
	//
	//戻ったポインタは自動削除されるため、deleteの必要なし
	//
	CTabBrowser100View*	CreateNewTab(LPCTSTR pszURL,LPCTSTR pszTitle,int nPos=-1,int nImage=-1)
	{
		bool	ret;
		CTabBrowser100View*	pView;

		pView = new CTabBrowser100View(&m_view);	//タブビューのポインタを渡しておく
		if(pView == NULL)
			return	NULL;

		//ビューウインドウ生成
		if(pszURL == NULL || *pszURL == NULL)
			pView->Create(m_view, rcDefault, _T("about:blank"), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
		else
			pView->Create(m_view, rcDefault, pszURL, WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_HSCROLL | WS_VSCROLL, 0);
		if(pView->IsWindow() == FALSE)
		{
			delete	pView;
			return	NULL;		//ビューウインドウ生成失敗
		}

		//タブにビューを割り当てる
		if(nPos < 0)
			nPos = m_view.GetPageCount();
		if(pszTitle)
			ret = m_view.InsertPage(nPos,pView->m_hWnd,pszTitle,nImage,pView);
		else
			ret = m_view.InsertPage(nPos,pView->m_hWnd,_T(""),nImage,pView);

		if(ret == false)
		{
			delete	pView;
			return	NULL;		//タブへの追加失敗
		}

		return	pView;
	}

この関数の実装によりCMainFrame内であれば、簡単にタブを作れるようになった。しかし、ビュークラスからタブを作りたい場合はこのままでは直接CreateNewTabを呼びだせない。

そのためWM_DNP_CREATENEWTABという独自メッセージを利用してビューからのメッセージによりタブを作れるようにする。今回は、、、
class	CTabBrowser100View;

// CMainFrame::nDnpCreateNewTab() 用の情報クラス
class	CDnpCreateNewTabInfo
{
public:
	CAtlString	strURL;
	CAtlString	strTitle;
	int		nImage;
	int		nPos;

	CTabBrowser100View*	pView;

	CDnpCreateNewTabInfo()
	{
		strURL	= _T("about:blank");
		pView	= NULL;
		nImage	= -1;
		nPos	= -1;
	}
};



	//WM_DNP_CREATENEWTABによりタブの作成
	//WPARAMはCDnpCreateNewTabInfoへのポインタを渡す
	LRESULT OnDnpCreateNewTab(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		CDnpCreateNewTabInfo*	pInfo = (CDnpCreateNewTabInfo*)wParam;

		if(pInfo == NULL)
			return	0;

		pInfo->pView = CreateNewTab(pInfo->strURL,pInfo->strTitle,pInfo->nPos,pInfo->nImage);

		return	0;
	}
というような雰囲気で実装した。






ビュークラスの単純機能をほかのクラスに追い出す

CTabBrowser100View の中には
	void	Back(void)
	{
		if(_pIWebBrowser2)
			_pIWebBrowser2->GoBack();
	}

	void	Next(void)
	{
		if(_pIWebBrowser2)
			_pIWebBrowser2->GoForward();
	}
のようにIWebBrowser2のメソッドを呼び出すだけの単純な関数が多くある(また今後も増えるだろう)。 それらをCIEUtilityというクラスに追い出して、CTabBrowser100View派生元に追加する。
class	CIEUtility
{
protected:
	CComPtr<IWebBrowser2>		_pIWebBrowser2;

public:

	//URLを開く
	bool	Navigate(LPCTSTR pszURL)
	{
		HRESULT	hr;

		if(_pIWebBrowser2 == NULL || pszURL == NULL || *pszURL == NULL)
			return	false;

		hr = _pIWebBrowser2->Navigate(CComBSTR(pszURL),&CComVariant(),&CComVariant(),&CComVariant(),&CComVariant());

		return	SUCCEEDED(hr) ? true : false;
	}

	void	Back(void)
	{
		if(_pIWebBrowser2)
			_pIWebBrowser2->GoBack();
	}

(・・・省略・・・)

	//「戻る」や「進む」ための履歴がIEにあるかどうかのチェック
	bool	IsButtonEnable(bool& bBack,bool& bNext)
	{
		HRESULT	hr;
		DWORD	dwCount;

		CComPtr<ITravelLogStg>			pITravelLogStg;
		CComPtr<IServiceProvider>		pIServiceProvider;

		bBack = false;
		bNext = false;

		if(_pIWebBrowser2)
			_pIWebBrowser2->QueryInterface(&pIServiceProvider);
		if(pIServiceProvider)
			pIServiceProvider->QueryService(SID_STravelLogCursor,IID_ITravelLogStg,(void**)&pITravelLogStg);
		if(pITravelLogStg == NULL)
			return	false;

		hr = pITravelLogStg->GetCount(TLEF_RELATIVE_BACK,&dwCount);
		if(dwCount)
			bBack = true;
		if(FAILED(hr))
			return	false;

		hr = pITravelLogStg->GetCount(TLEF_RELATIVE_FORE,&dwCount);
		if(FAILED(hr))
			return	false;
		if(dwCount)
			bNext = true;

		return	true;
	}
};






外部からビュークラスのIWebBrowser2に直接アクセスしないようにする

現状のCMainFrame::OnDnpChangeFocus() では
	//フォーカス変更時処理
	LRESULT OnDnpChangeFocus(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		BOOL	bHasFocus = (BOOL)wParam;		//アドレスバーなどがフォーカスを受け取っていたらTRUE

		CTabBrowser100View*	pView;

		pView = GetActivePageView();
		if(pView == NULL)
			return	0;

		if(bHasFocus)
		{
			//アドレスバーなどIE以外がフォーカスを受け取った

			CComPtr<IOleInPlaceObject> pIOleInPlaceObject;

			pView->_pIWebBrowser2->QueryInterface(&pIOleInPlaceObject);
			if(pIOleInPlaceObject)
				pIOleInPlaceObject->UIDeactivate();		//IEのUIを無効化
		}

		return	0;
	}
のようにビュークラス内の_pIWebBrowser2に直接アクセスしている。このような実装は行儀がわるいので修正する。 ここではCIEUtility::SetFocusChange()を作り、それを呼ぶようにした。






必要ないコードを削除

ビュークラス内のCTabBrowser100View::GetBrowserCtrlとCTabBrowser100View::PreTranslateMessage()は必要ないので削除する。

GetBrowserCtrlの削除に伴ってCTabBrowser100View::_pIWebBrowser2のインターフェースを確保する部分がなくなったのでしその処理をOnCreate()内で行っておく。
	//Create関数のオーバーライド。内部でIEの取得、接続などを行う
	HWND Create(HWND hWndParent, _U_RECT rect = NULL, LPCTSTR szWindowName = NULL,
			DWORD dwStyle = 0, DWORD dwExStyle = 0,
			_U_MENUorID MenuOrID = 0U, LPVOID lpCreateParam = NULL)
	{
		HWND	hWnd;

		hWnd = __super::Create(hWndParent,rect,szWindowName,dwStyle,dwExStyle,MenuOrID,lpCreateParam);
		if(hWnd == NULL)
			return	NULL;			//Create失敗

		QueryControl(&_pIWebBrowser2);		//_pIWebBrowser2にこのビューに関連づいているIEをセットする
		if(_pIWebBrowser2 == NULL)
			return	hWnd;			//WebBrowser取得失敗

		Advise(_pIWebBrowser2);		//IEとの接続

		return	hWnd;
	}



各クラス用のヘッダーファイルを作成する

今まではMainFrm.h内に複数のクラスを記述するなど、どんどん増やしていた。これをCFavoritesMenuは「FavoritesMenu.h」内に、、、というようにヘッダーファイルに分ける。






(その他。。。?)

現状ではクラス内のpublic、private、protectedの分類やコメントが全然ない。
そこまで整理しておこうと思ったのだがだんだん面倒になってきたのでこのくらいでソースコードの整理は終わりにした。

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

第18回 アドレスバーに自動で現在のURLを表示する (タブブラウザーを作る)

tabbrowser66.gif
今までアドレスバーはURLを入力して開くというアドレスバー→タブ方向の一方的な機能しか実装されていなかった。Internet Explorerなどのアドレスバーではリンクのクリックなどにより読み込まれたホームページのURLが自動でアドレスバーに表示され、アドレスバーとタブが双方向にアクセスし合っている。

今回はタブ内で読み込まれたURLをアドレスバーに表示する機能を実装する。まずはURL変更をメインウインドウに知らせるための独自メッセージをstdafx.h内に定義する。
#define	WM_DNP_CHANGEADDRESS	(WM_APP + 3)		//■追加

tabbrowser67.gif そして定義した独自メッセージハンドラをMainFrm.h内に書く。ここでは(あまりやりたくはないのだが)直接アドレスバーへSetWindowTextでURLを書き込むことにした。
	BEGIN_MSG_MAP(CMainFrame)
		MESSAGE_HANDLER(WM_DNP_CREATENEWTAB, OnDnpCreateNewTab)
		MESSAGE_HANDLER(WM_DNP_CHANGEFOCUS, OnDnpChangeFocus)
		MESSAGE_HANDLER(WM_DNP_CHANGEADDRESS, OnDnpChangeAddress)		//■追加
		NOTIFY_CODE_HANDLER(CBEN_ENDEDIT, OnAddressbarEnter)
		MESSAGE_HANDLER(WM_CREATE, OnCreate)
		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
		COMMAND_ID_HANDLER(ID_APP_EXIT, OnFileExit)
		COMMAND_ID_HANDLER(ID_FILE_NEW, OnFileNew)
		COMMAND_ID_HANDLER(ID_VIEW_TOOLBAR, OnViewToolBar)
		COMMAND_ID_HANDLER(ID_VIEW_STATUS_BAR, OnViewStatusBar)
		COMMAND_ID_HANDLER(ID_APP_ABOUT, OnAppAbout)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE, OnWindowClose)
		COMMAND_ID_HANDLER(ID_WINDOW_CLOSE_ALL, OnWindowCloseAll)
		COMMAND_RANGE_HANDLER(ID_WINDOW_TABFIRST, ID_WINDOW_TABLAST, OnWindowActivate)
		COMMAND_ID_HANDLER(ID_IE_BACK, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_NEXT, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_STOP, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_REFRESH, OnIECommand)
		COMMAND_ID_HANDLER(ID_IE_ADDFAVORITE, OnIEAddFavorite)
		COMMAND_ID_HANDLER(ID_IE_ORGANIZE_FAVORITE, OnIEOrganizeFavorite)
		NOTIFY_CODE_HANDLER(TBVN_CONTEXTMENU, OnTabContextMenu)
		MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect)
		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()


	//■追加
	//アドレスバー表示用URLの変更処理
	//アクティブなタブのIEでURL変更があったときにメッセージが届く
	LRESULT OnDnpChangeAddress(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
	{
		LPCTSTR		pszURL = (LPCTSTR)wParam;

		if(pszURL == NULL)
			return	0;

		_wndAddressBar.SetWindowText(pszURL);

		return	0;
	}

tabbrowser68.gif
さらにビュークラスにIEコントロールからのイベントを受信して、URL変更をメインウインドウへ知らせる処理を追加する。

URLはIEコントロールからのOnNavigateComplete2イベントを受信したときにアドレスバーへ表示することにした。
	//■追加
	//メインウインドウへアドレス変更を通知する
	bool	NotifyChangeURL(LPCTSTR pszURL)
	{
		if(_pTabView == NULL)
			return	false;
		if(_pTabView->GetActivePage() != _pTabView->PageIndexFromHwnd(m_hWnd))
			return	false;			//このビューがアクティブでなければ処理しない

		GetTopLevelWindow().SendMessage(WM_DNP_CHANGEADDRESS,(WPARAM)pszURL);

		return	true;
	}

	void __stdcall OnNavigateComplete2(IDispatch *pDisp,VARIANT *pvURL)
	{
		//■処理内容実装
		if(pDisp != _pIWebBrowser2)
			return;			//フレームなどのイベントは無視

		NotifyChangeURL((CAtlString)pvURL->bstrVal);
	}

tabbrowser69.gif
これでビルド/実行するとアクティブタブでのページ切り替えを検出してアドレスバーのURLが自動的に変わるようになった。

まだこの状態だと、タブを切り替えたときにきちんとアドレスバーのURL表示が更新されない、次回はその点を修正する。


ちなみにInternet Exploreのアドレスバーではユーザーがアドレスを入力中(もしくは入力した形跡がある)ならアドレスバーの表示を変更しない、などの機能も備えているようだ。

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

前の10件 1  2  3  4  5  6  7





usefullcode@gmail.com

About 2009年04月

2009年04月にブログ「UsefullCode.net」に投稿されたすべてのエントリーです。

前の記事は2009年03月です。

次の記事は2010年05月です。

他にも多くのエントリーがあります。メインページ記事一覧も見てください。