EternalWindows
シェル名前空間 / ShellBrowserサンプル(UI調整)

前節では、アプリケーションにフォルダビューを表示しましたが、 これだけではまだフォルダビューの機能を十分に扱えているとは言えません。 親ウインドウがメニューを作成していれば適切な項目をフォルダビューが追加してくれますし、 ツールバーを作成していれば適切なボタンが追加されます。 また、ステータスバーを作成していれば各種情報が送信されることになります。 よって、今回はこうしたコントロールを表示する例を示します。

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

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

class CShellBrowser : public IShellBrowser, public ICommDlgBrowser
{
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);
	
	STDMETHODIMP OnDefaultCommand(IShellView *ppshv);
	STDMETHODIMP OnStateChange(IShellView *ppshv, ULONG uChange);
	STDMETHODIMP IncludeObject(IShellView *ppshv, LPCITEMIDLIST pidl);

	CShellBrowser();
	~CShellBrowser();
	int Run(HINSTANCE hinst, int nCmdShow);
	LRESULT WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	BOOL Create();
	void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub);

private:
	LONG       m_cRef;
	HWND       m_hwnd;
	HWND       m_hwndView;
	HWND       m_hwndStatus;
	HWND       m_hwndToolbar;
	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;
	m_hwndStatus = NULL;
	m_hwndToolbar = 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 if (IsEqualIID(riid, IID_ICommDlgBrowser))
		*ppvObject = static_cast<ICommDlgBrowser *>(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)
{
	HMENU hmenuPopupFile;
	HMENU hmenuPopupEdit;
	HMENU hmenuPopupView;
	HMENU hmenuPopupTool;
	HMENU hmenuPopupHelp;
	
	hmenuPopupFile = CreatePopupMenu();
	hmenuPopupEdit = CreatePopupMenu();
	hmenuPopupView = CreatePopupMenu();
	hmenuPopupTool = CreatePopupMenu();
	hmenuPopupHelp = CreatePopupMenu();
	
	InitializeMenuItem(hmenuShared, TEXT("ファイル(&F)"), FCIDM_MENU_FILE, hmenuPopupFile);
	InitializeMenuItem(hmenuShared, TEXT("編集(&E)"), FCIDM_MENU_EDIT, hmenuPopupEdit);
	InitializeMenuItem(hmenuShared, TEXT("表示(&V)"), FCIDM_MENU_VIEW, hmenuPopupView);
	InitializeMenuItem(hmenuShared, TEXT("ツール(&T)"), FCIDM_MENU_TOOLS, hmenuPopupTool);
	InitializeMenuItem(hmenuShared, TEXT("ヘルプ(&H)"), FCIDM_MENU_HELP, hmenuPopupHelp);

	lpMenuWidths->width[0] = 0;
	lpMenuWidths->width[1] = 0;
	lpMenuWidths->width[2] = 0;
	lpMenuWidths->width[3] = 0;
	lpMenuWidths->width[4] = 0;
	lpMenuWidths->width[5] = 0;

	return S_OK;
}

STDMETHODIMP CShellBrowser::SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject)
{
	if (hwndActiveObject == NULL)
		return E_FAIL;

	SetMenu(m_hwnd, hmenuShared);
	
	return S_OK;
}

STDMETHODIMP CShellBrowser::RemoveMenusSB(HMENU hmenuShared)
{
	int i, nCount;

	nCount = GetMenuItemCount(hmenuShared);

	for (i = 0; i < nCount; i++)
		RemoveMenu(hmenuShared, i, MF_BYPOSITION);

	return S_OK;
}

STDMETHODIMP CShellBrowser::SetStatusTextSB(LPCWSTR lpszStatusText)
{
	SendMessageW(m_hwndStatus, SB_SETTEXT, 0, (LPARAM)lpszStatusText);

	return S_OK;
}

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)
{
	if (lphwnd == NULL)
		return E_POINTER;

	if (id == FCW_TOOLBAR)
		*lphwnd = m_hwndToolbar;
	else if (id == FCW_STATUS)
		*lphwnd = m_hwndStatus;
	else
		return E_FAIL;

	return S_OK;
}

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

	if (id == FCW_TOOLBAR) {
		lr = SendMessage(m_hwndToolbar, uMsg, wParam, lParam);
		if (pret != NULL)
			*pret = lr;
	}
	else if (id == FCW_STATUS) {
		lr = SendMessage(m_hwndStatus, uMsg, wParam, lParam);
		if (pret != NULL)
			*pret = lr;
	}
	else
		return E_FAIL;

	return S_OK;
}

