EternalWindows
ExplorerBrowser / ExplorerBrowserの作成

エクスプローラーのフォルダに表示されているフォルダビューは、 多くのGUIアプリケーションにとって有用になるものです。 フォルダビューは、シェル名前空間上のアイテムを表示するリストビューのようなウインドウであり、 コンテキストメニューの表示やD&Dの機能を既定で実装しています。 つまり、アプリケーションがフォルダビューを表示するようになれば、 エクスプローラーのフォルダが行っている動作を簡単に真似することができます。 フォルダビューを表示するには、Windows Vistaから追加されたExplorerBrowserというオブジェクトを使用するのが最も簡単です。

Windows Vista以前のWindowsにおいても、フォルダビューを表示するためのオブジェクトは存在していました。 そうしたオブジェクトを使用せず、ExplorerBrowserを使用することにはどのような利点があるのでしょうか。 まずは、各種オブジェクトの特徴を簡単に整理します。

オブジェクト 特徴
ShellBrowser フォルダビューを表示することができるが、このオブジェクトはCoCreateInstanceで作成することができない。 つまり、アプリケーションがIShellBrowserを実装したオブジェクトを自作することになる。
FolderViewHost フォルダビューを表示することができるが、アイテムにチェックボックスが付いていたり、 ファイルのアイテムしか表示されなかったりする。 Windows XPから使用可能。
ExplorerBrowser フォルダビューを表示できることに加え、履歴機能やペインの表示もサポートする。 Windows Vistaから使用可能。

FolderViewHostはフォルダビューの表示に問題があるため、 実用性があるのはShellBrowserかExplorerBrowserかのどちらかになります。 ShellBrowserを使用する場合は、オブジェクトのためのクラスを定義したり、 履歴機能を独自に実装したりするなど行うべきことが多くありますが、 ExplorerBrowserの場合はこうした作業をアプリケーションが行う必要はありません。 単純にオブジェクトを作成するだけで、必要な機能を利用することができます。 よって、基本的にはExplorerBrowserを使用するようにし、 Windows Vistaより前の環境でShellBrowserを使用するのがよいと思われます。

ExplorerBrowserを作成するには、CoCreateInstanceにCLSID_ExplorerBrowserを指定します。 これにより、ExplorerBrowserを識別するIExplorerBrowserを取得することができるため、 Initializeを呼び出してExplorerBrowserを初期化することになります。

HRESULT IExplorerBrowser::Initialize(
  HWND hwndParent,
  const RECT *prc,
  const FOLDERSETTINGS *pfs
);

hwndParentは、ExplorerBrowserを表示する親ウインドウのハンドルを指定します。 prcは、ExplorerBrowserの位置を格納したRECT構造体のアドレスを指定します。 pfsは、フォルダの表示情報を格納したFOLDERSETTINGS構造体のアドレスを指定します。

ExplorerBrowserの初期化が完了すれば、特定のフォルダの中身を表示できるようになります。 これには、BrowseToIDListを呼び出します。

HRESULT IExplorerBrowser::BrowseToIDList(
  PCUIDLIST_RELATIVE pidl,
  UINT uFlags
);

pidlは、表示したいフォルダのPIDLを指定します。 uFlagsは、PIDLの種類を表す定数を指定します。 PIDLが完全PIDLである場合は、SBSP_ABSOLUTEを指定します。

ExplorerBrowserは、エクスプローラーのフォルダで使用されているペインを完全に提供しています。 アプリケーションは、IExplorerBrowser::SetOptionsにEBO_SHOWFRAMESを指定するだけで、 次のようなペインを表示することができます。

各種ペインは既定の実装を持っており、それはExplorerBrowserと正しく関連しています。 たとえば、コマンドバーから新しい表示形式を選択した場合は、 その表示形式がExplorerBrowserへ実際に反映されます。 ペインが表示される段階になるとIExplorerPaneVisibility::GetPaneStateが呼ばれるため、 これを検出することで任意のペインのみを表示することもできます。

ExplorerBrowserからの通知をIExplorerPaneVisibilityとして受け取るには、 それを継承したクラスを定義し、オブジェクトのアドレスをExplorerBrowserに渡すようにします。 これにより、ExplorerBrowserはオブジェクトのGetPaneStateを呼び出せるようになります。 ただし、IExplorerBrowserにはオブジェクトを渡すためのメソッドが存在しないため、 COMの標準的なインターフェースであるIObjectWithSiteを使用します。 具体的には、IExplorerBrowserからIObjectWithSiteを取得し、 IObjectWithSite::SetSiteでオブジェクトのアドレスを渡すことになります。 ExplorerBrowserは、IServiceProviderを経由してIExplorerPaneVisibilityを参照するため、 オブジェクトはIServiceProviderも実装しておく必要があります。

