第24回 「Googleツールバー」をホストし検索バーとして利用する (タブブラウザーを作る)

tabbrowser92.gif
これまでの作業でタブブラウザーとしてまだ「お気に入り」機能を中心に致命的な機能不足があるものの、普段使いにも耐えれるレベルになってきた。しかし最近のブラウザーに必ず付いている検索バーがないため、検索しづらいという使いにくさもあった。

ゼロから検索バーを実装するのが正攻法だが、せっかくなので今回は「Googleツールバー」を利用できるようにする。当然のことながらパソコンに「Googleツールバー」がインストールされている必要がある。

まず下準備としてすべてのビューに対してタブの切り替えが通知されるようにする。「MainFrm.hのOntTabPageActivatedに処理を追加する。
	//タブが選択されたときの処理
	LRESULT OnTabPageActivated(int idCtrl, LPNMHDR pnmh, BOOL& bHandled)
	{
		if(pnmh == NULL)
			return	0;

		//■追加
		//全てのビューに対してタブ変更があったことを通知
		{
			int		i;
			int		nCount;
			CTabBrowser100View*	pView;

			int kk=m_view.GetActivePage();

			nCount = m_view.GetPageCount();
			for(i = 0; i < nCount; i++)
			{
				pView = GetPageView(i);
				if(pView == NULL)
					continue;

				if(pnmh->idFrom == i)
					pView->OnTabPageChange(true);
				else
					pView->OnTabPageChange(false);
			}
		}

		bool		ret;
		CTabBrowser100View*	pView;

		pView = GetActivePageView();		//アクティブビュー取得
		if(pView == NULL)
			return	0;

		//アドレスバーのURL変更
		{
			CAtlString	strURL;

			ret = pView->GetURL(strURL);		//URL取得
			if(ret)
				_wndAddressBar.SetWindowText(strURL);
		}

		//ステータスバーのテキスト変更
		{
			CAtlString	strText;

			pView->GetLastStatusText(strText);
			if(::IsWindow(m_hWndStatusBar))
				::SetWindowText(m_hWndStatusBar,strText);
		}

		//プログレス変更
		{
			long	nProgress;
			long	nProgressMax;

			pView->GetLastProgress(nProgress,nProgressMax);
			RefreshProgressBar(nProgress,nProgressMax);
		}

		return	0;
	}

tabbrowser93.gif 次に「Googleツールバー」を表示用の位置を確保するためにツールバー領域にリバーを作る。ここではCDummyWndというダミーウインドウを用意してそれにたいしてリバーを作成した。
	//■追加
	class	CDummyWnd	: public CWindowImpl<CDummyWnd>
	{
	public:
		BEGIN_MSG_MAP(CDummyWnd)
		END_MSG_MAP()
	};

	CDummyWnd			_wndDummy;		//■追加


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

		//■2行追加
		//ダミーウインドウを生成し、リバーに割り当てる。これはIEツールバーに利用
		//ここで生成してしまうと何もないリバーができてしまうが気にしない
		_wndDummy.Create(m_hWnd,CRect(0,0,500,24),0,WS_CHILD);
		AddSimpleReBarBand(_wndDummy,0,TRUE);						//1行表示

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

		_cTabImageList.Create(16,16,ILC_COLOR | ILC_MASK,1,10);
		m_view.SetImageList(_cTabImageList);

		return 0;
	}


tabbrowser94.gif
そしてタブが作成されるときに、「Googleツールバー」を配置するためのダミーウインドウがビューに渡すようにする。

本来であればこんな実装はするべきではないのだが、、、ソースコードが汚くなるが今回は(も)気にせずにどんどん変更していく。
	//タブの新規作成
	//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,_wndDummy);	//■変更 タブビューのポインタとIEツールバー用のダミーウインドウを渡しておく
		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;
	}

tabbrowser95.gif 「Googleツールバー」のようなInternet Explorer用ツールバーはIDeskBandとして実装されている。その内部では親ウインドウを取得するためにIOleWindow、IWebBrowser2を取得するためにIServiceProvider、フォーカス変更を知らせるためにIInputObjectSiteなどが利用されている。 それらを用意してIDeskBandをホストするためのインターフェースを作成する。
//■追加
//IEツールバーをホストするためのインターフェース
MIDL_INTERFACE("28997A04-86A4-436d-BF58-133F7BD82E8A")
IIEToolbar : public IUnknown
{
public:
	STDMETHOD (put_hwnd)(HWND hWnd) = 0;
	STDMETHOD (put_IWebBrowser2)(IDispatch* pIDispatch) = 0;
};