STDMETHODIMP CShellBrowser::QueryActiveShellView(IShellView **ppshv)
{
	*ppshv = m_pShellView;
	
	m_pShellView->AddRef();

	return S_OK;
}

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

STDMETHODIMP CShellBrowser::SetToolbarItems(LPTBBUTTONSB lpButtons, UINT nButtons, UINT uFlags)
{
	SendMessage(m_hwndToolbar, TB_ADDBUTTONS, nButtons, (LPARAM)lpButtons);

	return S_OK;
}

STDMETHODIMP CShellBrowser::OnDefaultCommand(IShellView *ppshv)
{
	return S_FALSE;
}

STDMETHODIMP CShellBrowser::OnStateChange(IShellView *ppshv, ULONG uChange)
{
	if (uChange == CDBOSC_SELCHANGE) {
		TCHAR       szBuf[256];
		HRESULT     hr;
		FORMATETC   formatetc;
		STGMEDIUM   medium;
		IDataObject *pDataObject;
		
		hr = ppshv->GetItemObject(SVGIO_SELECTION, IID_PPV_ARGS(&pDataObject));
		if (FAILED(hr))
			return 0;

		formatetc.cfFormat = CF_HDROP;
		formatetc.ptd      = NULL;
		formatetc.dwAspect = DVASPECT_CONTENT;
		formatetc.lindex   = -1;
		formatetc.tymed    = TYMED_HGLOBAL;

		hr = pDataObject->GetData(&formatetc, &medium);
		if (FAILED(hr)) {
			pDataObject->Release();
			return 0;
		}
		
		DragQueryFile((HDROP)medium.hGlobal, 0, szBuf, sizeof(szBuf) / sizeof(TCHAR));
		SetWindowText(m_hwnd, szBuf);
		
		ReleaseStgMedium(&medium);
		pDataObject->Release();
	}

	return S_OK;
}