今回のプログラムは、ExplorerBrowserを使用してフォルダビューを表示します。

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

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

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

	STDMETHODIMP QueryService(REFGUID guidService, REFIID riid, void **ppv);

	STDMETHODIMP GetPaneState(REFEXPLORERPANE ep, EXPLORERPANESTATE *peps);

	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;
	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_pExplorerBrowser = NULL;
}

CExplorerBrowserHost::~CExplorerBrowserHost()
{
}

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IServiceProvider))
		*ppvObject = static_cast<IServiceProvider *>(this);
	else if (IsEqualIID(riid, IID_IExplorerPaneVisibility))
		*ppvObject = static_cast<IExplorerPaneVisibility *>(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::QueryService(REFGUID guidService, REFIID riid, void **ppv)
{
	return QueryInterface(riid, ppv); 
}

STDMETHODIMP CExplorerBrowserHost::GetPaneState(REFEXPLORERPANE ep, EXPLORERPANESTATE *peps)
{
	if (ep == EP_NavPane)
		*peps = EPS_DEFAULT_ON;
	else
		*peps = EPS_DEFAULT_OFF | EPS_FORCE;

	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) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

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

	case WM_CREATE:
		m_hwnd = hwnd;
		if (!Create())
			return -1;
		return 0;

	case WM_SIZE: {
		RECT rc = {0, 0, LOWORD(lParam), HIWORD(lParam)};
		m_pExplorerBrowser->SetRect(NULL, rc);
		return 0;
	}

	case WM_DESTROY:
		if (m_pExplorerBrowser != NULL) {
			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;

	m_pExplorerBrowser->SetOptions(EBO_SHOWFRAMES);
	IUnknown_SetSite(m_pExplorerBrowser, static_cast<IServiceProvider *>(this));

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

	return TRUE;
}

既に述べたように、ExplorerBrowserからの通知を受け取るためには、通知用のインターフェースを継承したクラスが必要になります。 今回必要とする通知はIExplorerPaneVisibilityであるため、 これを継承したCExplorerBrowserHostというクラスを定義しています。 このクラスはIExplorerBrowserをメンバとして持っているため、 ExplorerBrowserを管理するということでクラス名にHostという単語を付けています。

WinMainではCExplorerBrowserHost::Runを呼び出し、ウインドウの作成などが完了した後にメッセージループへ入ります。 メッセージが送られるとWindowProcが呼ばれ、そこからさらにクラスのWindowProcが呼ばれることになっています。 WM_CREATEではCreateという自作メソッドを呼び出し、IExplorerBrowserを取得しています。

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;

	m_pExplorerBrowser->SetOptions(EBO_SHOWFRAMES);
	IUnknown_SetSite(m_pExplorerBrowser, static_cast<IServiceProvider *>(this));

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

	return TRUE;
}

IExplorerBrowserを取得するには、CoCreateInstanceにCLSID_ExplorerBrowserを指定します。 これに成功したら、Initializeを呼び出してExplorerBrowserを初期化することになります。 FOLDERSETTINGS.ViewModeにはExplorerBrowserの表示形式を格納でき、 FVM_DETAILSの場合は詳細表示されることになります。 fFlagsは0で構いませんが、たとえばFWF_CHECKSELECTを指定するとチェックボックスが表示されるようになります。 SetOptionsにEBO_SHOWFRAMESを指定することにより、ExplorerBrowserにペインが表示されるようになりますが、 今回は表示されるペインを制限されることになります。 理由は、IExplorerPaneVisibility(及びIServiceProvider)を実装したオブジェクトのアドレスをExplorerBrowserに渡しているからです。 これを行うには、IObjectWithSiteを取得してSetSiteを呼ばなければならないと説明しましたが、 IUnknown_SetSiteを呼び出せばこの2つの作業を一度に行うことができます。 初期化されたExplorerBrowserは既定で何も表示されていないため、 BrowseToIDListで特定のフォルダの中身を表示することになります。 今回はデスクトップにするということで、SHGetKnownFolderIDListでデスクトップのPIDLを取得しています。 これはSHGetSpecialFolderLocationでも可能ですが、対象とする環境がVistaであるため、Vistaから登場したSHGetKnownFolderIDListを使用しています。

WindowProcではWM_CREATEの他に、WM_SIZEとWM_DESTROYを処理しています。

case WM_SIZE: {
	RECT rc = {0, 0, LOWORD(lParam), HIWORD(lParam)};
	m_pExplorerBrowser->SetRect(NULL, rc);
	return 0;
}

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

