EternalWindows
ExplorerBrowser / フォルダの切り替え

ExplorerBrowserに表示されたフォルダを選択した場合、デフォルトでは新しいウインドウが作成されてそこにフォルダの中身が表示されます。 この動作を変更し、現在のウインドウにフォルダの中身を表示するためには、IExplorerBrowser::Adviseを呼び出す必要があります。

HRESULT IExplorerBrowser::Advise(
  IExplorerBrowserEvents *psbe,
  DWORD *pdwCookie
);

psbeは、IExplorerBrowserEventsを実装したオブジェクトのアドレスを指定します。 フォルダを切り替える場合には、このインターフェースを使用して通知が行われます。 pdwCookieは、登録を識別する値を受け取る変数のアドレスを指定します。 この値は登録を解除する際に必要になります。

今回のプログラムは、フォルダの切り替えをExplorerBrowserの中で行っています。

#include <windows.h>
#include <shlobj.h>

#define ID_TOOLBAR 100
#define ID_BACK 200
#define ID_FORWARD 300

#pragma comment (lib, "comctl32.lib")

class CExplorerBrowserHost : public IExplorerBrowserEvents
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();

	STDMETHODIMP OnNavigationPending(PCIDLIST_ABSOLUTE pidlFolder);
	STDMETHODIMP OnViewCreated(IShellView *psv);
	STDMETHODIMP OnNavigationComplete(PCIDLIST_ABSOLUTE pidlFolder);
	STDMETHODIMP OnNavigationFailed(PCIDLIST_ABSOLUTE pidlFolder);

	CExplorerBrowserHost();
	~CExplorerBrowserHost();
	int Run(HINSTANCE hinst, int nCmdShow);
	LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	BOOL Create();

private:
	LONG             m_cRef;
	HWND             m_hwnd;
	HWND             m_hwndToolbar;
	DWORD            m_dwCookie;
	IShellView       *m_pShellView;
	IExplorerBrowser *m_pExplorerBrowser;
};

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

CExplorerBrowserHost *g_pExplorerBrowserHost = NULL;

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	int nResult = 0;

	OleInitialize(NULL);
	
	g_pExplorerBrowserHost = new CExplorerBrowserHost;
	if (g_pExplorerBrowserHost != NULL) {
		nResult = g_pExplorerBrowserHost->Run(hinst, nCmdShow);
		g_pExplorerBrowserHost->Release();
	}

	OleUninitialize();

	return nResult;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	return g_pExplorerBrowserHost->WindowProc(hwnd, uMsg, wParam, lParam);
}


// CExplorerBrowserHost


CExplorerBrowserHost::CExplorerBrowserHost()
{
	m_cRef = 1;
	m_hwnd = NULL;
	m_hwndToolbar = NULL;
	m_dwCookie = 0;
	m_pShellView = NULL;
	m_pExplorerBrowser = NULL;
}

CExplorerBrowserHost::~CExplorerBrowserHost()
{
}

STDMETHODIMP CExplorerBrowserHost::QueryInterface(REFIID riid, void **ppvObject)
{
	*ppvObject = NULL;

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IExplorerBrowserEvents))
		*ppvObject = static_cast<IExplorerBrowserEvents *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

STDMETHODIMP_(ULONG) CExplorerBrowserHost::AddRef()
{
	return InterlockedIncrement(&m_cRef);
}

STDMETHODIMP_(ULONG) CExplorerBrowserHost::Release()
{
	if (InterlockedDecrement(&m_cRef) == 0) {
		delete this;
		return 0;
	}

	return m_cRef;
}

STDMETHODIMP CExplorerBrowserHost::OnNavigationPending(PCIDLIST_ABSOLUTE pidlFolder)
{
	return S_OK;
}

STDMETHODIMP CExplorerBrowserHost::OnViewCreated(IShellView *psv)
{
	m_pShellView = psv;

	return S_OK;
}

STDMETHODIMP CExplorerBrowserHost::OnNavigationComplete(PCIDLIST_ABSOLUTE pidlFolder)
{
	return S_OK;
}

STDMETHODIMP CExplorerBrowserHost::OnNavigationFailed(PCIDLIST_ABSOLUTE pidlFolder)
{
	return S_OK;
}

int CExplorerBrowserHost::Run(HINSTANCE hinst, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = ::WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		if (m_pShellView == NULL || m_pShellView->TranslateAcceleratorW(&msg) != S_OK) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int)msg.wParam;
}