STDMETHODIMP CShellBrowser::IncludeObject(IShellView *ppshv, LPCITEMIDLIST pidl)
{
	return S_OK;
}

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: {
		INITCOMMONCONTROLSEX ic;

		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_BAR_CLASSES;
		InitCommonControlsEx(&ic);
		
		m_hwndStatus = CreateWindowEx(0, STATUSCLASSNAME, TEXT(""), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)FCIDM_STATUS, ((LPCREATESTRUCT)lParam)->hInstance, NULL);	
		m_hwndToolbar = CreateToolbarEx(hwnd, WS_CHILD | WS_VISIBLE, FCIDM_TOOLBAR, 0, (HINSTANCE)0, 0, NULL, 0, 0, 0, 0, 0, sizeof(TBBUTTON));

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

		return 0;
	}

	case WM_COMMAND:
		SendMessage(m_hwndView, uMsg, wParam, lParam);
		return 0;

	case WM_SIZE: {
		int  nHeightToolbar, nHeightStatus;
		RECT rc;
		
		MoveWindow(m_hwndToolbar, 0, 0, LOWORD(lParam), HIWORD(wParam), TRUE);
		SendMessage(m_hwndStatus, WM_SIZE, wParam, lParam);
		
		GetWindowRect(m_hwndToolbar, &rc);
		nHeightToolbar = rc.bottom - rc.top;
		GetWindowRect(m_hwndStatus, &rc);
		nHeightStatus = rc.bottom - rc.top;		
		MoveWindow(m_hwndView, 0, nHeightToolbar, LOWORD(lParam), HIWORD(lParam) - nHeightStatus - nHeightToolbar, 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;
}

void CShellBrowser::InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId, HMENU hmenuSub)
{
	MENUITEMINFO mii;
	
	mii.cbSize = sizeof(MENUITEMINFO);
	mii.fMask  = MIIM_ID | MIIM_TYPE;
	mii.wID    = nId;

	if (lpszItemName != NULL) {
		mii.fType      = MFT_STRING;
		mii.dwTypeData = lpszItemName;
	}
	else
		mii.fType = MFT_SEPARATOR;

	if (hmenuSub != NULL) {
		mii.fMask   |= MIIM_SUBMENU;
		mii.hSubMenu = hmenuSub;
	}

	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

前節で定義したCShellBrowserはIShellBrowserを継承していましたが、 多くのメソッドは意味のある実装を持っていませんでした。 今回は各種メソッドの用途を順に見ていき、実装を行える場合は実装するようにします。

STDMETHODIMP CShellBrowser::InsertMenusSB(HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
	HMENU hmenuPopupFile;
	HMENU hmenuPopupEdit;
	HMENU hmenuPopupView;
	HMENU hmenuPopupTool;
	HMENU hmenuPopupHelp;
	
	hmenuPopupFile = CreatePopupMenu();
	hmenuPopupEdit = CreatePopupMenu();
	hmenuPopupView = CreatePopupMenu();
	hmenuPopupTool = CreatePopupMenu();
	hmenuPopupHelp = CreatePopupMenu();
	
	InitializeMenuItem(hmenuShared, TEXT("ファイル(&F)"), FCIDM_MENU_FILE, hmenuPopupFile);
	InitializeMenuItem(hmenuShared, TEXT("編集(&E)"), FCIDM_MENU_EDIT, hmenuPopupEdit);
	InitializeMenuItem(hmenuShared, TEXT("表示(&V)"), FCIDM_MENU_VIEW, hmenuPopupView);
	InitializeMenuItem(hmenuShared, TEXT("ツール(&T)"), FCIDM_MENU_TOOLS, hmenuPopupTool);
	InitializeMenuItem(hmenuShared, TEXT("ヘルプ(&H)"), FCIDM_MENU_HELP, hmenuPopupHelp);

	lpMenuWidths->width[0] = 0;
	lpMenuWidths->width[1] = 0;
	lpMenuWidths->width[2] = 0;
	lpMenuWidths->width[3] = 0;
	lpMenuWidths->width[4] = 0;
	lpMenuWidths->width[5] = 0;

	return S_OK;
}

InsertMenusSBは、IShellView::CreateViewWindowの内部で呼ばれます。 このメソッドでは第1引数のトップメニューに独自の項目を追加することになるため、 InitializeMenuItemという自作関数でいくつかの項目を追加しています。 項目のIDはFCIDM_MENU_XXXでなければならず、 たとえばFCIDM_MENU_FILEで識別される項目は「ファイル」という名前に設定するのが一般的です。 項目は選択された場合にポップアップメニューを表示することが多いため、 第4引数にはポップアップメニューのハンドルを指定するようにしています。 lpMenuWidths->widthには、ポップアップメニューに追加した項目の数を格納します。 たとえば、「ファイル」という1番目のポップアップメニューに2つの項目を追加したならば、 lpMenuWidths->width[0]に2という値を指定します。 このとき、追加する項目のIDはFCIDM_SHVIEWFIRSTからFCIDM_SHVIEWLASTの間でなければなりません。 今回は全ての要素に0を指定していることから、ポップアップメニューには何も項目が追加されないように思えますが、 実際にはフォルダビューによって一部の項目が追加されることになっています。 たとえば、フォルダビューはFCIDM_MENU_VIEWというIDを持つポップアップメニューに、 「元に戻す」という項目を追加することになっています。 なお、リファレンス上ではlpMenuWidths->widthの0、2、4の要素をコンテナ(ShellBrowser)が使用し、 1、3、5の要素をオブジェクトサーバー(フォルダビュー)が使用すると記述されていますが、 これについては特に意識する必要はないと思われます。

STDMETHODIMP CShellBrowser::SetMenuSB(HMENU hmenuShared, HOLEMENU holemenuRes, HWND hwndActiveObject)
{
	if (hwndActiveObject == NULL)
		return E_FAIL;

	SetMenu(m_hwndParent, hmenuShared);
	
	return S_OK;
}

SetMenuSBは、第1引数のメニューを親ウインドウに設定すべき際に、フォルダビューによって呼ばれます。 第3引数はビューのウインドウハンドルであるため、これに対してメニューを設定するようなことはありませんが、 ビューが既に作成されているかどうかは確認しておくべきと言えます。 これは、ビューが作成されていないのにも関わらず親ウインドウにメニューを設定しようとすると、ちらつきが生じるためです。

STDMETHODIMP CShellBrowser::RemoveMenusSB(HMENU hmenuShared)
{
	int i, nCount;

	nCount = GetMenuItemCount(hmenuShared);

	for (i = 0; i < nCount; i++)
		RemoveMenu(hmenuShared, i, MF_BYPOSITION);

	return S_OK;
}

RemoveMenusSBは、メニューの項目を削除するべき際にフォルダビューによって呼ばれます。 上記のように、項目の数だけRemoveMenuを呼び出せば問題ありません。

STDMETHODIMP CShellBrowser::SetStatusTextSB(LPCWSTR lpszStatusText)
{
	SendMessageW(m_hwndStatus, SB_SETTEXT, 0, (LPARAM)lpszStatusText);

	return S_OK;
}

SetStatusTextSBは、ステータスバーにテキストを設定する際に呼ばれます。 ただし、デフォルトのフォルダビューはこのメソッドを通じて文字列の設定を行わないようです。 単純にSB_SETTEXTをステータスバーに送信すれば問題ありません。

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

EnableModelessSBは、モードレスダイアログの表示を有効にするかどうかを決定する場合に呼ばれます。 今回はモードレスダイアログ自体を表示することがないため、E_NOTIMPLを返します。

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

TranslateAcceleratorSBは、アプリケーションが定義しているアクセラレータキーが入力された際に、フォルダビューによって呼ばれます。 今回はアクセラレータテーブルを持っていないため、E_NOTIMPLを返すようにしています。

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

BrowseObjectは、表示するフォルダを第1引数のPIDLで識別されるものに切り替える場合に呼ばれます。 一見するこのメソッドは、フォルダビューのフォルダをクリックした際に呼ばれそうですが、 デフォルトではそのような動作になっていません。 よって、呼ばれることがないのでE_NOTIMPLを返しています。 このメソッドが呼ばれるための方法は次節で取り上げます。

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

GetViewStateStreamは、IShellView::CreateViewWindowの内部で呼ばれます。 このメソッドの役割はよく分からないのですが、 おそらくはビューの情報(アイコン位置など)を読み書きするためのIStreamを作成して、それを返すのではないかと思われます。 特に実装しなくても問題ないようなので、E_NOTIMPLを返すようにしています。

STDMETHODIMP CShellBrowser::GetControlWindow(UINT id, HWND *lphwnd)
{
	if (lphwnd == NULL)
		return E_POINTER;

	if (id == FCW_TOOLBAR)
		*lphwnd = m_hwndToolbar;
	else if (id == FCW_STATUS)
		*lphwnd = m_hwndStatus;
	else
		return E_FAIL;

	return S_OK;
}

GetControlWindowは、IShellView::CreateViewWindowの内部で呼ばれます。 フォルダビューは、ShellBrowserのコントロールを取得するためにこのメソッドを呼び出すため、 idがFCW_TOOLBARの際にはツールバーのハンドルを返し、 idがFCW_STATUSの際にはステータスバーのハンドルを返します。 要求されたコントロールをShellBrowserが作成していない場合は、E_FAILを返します。

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

	if (id == FCW_TOOLBAR) {
		lr = SendMessage(m_hwndToolbar, uMsg, wParam, lParam);
		if (pret != NULL)
			*pret = lr;
	}
	else if (id == FCW_STATUS) {
		lr = SendMessage(m_hwndStatus, uMsg, wParam, lParam);
		if (pret != NULL)
			*pret = lr;
	}
	else
		return E_FAIL;

	return S_OK;
}