WM_SIZEでは、ExplorerBrowserのサイズを親ウインドウのクライアント領域に合わせるために、 SetRectを呼び出しています。 これにより、ExplorerBrowserのサイズは第2引数のRECT構造体を反映します。 WM_DESTROYはIExplorerBrowser::Releaseを呼び出すことになりますが、 その前にDestroyを必ず呼び出すようにします。

ExplorerBrowserはペインを表示する段階になると、オブジェクトのIServiceProvider::QueryServiceからIExplorerPaneVisibilityを取得しようとします。 よって、QueryServiceではIID_IExplorerPaneVisibilityの際にオブジェクトのアドレスを返すようになっていなければなりません。

STDMETHODIMP CExplorerBrowserHost::QueryService(REFGUID guidService, REFIID riid, void **ppv)
{
	return QueryInterface(riid, ppv); 
}

上記ではQueryServiceを呼び出しているだけですが、QueryServiceでIID_IExplorerPaneVisibilityを考慮するようになっていれば問題はありません。 ちなみに、riidがIID_IExplorerPaneVisibilityの際には、 guidServiceにSID_ExplorerPaneVisibilityが格納されます。

IExplorerPaneVisibilityを取得したExplorerBrowserは、GetPaneStateを呼び出すことで表示すべきペインを確認します。

STDMETHODIMP CExplorerBrowserHost::GetPaneState(REFEXPLORERPANE ep, EXPLORERPANESTATE *peps)
{
	if (ep == EP_NavPane)
		*peps = EPS_DEFAULT_ON;
	else
		*peps = EPS_DEFAULT_OFF | EPS_FORCE;

	return S_OK;
}

第1引数のepには、どのようなペインが表示されようとしているかを表す定数が格納されます。 今回はEP_NavPaneで表されるペインを表示するということで、このときのみpepsにEPS_DEFAULT_ONを格納しています。 一方、それ以外のペインは表示しないということで、EPS_DEFAULT_OFFとEPS_FORCEを格納しています。 戻り値はS_OKで問題ありません。

アイテムのフィルタについて

ExplorerBrowserに表示されるアイテムをフィルタする方法は主に2つあります。 1つはICommDlgBrowser::IncludeObjectを処理する方法で、 特定のアイテムの際にS_FALSEを返すようにします。 これにより、そのアイテムはExplorerBrowserに表示されなくなります。 ICommDlgBrowserの通知を受け取るには、 これを実装したオブジェクトをIUnknown_SetSiteで指定するだけで構いません。 IncludeObjectを処理する例はシェル名前空間の章で取り上げています。

アイテムをフィルタするもう1つの方法は、オブジェクトがIFolderFilterを実装し、 これをIFolderFilterSiteでExplorerBrowserに渡す方法です。 IFolderFilterSiteは、ExplorerBrowserに対してQueryInterfaceを呼び出せば取得できます。

IFolderFilterSite *pFolderFilterSite;

m_pExplorerBrowser->QueryInterface(IID_PPV_ARGS(&pFolderFilterSite));
pFolderFilterSite->SetFilter(static_cast<IFolderFilter *>(this));

IFolderFilterSiteを取得したら、SetFilterを呼び出してオブジェクトのアドレスを渡すようにします。 これにより、アイテムが追加される段階になるとIFolderFilter::ShouldShowが呼ばれることになります。 当然ながら、オブジェクトのQueryInterfaceではIID_IFolderFilterを処理しておく必要があります。 次に、ShouldShowの例を示します。

STDMETHODIMP CExplorerBrowserHost::ShouldShow(IShellFolder *psf, PCIDLIST_ABSOLUTE pidlFolder, PCUITEMID_CHILD pidlItem)
{
	TCHAR  szDisplayName[256];
	STRRET strret;
	
	psf->GetDisplayNameOf(pidlItem, SHGDN_NORMAL, &strret);
	StrRetToBuf(&strret, pidlItem, szDisplayName, sizeof(szDisplayName) / sizeof(TCHAR));
	
	if (StrCmp(PathFindExtension(szDisplayName), TEXT(".txt")) == 0)
		return S_FALSE;

	return S_OK;
}

psfは表示対象となっているフォルダのIShellFolderであり、pidlItemは表示されようとしているアイテムのPIDLです。 上記ではテキストファイルのアイテムを追加しないということで、 GetDisplayNameOfとStrRetToBufでアイテムの名前を取得し、 この名前の拡張子をPathFindExtensionで取得しています。 そして、取得した拡張子と.txtが一致するかをStrCmpで調べ、 一致する場合はS_FALSEを返してアイテムが表示されないようにしています。 なお、IFolderFilterにはGetEnumFlagsというメソッドがありますが、こちらの呼び出しについては確認することができませんでした。



戻る