EternalWindows
シェル名前空間 / ShellBrowserサンプル

シェル名前空間のフォルダを識別するIShellFolderは、フォルダとして適切なビュー(画面)を常に維持しています。 このビューはIShellViewとして識別され、IShellFolder::CreateViewObjectで取得することができます。

HRESULT IShellFolder::CreateViewObject(
  HWND hwndOwner,
  REFIID riid,
  void **ppv
);

hwndOwnerは、ビューの親ウインドウとするウインドウハンドルを指定します。 riidは、取得したいインターフェースのIIDを指定します。 通常は、IID_IShellViewを指定します。 ppvは、インターフェースを受け取る変数のアドレスを指定します。

IShellViewはフォルダビューを識別しているわけですが、 このビューを実際にウインドウとして作成するためには、 アプリケーションがIShellBrowserを実装したオブジェクトを用意しておかなければなりません。 そうすることで、アプリケーションはフォルダビュー内で発生した通知を受け取ることができるようになります。 ビューを表すウインドウは、IShellView::CreateViewWindowで作成することができます。

HRESULT IShellView::CreateViewWindow(
 IShellView *psvPrevious,
  LPCFOLDERSETTINGS pfs,
  IShellBrowser *psb,
  RECT *prcView,
  HWND *phWnd
);

psvPreviousは、以前に使用していたIShellViewを指定します。 NULLでも問題ありません。 pfsは、ビューの表示情報を格納したFOLDERSETTINGS構造体のアドレスを指定します。 psbは、IShellBrowserを実装したオブジェクトのアドレスを指定します。 prcViewは、ビューのクライアント領域を格納したRECT構造体のアドレスを指定します。 phWndは、作成されたビューのウインドウハンドルを受け取る変数のアドレスを指定します。 このウインドウをアプリケーションが作成したウインドウに表示すれば、 実際にフォルダビューを確認できるようになります。

今回のプログラムは、IShellBrowserを使用してフォルダビューを作成する例を示しています。

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

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

class CShellBrowser : public IShellBrowser
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP GetWindow(HWND *phwnd);
	STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode);
	STDMETHODIMP InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
	STDMETHODIMP SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject);
	STDMETHODIMP RemoveMenusSB(HMENU hmenuShared);
	STDMETHODIMP SetStatusTextSB(LPCWSTR lpszStatusText);
	STDMETHODIMP EnableModelessSB(BOOL fEnable);
	STDMETHODIMP TranslateAcceleratorSB(LPMSG lpmsg, WORD wID);
	STDMETHODIMP BrowseObject(PCUIDLIST_RELATIVE pidl, UINT wFlags);
	STDMETHODIMP GetViewStateStream(DWORD grfMode, IStream **ppStrm);
	STDMETHODIMP GetControlWindow(UINT id, HWND *lphwnd);
	STDMETHODIMP SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret);
	STDMETHODIMP QueryActiveShellView(IShellView **ppshv);
	STDMETHODIMP OnViewWindowActive(IShellView *ppshv);
	STDMETHODIMP SetToolbarItems(LPTBBUTTONSB lpButtons, UINT nButtons, UINT uFlags);
	
	CShellBrowser();
	~CShellBrowser();
	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_hwndView;
	IShellView *m_pShellView;
};

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

CShellBrowser *g_pShellBrowser = NULL;

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

	OleInitialize(NULL);
	
	g_pShellBrowser = new CShellBrowser;
	if (g_pShellBrowser != NULL) {
		nResult = g_pShellBrowser->Run(hinst, nCmdShow);
		g_pShellBrowser->Release();
	}
	
	OleUninitialize();

	return nResult;
}

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


// CShellBrowser


CShellBrowser::CShellBrowser()
{
	m_cRef = 1;
	m_hwnd = NULL;
	m_hwndView = NULL;
	m_pShellView = NULL;
}

CShellBrowser::~CShellBrowser()
{
}

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IOleWindow) || IsEqualIID(riid, IID_IShellBrowser))
		*ppvObject = static_cast<IShellBrowser *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

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

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

	return m_cRef;
}