SendControlMsgは、指定したidで識別されるコントロールにメッセージを送る場合に呼ばれます。 IShellView::CreateViewWindowは、このメソッドを通じてツールバーにTB_ADDBITMAPSを送信しています。 pretがNULLでない場合は、メッセージの戻り値を格納するようにします。

STDMETHODIMP CShellBrowser::QueryActiveShellView(IShellView **ppshv)
{
	*ppshv = m_pShellView;
	
	m_pShellView->AddRef();

	return S_OK;
}

QueryActiveShellViewは、現在アクティブであるフォルダビューを取得する際に呼ばれます。 通常、このメソッドを呼び出すのは外部アプリケーションと思われるため、 フォルダビューが呼び出すことはないと思われます。 *ppshvにフォルダビューを格納したことにより、 フォルダビューを参照できるオブジェクトが1つ増えたことになりますから、 フォルダビューの参照カウントをAddRefで増加させます。

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

OnViewWindowActiveは、フォルダビューがアクティブになったときにフォルダビューによって呼ばれます。 アクティブになる場合というのは、アイテムを選択した場合などが挙げられます。 今回は特に行うことがないのでE_NOTIMPLを返しています。

STDMETHODIMP CShellBrowser::SetToolbarItems(LPTBBUTTONSB lpButtons, UINT nButtons, UINT uFlags)
{
	SendMessage(m_hwndToolbar, TB_ADDBUTTONS, nButtons, (LPARAM)lpButtons);

	return S_OK;
}

