EternalWindows
シェル名前空間 / FolderViewHost

これまで、フォルダビューを管理するオブジェクトとしてShellBrowserを取り上げてきましたが、 Windows XPからはFolderViewHostというオブジェクトも使用することができます。 FolderViewHostはCoCreateInstanceで作成可能になっているため、 アプリケーションがIShellBrowserを実装したクラスを定義し、 その型のオブジェクトを作成する必要はなくなります。 FolderViewHostはIFolderViewHostで表され、Initializeで初期化することができます

HRESULT IFolderViewHost::Initialize(
  HWND hwndParent,
  IDataObject *pdo,
  RECT *prc
);

hwndParentは、フォルダビューを表示する親ウインドウのハンドルを指定します。 pdoは、フォルダビューに表示するアイテムを可能したオブジェクトのアドレスを指定します。 このオブジェクトはIDataObjectを実装している必要があります。 prcは、フォルダビューを表示する位置を格納したRECT構造体のアドレスを指定します。

IFolderViewHostに含まれているメソッドはInitializeだけであるため、 このインターフェースだけでは何らかの作業を行うことはできません。 ただし、FolderViewHostはフォルダビューを管理しているということもあり、 IShellBrowserを実装するようになっています。 つまり、QueryInterfaceを呼び出すことによってIShellBrowserを取得できます。 IShellBrowserを取得できればQueryActiveShellViewを呼び出すことによって、 フォルダビューを表すIShellViewを取得できます。

これまで説明してきませんでしたが、フォルダビューを表すインターフェースはIShellViewの他にIFolderViewが存在します。 このインターフェースはWindows XPから使用可能ですが、IShellViewより多機能ということもあり、 IFolderViewを使用しなければならい状況もいくつか存在します。 IFolderViewを使用する例として、前々節で実装したICommDlgBrowser::OnStateChangeを修正します。

STDMETHODIMP CShellBrowser::OnStateChange(IShellView *ppshv, ULONG uChange)
{
	if (uChange == CDBOSC_SELCHANGE) {
		int           nItemIndex;
		STRRET        strret;
		TCHAR         szDisplayName[256];
		IFolderView   *pFolderView;
		IShellFolder  *pShellFolder;
		PITEMID_CHILD pidlChild;

		if (ppshv->QueryInterface(IID_PPV_ARGS(&pFolderView)) == S_OK) {
			pFolderView->GetFocusedItem(&nItemIndex);
			pFolderView->Item(nItemIndex, &pidlChild);
			if (pFolderView->GetFolder(IID_PPV_ARGS(&pShellFolder)) == S_OK) {
				pShellFolder->GetDisplayNameOf(pidlChild, SHGDN_NORMAL, &strret);
				StrRetToBuf(&strret, pidlChild, szDisplayName, sizeof(szDisplayName) / sizeof(TCHAR));
				SetWindowText(m_hwndParent, szDisplayName);
				pShellFolder->Release();
			}
			CoTaskMemFree(pidlChild);
			pFolderView->Release();
		}
	}

	return S_OK;
}

uChangeがCDBOSC_SELCHANGEである場合はアイテムが選択されたことを意味し、 前々節ではIShellView::GetItemObjectで選択されたアイテムを取得していました。 しかし、上記ではIFolderViewを使用して選択されたアイテムを取得しています。 まず、IShellViewのQueryInterfaceからIFolderViewを取得し、 次にGetFocusedItemで選択されたアイテムのインデックスを取得します。 続いてItemを呼び出してインデックスからPIDLを取得し、 GetFolderを呼び出してIShellFolderを取得します。 IShellFolderを取得すれば、先に取得したPIDLをGetDisplayNameOfに指定することでアイテムの名前を取得することができます。 IShellViewにはGetFolderに相当するメソッドが存在しないため、 IShellFolderを取得する場合はIFolderViewを使用するようにします。

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

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

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

