第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の分類やコメントが全然ない。
そこまで整理しておこうと思ったのだがだんだん面倒になってきたのでこのくらいでソースコードの整理は終わりにした。

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


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