STDMETHODIMP CShellBrowser::GetWindow(HWND *phwnd)
{
	*phwnd = m_hwnd;

	return S_OK;
}

STDMETHODIMP CShellBrowser::ContextSensitiveHelp(BOOL fEnterMode)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::RemoveMenusSB(HMENU hmenuShared)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::SetStatusTextSB(LPCWSTR lpszStatusText)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::EnableModelessSB(BOOL fEnable)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::TranslateAcceleratorSB(LPMSG lpmsg, WORD wID)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::BrowseObject(PCUIDLIST_RELATIVE pidl, UINT wFlags)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::GetViewStateStream(DWORD grfMode, IStream **ppStrm)
{
	return E_NOTIMPL;
}


STDMETHODIMP CShellBrowser::GetControlWindow(UINT id, HWND *lphwnd)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::QueryActiveShellView(IShellView **ppshv)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::OnViewWindowActive(IShellView *ppshv)
{
	return E_NOTIMPL;
}

STDMETHODIMP CShellBrowser::SetToolbarItems(LPTBBUTTONSB lpButtons, UINT nButtons, UINT uFlags)
{
	return E_NOTIMPL;
}

int CShellBrowser::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->TranslateAcceleratorW(&msg) != S_OK) {
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int)msg.wParam;
}

LRESULT CShellBrowser::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:
		MoveWindow(m_hwndView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

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

	default:
		break;

	}

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

BOOL CShellBrowser::Create()
{
	RECT           rc;
	FOLDERSETTINGS fs;
	IShellFolder   *pShellFolder;
	
	SHGetDesktopFolder(&pShellFolder);
	
	if (pShellFolder->CreateViewObject(m_hwnd, IID_PPV_ARGS(&m_pShellView)) != S_OK) {
		pShellFolder->Release();
		return FALSE;
	}

	fs.ViewMode = FVM_DETAILS;
	fs.fFlags   = 0;
	SetRectEmpty(&rc);
	if (m_pShellView->CreateViewWindow(NULL, &fs, static_cast<IShellBrowser *>(this), &rc, &m_hwndView) != S_OK) {
		m_pShellView->Release();
		pShellFolder->Release();
		return FALSE;
	}

	m_pShellView->UIActivate(SVUIA_ACTIVATE_NOFOCUS);
	
	pShellFolder->Release();

	return TRUE;
}

既に述べたように、IShellView::CreateViewWindowの第3引数には、 IShellBrowserを実装したオブジェクトのアドレスを指定します。 オブジェクトとはC++クラスにおけるインスタンスであり、 次のようにIShellBrowserを継承したクラスが型になります。

class CShellBrowser : public IShellBrowser
{
public:
	STDMETHODIMP QueryInterface(REFIID riid, void **ppvObject);
	STDMETHODIMP_(ULONG) AddRef();
	STDMETHODIMP_(ULONG) Release();
	
	STDMETHODIMP GetWindow(HWND *phwnd);
	STDMETHODIMP ContextSensitiveHelp(BOOL fEnterMode);
	STDMETHODIMP InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
	STDMETHODIMP SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject);
	STDMETHODIMP RemoveMenusSB(HMENU hmenuShared);
	STDMETHODIMP SetStatusTextSB(LPCWSTR lpszStatusText);
	STDMETHODIMP EnableModelessSB(BOOL fEnable);
	STDMETHODIMP TranslateAcceleratorSB(LPMSG lpmsg, WORD wID);
	STDMETHODIMP BrowseObject(PCUIDLIST_RELATIVE pidl, UINT wFlags);
	STDMETHODIMP GetViewStateStream(DWORD grfMode, IStream **ppStrm);
	STDMETHODIMP GetControlWindow(UINT id, HWND *lphwnd);
	STDMETHODIMP SendControlMsg(UINT id, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT *pret);
	STDMETHODIMP QueryActiveShellView(IShellView **ppshv);
	STDMETHODIMP OnViewWindowActive(IShellView *ppshv);
	STDMETHODIMP SetToolbarItems(LPTBBUTTONSB lpButtons, UINT nButtons, UINT uFlags);
	
	CShellBrowser();
	~CShellBrowser();
	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_hwndView;
	IShellView *m_pShellView;
};