SetToolbarItemsは、ツールバーにボタンを追加する際に呼ばれます。 IShellView::CreateViewWindowは、このメソッドを呼び出すことになっているため、 TB_ADDBUTTONSをツールバーに送信することになります。

WindowProcの実装は次のようになっています。

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

	case WM_CREATE: {
		INITCOMMONCONTROLSEX ic;

		ic.dwSize = sizeof(INITCOMMONCONTROLSEX);
		ic.dwICC  = ICC_BAR_CLASSES;
		InitCommonControlsEx(&ic);
		
		m_hwndStatus = CreateWindowEx(0, STATUSCLASSNAME, TEXT(""), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)FCIDM_STATUS, ((LPCREATESTRUCT)lParam)->hInstance, NULL);	
		m_hwndToolbar = CreateToolbarEx(hwnd, WS_CHILD | WS_VISIBLE, FCIDM_TOOLBAR, 0, (HINSTANCE)0, 0, NULL, 0, 0, 0, 0, 0, sizeof(TBBUTTON));

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

		return 0;
	}

	case WM_COMMAND:
		SendMessage(m_hwndView, uMsg, wParam, lParam);
		return 0;

	case WM_SIZE: {
		int  nHeightToolbar, nHeightStatus;
		RECT rc;
		
		MoveWindow(m_hwndToolbar, 0, 0, LOWORD(lParam), HIWORD(wParam), TRUE);
		SendMessage(m_hwndStatus, WM_SIZE, wParam, lParam);
		
		GetWindowRect(m_hwndToolbar, &rc);
		nHeightToolbar = rc.bottom - rc.top;
		GetWindowRect(m_hwndStatus, &rc);
		nHeightStatus = rc.bottom - rc.top;		
		MoveWindow(m_hwndView, 0, nHeightToolbar, LOWORD(lParam), HIWORD(lParam) - nHeightStatus - nHeightToolbar, 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);
}

ステータスバーを作成するには、CreateWindowExの第2引数にSTATUSCLASSNAMEを指定します。 第10引数はコントロールのIDですが、上記ではFCIDM_STATUSという定数を指定しています。 このような既知の定義を使用することで、フォルダビューとしてもコントロールを識別しやすくなります。 ツールバーを作成するには、CreateToolbarExを呼び出します。 第3引数はコントロールのIDであり、既知の定数であるFCIDM_TOOLBARを指定するようにしています。 CreateToolbarExの第6引数は、ボタンのビットマップリソースを識別するIDですが、 こうした情報についてはアプリケーションが値を指定することはできません。 どのようなIDが適切なのかを知っているのはフォルダビュー自身ですから、 フォルダビューに任せることになります。 Createが内部で呼び出すIShellView::CreateViewWindowは、 内部でコントロールのハンドルを必要とするため、 Createの呼び出しの前にコントロールの作成は済ませておくようにします。 WM_SIZEでは、フォルダビューがツールバーとステータスバーを覆うことのないように、 フォルダビューのサイズを調整しています。