LRESULT CExplorerBrowserHost::WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	switch (uMsg) {

	case WM_CREATE: {
		TBBUTTON tbButton[] = {
			{HIST_BACK, ID_BACK, TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0},
			{HIST_FORWARD, ID_FORWARD, TBSTATE_ENABLED, BTNS_BUTTON, {0}, 0, 0}
		};
		int                  nCount = sizeof(tbButton) / sizeof(tbButton[0]);
		INITCOMMONCONTROLSEX ic;

		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_BAR_CLASSES;
		InitCommonControlsEx(&ic);
		
		m_hwndToolbar = CreateToolbarEx(hwnd, WS_CHILD | WS_VISIBLE, ID_TOOLBAR, 0, HINST_COMMCTRL, IDB_HIST_LARGE_COLOR, tbButton, nCount, 0, 0, 0, 0, sizeof(TBBUTTON));

		m_hwnd = hwnd;
		if (!Create())
			return -1;

		return 0;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_BACK)
			m_pExplorerBrowser->BrowseToIDList(NULL, SBSP_NAVIGATEBACK);
		else if (LOWORD(wParam) == ID_FORWARD)
			m_pExplorerBrowser->BrowseToIDList(NULL, SBSP_NAVIGATEFORWARD);
		else
			;
		return 0;


	case WM_SIZE: {
		int  nHeightToolbar;
		RECT rc;
		
		MoveWindow(m_hwndToolbar, 0, 0, LOWORD(lParam), HIWORD(wParam), TRUE);

		GetWindowRect(m_hwndToolbar, &rc);
		nHeightToolbar = rc.bottom - rc.top;
		SetRect(&rc, 0, nHeightToolbar, LOWORD(lParam), HIWORD(lParam));
		m_pExplorerBrowser->SetRect(NULL, rc);
		return 0;
	}

	case WM_DESTROY:
		if (m_pExplorerBrowser != NULL) {
			if (m_dwCookie != 0)
				m_pExplorerBrowser->Unadvise(m_dwCookie);
			m_pExplorerBrowser->Destroy();
			m_pExplorerBrowser->Release();
		}
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

BOOL CExplorerBrowserHost::Create()
{
	RECT             rc;
	HRESULT          hr;
	FOLDERSETTINGS   fs;
	PIDLIST_ABSOLUTE pidlDesktop;

	hr = CoCreateInstance(CLSID_ExplorerBrowser, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&m_pExplorerBrowser));
	if (FAILED(hr))
		return FALSE;
	
	fs.ViewMode = FVM_DETAILS;
	fs.fFlags   = 0;
	SetRectEmpty(&rc);
	hr = m_pExplorerBrowser->Initialize(m_hwnd, &rc, &fs);
	if (FAILED(hr))
		return FALSE;

	hr = m_pExplorerBrowser->Advise(static_cast<IExplorerBrowserEvents *>(this), &m_dwCookie);
	if (FAILED(hr))
		return FALSE;

	SHGetKnownFolderIDList(FOLDERID_Desktop, 0, NULL, &pidlDesktop);
	m_pExplorerBrowser->BrowseToIDList(pidlDesktop, SBSP_ABSOLUTE);
	ILFree(pidlDesktop);

	return TRUE;
}

今回のCreateでは、IExplorerBrowser::Adviseを呼び出してオブジェクトを登録しています。 このオブジェクトはIExplorerBrowserEventsを実装していなければならないため、 CExplorerBrowserHostはIExplorerBrowserEventsを継承するようになっています。 IExplorerBrowserEventsのメソッドは次に示す4つです。

STDMETHODIMP CExplorerBrowserHost::OnNavigationPending(PCIDLIST_ABSOLUTE pidlFolder)
{
	return S_OK;
}

STDMETHODIMP CExplorerBrowserHost::OnViewCreated(IShellView *psv)
{
	m_pShellView = psv;

	return S_OK;
}

STDMETHODIMP CExplorerBrowserHost::OnNavigationComplete(PCIDLIST_ABSOLUTE pidlFolder)
{
	return S_OK;
}

STDMETHODIMP CExplorerBrowserHost::OnNavigationFailed(PCIDLIST_ABSOLUTE pidlFolder)
{
	return S_OK;
}