見て分かるように、IShellBrowserを継承したCShellBrowserというクラスを定義しています。 各メソッドについては、QueryInterfaceからReleaseまでがIUnknownのメソッドであり、 GetWindowとContextSensitiveHelpがIOleWindowのメソッドになります。 IUnknownは全てのインターフェースが継承していますし、 IShellBrowserはIOleWindowを継承しているので、これらのメソッドはオーバーライドする必要があります。 BrowseObjectからTranslateAcceleratorSBはIShellBrowserのメソッドであり、 RunからCreateはアプリケーションが呼び出すメソッドです。

インターフェースを継承するといっても、全てのメソッドを正しく実装しなければならないわけではありません。 ただし、インターフェースを使用するフォルダビューとしては、これだけは実装してもらわなければ困るというメソッドがありますから、 そうしたメソッドについて今回は焦点を当てることにします。 まずは、IUnknownのメソッドからです。

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

	if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IOleWindow) || IsEqualIID(riid, IID_IShellBrowser))
		*ppvObject = static_cast<IShellBrowser *>(this);
	else
		return E_NOINTERFACE;

	AddRef();
	
	return S_OK;
}

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

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

	return m_cRef;
}

QueryInterfaceでは、指定されたインターフェースがオブジェクトの実装するインターフェースと一致するかを調べ、 一致する場合はオブジェクトのアドレスを返すことになります。 先に定義したクラスは、IUnknown、IOleWindow、IShellBrowser、ICommDlgBrowserを実装していますから、 これらのインターフェースが指定された場合はオブジェクトの操作を許可するために、オブジェクトのアドレスをppvObjectに格納します。 AddRefはオブジェクトの参照カウントを1つ上げ、Releaseはオブジェクトの参照カウントを1つ下げるのが目的ですが、 今回のような場合は特に実装する意味がありません。 オブジェクトを作成するのはアプリケーション自身ですから、 オブジェクトを破棄するタイミングを決定するのもアプリケーション自身になるからです。

IOleWindowのメソッドの実装は次のようになります。

STDMETHODIMP CShellBrowser::GetWindow(HWND *phwnd)
{
	*phwnd = m_hwndParent;

	return S_OK;
}

STDMETHODIMP CShellBrowser::ContextSensitiveHelp(BOOL fEnterMode)
{
	return E_NOTIMPL;
}

あるオブジェクトがIOleWindowという事実は、そのオブジェクトがウインドウハンドルを持っていることを意味しています。 よって、GetWindowではそのウインドウハンドルを返すことになります。 ここで返すウインドウハンドルは、フォルダビューのウインドウハンドルではなく、 フォルダビューを貼り付けている親ウインドウのハンドルであることに注意してください。 ContextSensitiveHelpは特に呼ばれることもないため、実装していないことを示すE_NOTIMPLを返すだけで構いません。 また、IShellBrowserのメソッドについても今回はE_NOTIMPLを返すだけで構いません。 少なくとも、QueryInterfaceとGetWindowを正しく実装していれば、フォルダビューを表示することは可能です。

WinMainとWindowProcの処理は次のようになっています。

CShellBrowser *g_pShellBrowser = NULL;

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

	OleInitialize(NULL);
	
	g_pShellBrowser = new CShellBrowser;
	if (g_pShellBrowser != NULL) {
		nResult = g_pShellBrowser->Run(hinst, nCmdShow);
		g_pShellBrowser->Release();
	}
	
	OleUninitialize();

	return nResult;
}

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