#define ID_CHECK 100

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId);
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, 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 CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static IFolderViewHost *pFolderViewHost = NULL;
	static IShellView      *pShellView = NULL;
	
	switch (uMsg) {

	case WM_CREATE: {
		HMENU            hmenu;
		HRESULT          hr;
		IDataObject      *pDataObject;
		IShellBrowser    *pShellBrowser;
		PIDLIST_ABSOLUTE pidlDesktop;

		OleInitialize(NULL);

		hr = CoCreateInstance(CLSID_FolderViewHost, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFolderViewHost));
		if (FAILED(hr))
			return -1;

		SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
		SHCreateDataObject(pidlDesktop, 0, 0, NULL, IID_PPV_ARGS(&pDataObject));
		pFolderViewHost->Initialize(hwnd, pDataObject, NULL);
		pDataObject->Release();
		CoTaskMemFree(pidlDesktop);

		pFolderViewHost->QueryInterface(IID_PPV_ARGS(&pShellBrowser));
		pShellBrowser->QueryActiveShellView(&pShellView);
		pShellBrowser->Release();

		hmenu = CreateMenu();
		InitializeMenuItem(hmenu, TEXT("チェック確認(&C)"), ID_CHECK);
		SetMenu(hwnd, hmenu);

		return 0;
	}

	case WM_COMMAND:
		if (LOWORD(wParam) == ID_CHECK) {
			int         i, nCount;
			TCHAR       szBuf[256];
			HRESULT     hr;
			FORMATETC   formatetc;
			STGMEDIUM   medium;
			IDataObject *pDataObject;
			IFolderView *pFolderView;

			pShellView->QueryInterface(IID_PPV_ARGS(&pFolderView));
			hr = pFolderView->Items(SVGIO_CHECKED, IID_PPV_ARGS(&pDataObject));
			pFolderView->Release();
			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;
			}

			nCount = DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, NULL, 0);
			for (i = 0; i < nCount; i++) {
				DragQueryFile((HDROP)medium.hGlobal, i, szBuf, sizeof(szBuf) / sizeof(TCHAR));
				MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
			}
			
			ReleaseStgMedium(&medium);
			pDataObject->Release();
		}
		else
			;
		
		return 0;

	case WM_SIZE: {
		HWND hwndView;
		IUnknown_GetWindow(pShellView, &hwndView);
		MoveWindow(hwndView, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;
	}

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

	default:
		break;

	}

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

void InitializeMenuItem(HMENU hmenu, LPTSTR lpszItemName, int nId)
{
	MENUITEMINFO mii;
	
	mii.cbSize     = sizeof(MENUITEMINFO);
	mii.fMask      = MIIM_ID | MIIM_TYPE;
	mii.fType      = MFT_STRING;
	mii.wID        = nId;
	mii.dwTypeData = lpszItemName;
	
	InsertMenuItem(hmenu, nId, FALSE, &mii);
}

プログラムの実行結果ですが、おそらく予想していたものとは異なるのではないかと思われます。 たとえば、フォルダビューはサムネイル表示になっていますし、アイテムの横にチェックボックスが付いていたりします。 また、アイテムの1つずつがグループ化されており、どういうわけかフォルダのアイテムは表示されないようになっています。 つまり、FolderViewHostではエクスプローラーで表示されているような見た目は得られないということになります。 FolderViewHostの作成はWM_CREATEで行われています。

case WM_CREATE: {
	HMENU            hmenu;
	HRESULT          hr;
	IDataObject      *pDataObject;
	IShellBrowser    *pShellBrowser;
	PIDLIST_ABSOLUTE pidlDesktop;

	OleInitialize(NULL);

	hr = CoCreateInstance(CLSID_FolderViewHost, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pFolderViewHost));
	if (FAILED(hr))
		return -1;

	SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidlDesktop);
	SHCreateDataObject(pidlDesktop, 0, 0, NULL, IID_PPV_ARGS(&pDataObject));
	pFolderViewHost->Initialize(hwnd, pDataObject, NULL);
	pDataObject->Release();
	CoTaskMemFree(pidlDesktop);

	pFolderViewHost->QueryInterface(IID_PPV_ARGS(&pShellBrowser));
	pShellBrowser->QueryActiveShellView(&pShellView);
	pShellBrowser->Release();

	hmenu = CreateMenu();
	InitializeMenuItem(hmenu, TEXT("チェック確認(&C)"), ID_CHECK);
	SetMenu(hwnd, hmenu);

	return 0;
}