//■追加
//IEツールバーをホストするためのクラス
class ATL_NO_VTABLE CIEToolbar : 
	public CComObjectRootEx<CComSingleThreadModel>,
	public IOleCommandTarget,
	public IOleWindow,
	public IServiceProvider,
	public IInputObjectSite,
	public IIEToolbar
{
public:
	CIEToolbar()
	{
	}

	BEGIN_COM_MAP(CIEToolbar)
		COM_INTERFACE_ENTRY(IIEToolbar)
		COM_INTERFACE_ENTRY(IOleCommandTarget)
		COM_INTERFACE_ENTRY(IOleWindow)
		COM_INTERFACE_ENTRY(IServiceProvider)
		COM_INTERFACE_ENTRY(IInputObjectSite)
	END_COM_MAP()

	DECLARE_PROTECT_FINAL_CONSTRUCT()


	HWND	m_hWnd;			//ツールバーの親となるウインドウハンドルを保持 IOleWindow::GetWindow() でツールバーに渡す

	CComPtr<IDispatch>		_pIDispatch;		//ツールバーに渡すIWebBrowser2のIDispatch


	//IIEToolbar
	STDMETHOD (put_hwnd)(HWND hWnd)
	{
		m_hWnd = hWnd;
		return	S_OK;
	}
	STDMETHOD (put_IWebBrowser2)(IDispatch* pIDispatch)
	{
		_pIDispatch = NULL;

		if(pIDispatch == NULL)
			return	E_POINTER;

		return	pIDispatch->QueryInterface(IID_IDispatch,(void**)&_pIDispatch);
	}



	//IOleCommandTarget
	STDMETHOD(Exec)(const GUID *pguidCmdGroup,DWORD nCmdID,DWORD nCmdExecOpt,VARIANTARG *pvaIn,VARIANTARG *pvaOut)
	{
		return	S_OK;
	}
	STDMETHOD(QueryStatus)(const GUID *pguidCmdGroup,ULONG cCmds,OLECMD *prgCmds,OLECMDTEXT *pCmdText)
	{
		return	S_OK;
	}


	//IOleWindow
	STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode)
	{
		return	E_NOTIMPL;
	}
	STDMETHOD(GetWindow)(HWND* phwnd)
	{
		if(phwnd == NULL)
			return	E_FAIL;

		*phwnd = m_hWnd;
		return	S_OK;
	}


	//IServiceProvider
	STDMETHOD(QueryService)(REFGUID guidService,REFIID riid,void **ppv)
	{
		if(_pIDispatch)
		{
			*ppv = _pIDispatch;
			return	S_OK;
		}

		{
			//IEを起動して渡す

			HRESULT	hr;
			CComPtr<IWebBrowserApp>	pIWebBrowserApp;

			pIWebBrowserApp = NULL;
			hr = ::CoCreateInstance(CLSID_InternetExplorer,NULL,CLSCTX_SERVER,IID_IWebBrowserApp,(void**)&pIWebBrowserApp);
			if(pIWebBrowserApp)
				pIWebBrowserApp->put_Visible(VARIANT_TRUE);
			if(pIWebBrowserApp)
				pIWebBrowserApp->Navigate(L"about:blank",NULL,NULL,NULL,NULL);
			::Sleep(1000);		//■■1秒固定で待つ
			if(pIWebBrowserApp)
			{
				IWebBrowser2*	pIWebBrowser2;

				hr = pIWebBrowserApp->QueryInterface(IID_IWebBrowser2,(void**)&pIWebBrowser2);

				*ppv = pIWebBrowser2;
				return	S_OK;
			}
		}

		return	E_FAIL;
	}

	//IInputObjectSite
	STDMETHOD(OnFocusChangeIS)(IUnknown *punkObj,BOOL fSetFocus)
	{
		if(fSetFocus)
		{
			HRESULT	hr = E_FAIL;
			CComPtr<IOleInPlaceObject> pIOleInPlaceObject;

			if(_pIDispatch)
				_pIDispatch->QueryInterface(&pIOleInPlaceObject);
			if(pIOleInPlaceObject)
				hr = pIOleInPlaceObject->UIDeactivate();		//IEのUIを無効化
		}

		return	S_OK;
	}
};