フォルダを切り替えようとすると、最初にOnNavigationPendingが呼ばれることになります。 ここでS_OKを返すと、ExplorerBrowserに新しいフォルダの中身が表示されてOnViewCreatedが呼ばれ、 さらにその後にOnNavigationCompleteが呼ばれます。 一方、OnNavigationPendingでE_FAILなどを返すようにした場合は、 新しいフォルダは表示されずOnNavigationFailedが呼ばれることになります。 どのメソッドでも具体的な処理を行っていませんが、 たとえばOnNavigationPendingではパスワードの入力を促すと面白いでしょう。 つまり、入力されたパスワードが正しい場合にのみフォルダのアクセスを許可するわけです。 OnViewCreatedでは新しく作成されたIShellViewをメンバに保存していますが、 これはメッセージループで使用されることになっています。

while (GetMessage(&msg, NULL, 0, 0) > 0) {
	if (m_pShellView == NULL || m_pShellView->TranslateAcceleratorW(&msg) != S_OK) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
}

m_pShellViewがNULLでない場合は、IShellView::TranslateAcceleratorWを呼ぶようにしています。 これにより、IShellViewにメッセージが伝わることになり、 アクセラレータキーが押されていた場合はそれに準じた動作が行われます。 たとえば、Ctrl + Aが押されたならば、全てのアイテムが選択されることになります。

ExplorerBrowserは履歴情報を内部で維持しているため、 新しいフォルダに切り替わっても前のフォルダに戻ることができます。 今回はツールバー上に2つのボタンを表示しているわけですが、 そのボタンが押された場合に履歴情報を使用します。

case WM_COMMAND:
	if (LOWORD(wParam) == ID_BACK)
		m_pExplorerBrowser->BrowseToIDList(NULL, SBSP_NAVIGATEBACK);
	else if (LOWORD(wParam) == ID_FORWARD)
		m_pExplorerBrowser->BrowseToIDList(NULL, SBSP_NAVIGATEFORWARD);
	else
		;
	return 0;

ID_BACKは「戻る」を意味するボタンのIDであり、これが押された場合はBrowseToIDListにSBSP_NAVIGATEBACKを指定します。 これで前のフォルダを表示することができます。 ID_FORWARDは「進む」を意味するボタンのIDであり、これが押された場合はSBSP_NAVIGATEFORWARDを指定します。 どちらの定数についても第1引数はNULLでよく、 履歴情報が存在しない場合はS_OKが返るだけで何も起こりません。

ExplorerBrowserの問題点

ExplorerBrowserが使用していて少し気になった点をいくつか紹介します。 まず、今回呼び出したBrowseToIDListですが、 このメソッドには"戻ることが可能であるか"を調べるための定数はありません。 このため、必要に応じてツールバーのボタンを無効にすることができず、 ボタンは常に有効という状態になっています。 ただし、戻ることができない場合はBrowseToIDListでOnNavigationPendingが呼ばれないということもあり、 OnNavigationPendingが呼ばれたかどうかで判定を行うことも不可能ではないかもしれません。 これを試す場合は、BrowseToIDListが非同期(初回呼び出しは同期)に実行されるという点に注意してください。

2つ目の問題点は、IUnknown_SetSiteとIExplorerBrowser::Adviseの両方を一緒に呼び出せないと言う点です。 Adviseにオブジェクトのアドレスを指定した場合は、 IExplorerBrowserEventsによる通知が行われるわけですが、それ以外の通知は一切行われないようになっています。 たとえば、前節で取り上げたIExplorerPaneVisibilityよる通知は行われません。 この事を考えるとIUnknown_SetSiteを呼び出せばよいように思えますが、 そのようにすると今度はIExplorerBrowserEventsによる通知が行われないようになってしまいます。 つまり、IExplorerBrowserEventsによる通知を受け取る場合は、 それ以外の通知を断念せざる得ないことになります。

最後の問題点はExplorerBrowserというよりフォルダビューについてです。 フォルダビューではアイテム上で右クリックをした場合にコンテキストメニューが表示されるわけですが、 残念ながらこれを独自のメニューにすることはできないようです。 オブジェクトがICommDlgBrowser2を実装している場合は、ICommDlgBrowser2::Notifyでメニューが表示されるタイミングを特定できるのですが、 このメソッドでは戻り値を通じてメニューの表示をキャンセルできません。 つまり、メソッドが制御を返せば既定のメニューが表示されることになります。 ちなみに、表示されることになるIContextMenuは、 IShellView::GetItemObjectにSVGIO_SELECTIONを指定することで取得できます。 実はフォルダビューは、IContextMenuを渡すためのインターフェースとしてIContextMenuSiteを実装しているため、 これを利用することでIContextMenuを渡すこともできると思ったのですが、 残念ながら上手くいきませんでした。



戻る