
これまでの作業でタブブラウザーとしてまだ「お気に入り」機能を中心に致命的な機能不足があるものの、普段使いにも耐えれるレベルになってきた。しかし最近のブラウザーに必ず付いている検索バーがないため、検索しづらいという使いにくさもあった。
ゼロから検索バーを実装するのが正攻法だが、せっかくなので今回は「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;
}

次に「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;
}

そしてタブが作成されるときに、「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;
}

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

ビュークラス内のコンストラクタ周りでリバー用の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; //■追加
}

タブが切り替えられたときに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;
}

最後に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;
}

これでビルド/実行してタブを開くと「Googleツールバー」が表示され、検索などができるようになった。もちろんページランクの表示機能なども動作した。
しかしホスト方法、特に親ウインドウを共通にしているなどだいぶ実装を省略しているため、「Googleツールバー」ではドロップダウンリストからアクセスできる検索履歴を利用するときにうまく動かないことがあるようだ。
次回は「印刷」機能を実装する。
プロジェクトファイルをダウンロード