tabbrowser96.gif ビュークラス内のコンストラクタ周りでリバー用のHWNDを保存するように修正する。
class CTabBrowser100View : public CWindowImpl<CTabBrowser100View, CAxWindow>
	,public IDispEventImpl<SINKID_EVENTS, CTabBrowser100View, &DIID_DWebBrowserEvents2>
	,public CIEUtility
{
	CTabView*	_pTabView;		//タブビューを保持
	HWND		_hWndRebarIE;	//■追加 IEツールバー用のリバー
public:
	DECLARE_WND_SUPERCLASS(NULL, CAxWindow::GetWndClassName())


	CTabBrowser100View(CTabView* pTabView,HWND hWndRebarIE)		//■変更
	{
		_pTabView = pTabView;
		_nLastProgress		= 0;
		_nLastProgressMax	= -1;
		_hWndRebarIE = hWndRebarIE;		//■追加
	}

tabbrowser97.gif
タブが切り替えられたときにIDeskBandの表示/非表示を切り替える動作を実装する。

ShowWindowで表示/非表示した方が効率がいいのだが、ここではIDeskBand::ShowDWを利用した。


Internet Explorer用のツールバーは1つのツールバーに対して1つのIEコントロールを割り当てる仕組みで作られている。これはIDeskBandがタブブラウザが一般的でなかった頃に設計されたインターフェースであり、当時は1つのInternet Explorerの中で複数のページを切り替えて表示することが想定されていなかったためだ。

スタンドアロン型のIE6からタブブラウザ型のIE7にバージョンアップするときに、このIDeskBandも併せてタブブラウザーに即した形に大きく変わるのではないか?と期待半分、大きく変わったらまたIEツールバーを作りなおさなければいけないという悪寒半分で昔はIE7の登場を待っていた。

しかし蓋を開けてみればIE6からIE7でIDeskBandはまったく同じままに残った。最初は不思議だったのだが、SPY++でのぞいてみるとすぐにその理由がわかった。Internet Explorer 7は1つのタブに対して1つのIEツールバーを生成する。。。そしてタブを切り替えたときにそれらを表示/非表示して切り換えていた。平たく言えば、IE7は内部でタブの数だけIE6を開くような造りになっていた。

今回実装しているタブブラウザー内「Googleツールバー」の表示でも同じ方法を使っている。ビュークラス内でIDeskBandを生成し、タブが切り替わったときにアクティブになったタブのIDeskBandを表示、それ以外を非表示にしている。
	CComPtr<IDeskBand>	_pIDeskBand;	//■追加


	//■追加
	//タブの選択変更があったときメインウインドウから呼ばれる
	bool	OnTabPageChange(bool bActivated)
	{
		if(_pIDeskBand == NULL)
			return	false;

		if(bActivated)
			_pIDeskBand->ShowDW(TRUE);		//IEツールバーを表示
		else
			_pIDeskBand->ShowDW(FALSE);		//IEツールバーを消す

		return	true;
	}

tabbrowser98.gif
最後にCreate()の中で「Googleツールバー」のIDeskBandを生成する。

「Googleツールバー」のGUIDは数年前にメモしていたものを流用したが、2009年4月4日現在の最新版のものでも同じGUIDが使われているようだ。 このGUIDを使ってCOMインターフェースをCoCreateInstanceし、ホストするためのIIEToolbarインターフェースに割り当てている。
		//■追加
		//Googleツールバーを生成する
		{
			HRESULT	hr;
			GUID	rclsid;
			WCHAR	pszGUID[] = {L"{2318C2B1-4965-11d4-9B18-009027A5CD4F}"};		//GoogleツールバーのGUID

			::CLSIDFromString(pszGUID,&rclsid);

			_pIDeskBand = NULL;
			hr = ::CoCreateInstance(rclsid,NULL,CLSCTX_INPROC_SERVER,IID_IDeskBand,(void**)&_pIDeskBand);
			if(_pIDeskBand)
			{
				CComPtr<IObjectWithSite>	pIObjectWithSite;

				_pIDeskBand->QueryInterface(IID_IObjectWithSite,(void**)&pIObjectWithSite);

				DESKBANDINFO	sDeskBandInfo;

				//別に必要ないが情報をたくさん取得
				::ZeroMemory(&sDeskBandInfo,sizeof(DESKBANDINFO));
				sDeskBandInfo.dwMask = DBIM_MINSIZE | DBIM_MAXSIZE | DBIM_INTEGRAL | DBIM_ACTUAL | DBIM_TITLE | DBIM_MODEFLAGS | DBIM_BKCOLOR;
				hr = _pIDeskBand->GetBandInfo(0,DBIF_VIEWMODE_NORMAL,&sDeskBandInfo);

				//■■本当はきちんと調整しないとだめ!!
				CRect	rect(0,0,2000,sDeskBandInfo.ptMinSize.y);		//横サイズは十分大きいサイズを確保(//■■2000px固定)

				//デスクバンドをホスト用クラスに割り当てる
				{
					CComPtr<IIEToolbar>	pIIEToolbar;

					pIIEToolbar = new CComObject<CIEToolbar>;

					pIIEToolbar->put_hwnd(_hWndRebarIE);
					if(_pIWebBrowser2)
						pIIEToolbar->put_IWebBrowser2(_pIWebBrowser2);
					if(pIObjectWithSite)
						hr = pIObjectWithSite->SetSite(pIIEToolbar);

					if(SUCCEEDED(hr))
						hr = _pIDeskBand->ShowDW(TRUE);
				}

				//位置調整
				{
					HWND	hWnd;

					_pIDeskBand->GetWindow(&hWnd);
					if(::IsWindow(hWnd))
						::MoveWindow(hWnd,0,0,rect.Width(),rect.Height(),TRUE);
				}
			}
		}

		return	hWnd;
	}

tabbrowser99.gif
これでビルド/実行してタブを開くと「Googleツールバー」が表示され、検索などができるようになった。もちろんページランクの表示機能なども動作した。

しかしホスト方法、特に親ウインドウを共通にしているなどだいぶ実装を省略しているため、「Googleツールバー」ではドロップダウンリストからアクセスできる検索履歴を利用するときにうまく動かないことがあるようだ。

次回は「印刷」機能を実装する。

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


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