FolderViewHostを作成するには、CoCreateInstanceにCLSID_FolderViewHostを指定します。 IFolderViewHostを取得したらInitializeを呼び出すことになりますが、 これにはIDataObjectを実装したオブジェクトが必要になります。 よって、SHCreateDataObjectを呼び出すことによってそれを作成することになります。 第1引数に指定しているPIDLはデスクトップのものであるため、 フォルダビューにはデスクトップのアイテムが表示されることになります。 SHCreateDataObjectはWindows Vistaから使用できる関数であるため、 それ以前の 環境ではIShellFolder::GetUIObjectOfを呼び出すとよいでしょう。 ただし、この方法の場合はアイテムに既定でチェックがつくようです。 Initializeの呼び出しが終わればQueryInterfaceでIShellBrowserを取得し、 QueryActiveShellViewでIShellViewを取得します。 このIShellViewは他のメッセージでも使用されることになります。

FolderViewHostのアイテムにはチェックボックスが表示されているということもあり、 チェックされたアイテムを確認するためのメニュー項目を用意しています。 これが選択されるとWM_COMMANDが送られ、そこで確認処理が行うようにしています。 IDataObjectを取得する部分に注目します。

// pShellView->GetItemObject(SVGIO_CHECKED, IID_PPV_ARGS(&pDataObject)) は失敗する

pShellView->QueryInterface(IID_PPV_ARGS(&pFolderView));
hr = pFolderView->Items(SVGIO_CHECKED, IID_PPV_ARGS(&pDataObject));

既に述べてきたように、選択されたアイテムを取得したい場合はIShellView::GetItemObjectにSVGIO_SELECTIONを指定することができます。 この要領で考えると、チェックされたアイテムはSVGIO_CHECKEDを指定することで取得できそうですが、 残念ながらこれは上手くいきません。 よって、QueryInterfaceからIFolderViewを取得し、 ItemsにSVGIO_CHECKEDを指定するようにしています。 この方法ならば問題なくIDataObjectを取得することができます。

最後にフォルダビューの表示情報を変更する方法について説明します。 IShellViewにはGetCurrentInfoというFOLDERSETTINGS構造体を取得できるメソッドがあるのですが、 SetCurrentInfoというメソッドはありません。 よって、IFolderViewとIFolderView2で表示情報を設定することになります。

IFolderView  *pFolderView;
IFolderView2 *pFolderView2;
DWORD        dwFlags;

pShellView->QueryInterface(IID_PPV_ARGS(&pFolderView));
pFolderView->SetCurrentViewMode(FVM_DETAILS);
pFolderView->Release();

pShellView->QueryInterface(IID_PPV_ARGS(&pFolderView2));
pFolderView2->GetCurrentFolderFlags(&dwFlags);
pFolderView2->SetCurrentFolderFlags(dwFlags, 0);
pFolderView2->Release();

FOLDERSETTINGS構造体のViewModeに相当する情報は、IFolderView::SetCurrentViewModeで設定することができます。 一方、FOLDERSETTINGS構造体のfFlagsに相当する情報は、IFolderView2->SetCurrentFolderFlagsで設定することができます。 このメソッドは第1引数に変更の対象となる定数を指定し、第2引数に変更後の値を指定します。 上記では第2引数が0になっているため、dwFlagsの定数は全て取り除かれることになります。 dwFlagsはGetCurrentFolderFlagsによって取得されているため、 現在のフォルダのフラグは全て無効にされると考えてもよいでしょう。 ちなみに、GetCurrentFolderFlagsで取得できる既定の定数は、 FWF_AUTOARRANGE | FWF_NOWEBVIEW | FWF_HIDEFILENAMES | FWF_CHECKSELECT | FWF_FULLROWSELECTになっています。


戻る