これまでは「お気に入り」フォルダから保存されているインターネットショートカットを列挙して独自にメニューを生成/表示していた。これをInternet Explorerの「お気に入り」や「スタート」メニューと同じようにシェルメニュー(IShellMenu)ベースのものに変える。
Windows Vista以降であればITrackShellMenuを利用することで非常に簡単にIShellMenuをメニューとして表示できる。しかし2009年4月現在Windows Vistaはまだまだ普及率が悪い。そのためITrackShellMenuは使わずに力技でIShellMenuを表示することにした。
CMainFrameにIShellMenuを利用した「お気に入り」表示関数を追加する。
CComPtr<IMenuBand> _pIMenuBand;
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)
{
//とりあえずCallback指定していない
pIShellMenu->Initialize(NULL,-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;
}
COMMAND_ID_HANDLER(ID_IE_ADDFAVORITE, OnIEAddFavorite)
COMMAND_ID_HANDLER(ID_IE_ORGANIZE_FAVORITE, OnIEOrganizeFavorite)
NOTIFY_CODE_HANDLER(TBVN_CONTEXTMENU, OnTabContextMenu)
NOTIFY_CODE_HANDLER(TBVN_PAGEACTIVATED, OnTabPageActivated)
MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelectForPopup) //■追加
// MESSAGE_HANDLER(WM_MENUSELECT, OnMenuSelect) //■削除
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 OnMenuSelectForPopup(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE;
UINT nMenuItem = LOWORD(wParam);
HMENU hMenu = (HMENU)lParam;
if(HIWORD(wParam) & MF_POPUP)
return 0;
if(nMenuItem != 4) //メニュー位置
return 0;
RECT rect;
m_CmdBar.GetItemRect(nMenuItem,&rect);
m_CmdBar.MapWindowPoints(NULL,&rect);
PopupMenu(rect.left,rect.bottom);
return 0;
}
//■追加
class CCommandBarCtrl2 : public CCommandBarCtrlImpl<CCommandBarCtrl2>
{
public:
BEGIN_MSG_MAP(CCommandBarCtrl2)
CHAIN_MSG_MAP(__super)
ALT_MSG_MAP(1) // Parent window messages
CHAIN_MSG_MAP_ALT(__super,1)
ALT_MSG_MAP(2) // MDI client window messages
CHAIN_MSG_MAP_ALT(__super,2)
ALT_MSG_MAP(3) // Message hook messages
CHAIN_MSG_MAP_ALT(__super,3)
END_MSG_MAP()
void DoPopupMenu(int nIndex, bool bAnimate)
{
if(nIndex == 4) //メニュー位置固定
{
HWND hWnd = ::GetAncestor(m_hWnd,GA_ROOT);
::SendMessage(hWnd,WM_MENUSELECT,MAKEWPARAM(nIndex,MF_HILITE),(LPARAM)GetMenu().m_hMenu);
return;
}
__super::DoPopupMenu(nIndex,bAnimate);
}
};
class CMainFrame : public CFrameWindowImpl<CMainFrame>, public CUpdateUI<CMainFrame>,
public CMessageFilter, public CIdleHandler
{
CAddressBarCtrl _wndAddressBar; // アドレスバー用コンボボックス
public:
DECLARE_FRAME_WND_CLASS(NULL, IDR_MAINFRAME)
CTabView m_view;
CCommandBarCtrl2 m_CmdBar;
virtual BOOL PreTranslateMessage(MSG* pMsg)
{
//■追加
if(TranslatePopupMessage(pMsg->hwnd,pMsg->message,pMsg->wParam,pMsg->lParam,pMsg))
return TRUE;
if(_wndAddressBar.IsChild(::GetFocus()) == FALSE) //アドレスバーにフォーカスがあるときはPreTranslateMessageを回さない(アドレスバーでCtrl+Cなどを使えるようにするため)
{
if(CFrameWindowImpl<CMainFrame>::PreTranslateMessage(pMsg))
return TRUE;
}
return m_view.PreTranslateMessage(pMsg);
}
//■追加
//
//ポップアップメニューのメッセージ処理
//
//pMsg == NULLのときは開いているメニューを閉じるべきかどうかのチェックも併せておこなう
//(PreTranslateMessageではpMsgも指定する)
//
bool TranslatePopupMessage(HWND hWnd,UINT uMsg, WPARAM wParam, LPARAM lParam,MSG* pMsg=NULL)
{
if(_pIMenuBand == NULL)
return false;
HRESULT hr;
LRESULT ret = 0;
MSG msg;
if(pMsg == NULL)
{
::ZeroMemory(&msg,sizeof(MSG));
msg.hwnd = hWnd;
msg.message = uMsg;
msg.wParam = wParam;
msg.lParam = lParam;
}
else
{
msg = *pMsg;
}
hr = _pIMenuBand->IsMenuMessage(&msg);
if(hr == S_OK)
hr = _pIMenuBand->TranslateMenuMessage(&msg,&ret);
if(hr == S_OK)
return true;
//メニューを閉じるべきかどうかのチェック
//もう少しチェック項目を増やした方がいい。。。
if(pMsg == NULL
&& (hr == E_FAIL
|| (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
|| uMsg == WM_INITMENUPOPUP
|| uMsg == WM_SIZE
|| uMsg == WM_MOVE
|| uMsg == WM_MOVING
|| uMsg == WM_INITMENU
|| 0))
{
//メニューを閉じる
CComPtr<IOleCommandTarget> pIOleCommandTarget;
_pIMenuBand->QueryInterface(&pIOleCommandTarget);
if(pIOleCommandTarget)
pIOleCommandTarget->Exec(&CLSID_MenuBand,22,0,NULL,NULL);
}
return false;
}
BEGIN_MSG_MAP(CMainFrame)
//■追加
{
//「お気に入り」メニュー用TranslateMessage
if(TranslatePopupMessage(hWnd,uMsg,wParam,lParam))
return TRUE;
}
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)
![]()
これでビルド/実行するとIShellMenuにより「お気に入り」が表示されるようになった。
まだ実装が不十分なため「お気に入り」メニューが開いているのにコマンドバーの「お気に入り」がハイライト状態になっていない、ドラッグアンドドロップによる「お気に入り」の移動ができない、メニュー表示がクラシックスタイルなどなど機能が足りない。
しかし「お気に入り」の並び順がInternet Explorerでの表示と同じだったり、アイコンの読み込みがバックグラウンドで行われる、TIPSが表示されるなど「お気に入り」メニューとしてそこそこ使える状態になっている。
次回は選択した「お気に入り」のURLをタブとして開くようにする。