ShellBrowserは、オプションとしてICommDlgBrowserを実装することができます。 このインターフェースを実装することで、いくつかの通知を受け取れるようになります。

STDMETHODIMP CShellBrowser::OnDefaultCommand(IShellView *ppshv)
{
	return S_FALSE;
}

OnDefaultCommandは、フォルダビューのアイテムに対してダブルクリックを実行した場合や、 Enterキーを押下した場合に呼ばれます。 S_FALSEを返した場合はデフォルトの動作が行われますが、S_OKを返した場合はデフォルトの動作が行われません。

STDMETHODIMP CShellBrowser::OnStateChange(IShellView *ppshv, ULONG uChange)
{
	if (uChange == CDBOSC_SELCHANGE) {
		TCHAR       szBuf[256];
		HRESULT     hr;
		FORMATETC   formatetc;
		STGMEDIUM   medium;
		IDataObject *pDataObject;
		
		hr = ppshv->GetItemObject(SVGIO_SELECTION, IID_PPV_ARGS(&pDataObject));
		if (FAILED(hr))
			return 0;

		formatetc.cfFormat = CF_HDROP;
		formatetc.ptd      = NULL;
		formatetc.dwAspect = DVASPECT_CONTENT;
		formatetc.lindex   = -1;
		formatetc.tymed    = TYMED_HGLOBAL;

		hr = pDataObject->GetData(&formatetc, &medium);
		if (FAILED(hr)) {
			pDataObject->Release();
			return 0;
		}
		
		DragQueryFile((HDROP)medium.hGlobal, 0, szBuf, sizeof(szBuf) / sizeof(TCHAR));
		SetWindowText(m_hwnd, szBuf);
		
		ReleaseStgMedium(&medium);
		pDataObject->Release();
	}

	return S_OK;
}

OnStateChangeは、フォルダビューの状態が変更された場合にフォルダビューによって呼ばれます。 uChangeにCDBOSC_SELCHANGEが格納されている場合はアイテムが選択されたことを意味するため、 そのアイテムを特定するためにIShellView::GetItemObjectを呼び出しています。 第1引数にSVGIO_SELECTIONを指定すればIDataObjectを取得できるため、 これのGetDataを呼び出すことでアイテムのデータを取得することができます。 第1引数はデータをどのようなフォーマットで取得するかであり、 FORMATETC.cfFormatにCF_HDROPを指定した場合は、DragQueryFileでアイテムのファイルパスを取得できるようになります。 DragQueryFileの第1引数に指定すべきハンドルは、STGMEDIUM.hGlobalに格納されます。

STDMETHODIMP CShellBrowser::IncludeObject(IShellView *ppshv, LPCITEMIDLIST pidl)
{
	return S_OK;
}

IncludeObjectは、アイテムを列挙する段階でフォルダビューによって呼ばれます。 S_OKを返すと、pidlで識別されるアイテムの列挙を許可することになりますが、 S_FALSEを返すと列挙を許可しないことになります。 上記では常にS_OKを返しているため、全てのアイテムの列挙を許可することになります。 リファレンス上ではIncludeObjectは、IEnumIDListの使用時に呼ばれるとされていますが、 これをShellBrowserが行った場合は呼ばれることがないようです。 次に、IncludeObjectの実装例を示します。

STDMETHODIMP CShellBrowser::IncludeObject(IShellView *ppshv, LPCITEMIDLIST pidl)
{
	TCHAR szPath[256];

	if (SHGetPathFromIDList(pidl, szPath)) {
		if (StrCmp(PathFindExtension(szPath), TEXT(".txt")) == 0)
			return S_OK;
	}
	
	return S_FALSE;
}

IncludeObjectの第2引数は完全PIDLであるため、SHGetPathFromIDListでフルパスを取得することができます。 これが成功した場合はPathFindExtensionで拡張子を取得し、 それが.txtと一致するかをStrCmpで調べます。 成功した場合はS_OKを返し、失敗した場合はS_FALSEを返すようにしているため、 テキストファイルだけがフォルダビューに表示されることになります。


戻る