CoInitializeではなくOleInitializeを呼び出しているのは、 フォルダビューにおけるD&Dを機能させるためです。 newによってCShellBrowserを型としたオブジェクトが作成され、 これはグローバル変数であるg_pShellBrowserで識別することになります。 グローバルで宣言している理由は、WindowProcの呼び出し時にクラスのメソッドであるWindowProcを呼び出すためです。 Runというメソッドには、ウインドウの作成やメッセージループといった基本コードが含まれており、 実質いつものWinMainに記述しているコードと変化ありません。 よって、いつものようにWinMainへ基本コードを記述してもよいのですが、 できるだけコードはクラスのメソッドに集約したいという考えから、上記のような実装にしています。 WindowProcでクラスのメソッドであるWindowProcを呼び出しているのも、そうした理由からです。

CShellBrowser::Runの実装は、先に述べたように基本コードで構成されていますが、 一点だけ補足すべき個所があります。

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

IShellView::TranslateAcceleratorWを呼び出す理由は、フォルダビュー上におけるアクセラレータキーの入力を検出するためです。 これがないと、たとえばCtrl + Aの組み合わせが押された場合に、全てのアイテムが選択されないことになります。 戻り値がS_OKでない場合は、アクセラレータキーが入力されていないことを意味するため、 通常通りの処理を行うようにします。

CShellBrowser::WindowProcの実装は、次のようになります。

LRESULT CShellBrowser::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:
		MoveWindow(m_hwndView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

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

	default:
		break;

	}

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

Createは内部でフォルダビューを作成するメソッドですが、 これを呼び出すためにはm_hwndを初期化しておかなければなりません。 理由は、Createが呼び出すIShellView::CreateViewWindowが、 内部でIOleWindow::GetWindowを呼び出すことになっているからです。 フォルダビューが親ウインドウ上で正しく表示されるために、 WM_SIZEではフォルダビューのウインドウに対してMoveWindowを呼び出しています。

Createの内部は次のようになります。

BOOL CShellBrowser::Create()
{
	RECT           rc;
	FOLDERSETTINGS fs;
	IShellFolder   *pShellFolder;
	
	SHGetDesktopFolder(&pShellFolder);

	if (pShellFolder->CreateViewObject(m_hwnd, IID_PPV_ARGS(&m_pShellView)) != S_OK) {
		pShellFolder->Release();
		return FALSE;
	}

	fs.ViewMode = FVM_DETAILS;
	fs.fFlags   = 0;
	SetRectEmpty(&rc);
	if (m_pShellView->CreateViewWindow(NULL, &fs, static_cast<IShellBrowser *>(this), &rc, &m_hwndView) != S_OK) {
		m_pShellView->Release();
		pShellFolder->Release();
		return FALSE;
	}

	m_pShellView->UIActivate(SVUIA_ACTIVATE_NOFOCUS);
	
	pShellFolder->Release();

	return TRUE;
}

フォルダビューの表示対象とするフォルダはデスクトップにするということで、 SHGetDesktopFolderでIShellFolderを取得し、CreateViewObjectでIShellViewを取得します。 続いて、IShellView::CreateViewWindowを呼び出すために、FOLDERSETTINGS構造体を初期化します。 ViewModeはフォルダビューの表示形式であり、FVM_DETAILSを指定すると詳細表示の形式になります。 FVM_ICONを指定した場合はアイコン表示になり、FVM_THUMBNAILを指定した場合はサムネイル表示になるでしょう。 fFlagsについては0で問題ありませんが、何らかの定数を指定することもできます。 たとえば、FWF_CHECKSELECTを指定すればアイテムの横にチェックボックスが表示され、 FWF_FULLROWSELECTを指定すれば一行選択が可能になります。 CreateViewWindowはビューのクライアント領域を格納したRECT構造体も要求しますが、 これはSetRectEmptyで0初期化しておくだけで構いません。 理由は、WindowProcのWM_SIZEでサイズ調整が行われるからです。 CreateViewWindowの第3引数はIShellBrowserを実装するオブジェクトのアドレスであり、 CShellBrowserはこれを実装していますから、問題なくthisポインタを指定することができます。 UIActivateを呼び出せば、フォルダビューは実際に表示されることになります。 SVUIA_ACTIVATE_NOFOCUSではなく、SVUIA_ACTIVATE_FOCUSを指定した場合はIShellBrowser::OnViewWindowActiveが呼ばれることになります。


戻る