EternalWindows
ExplorerBrowser / 結果フォルダの使用

ExplorerBrowserに表示されるアイテムは、基本的に特定のフォルダの中身を反映したものになりますが、 場合によってはこれを変更したいことがあります。 たとえば、AとBというフォルダがあったとして、 その両方のフォルダのアイテムを同時にExplorerBrowserへ表示できたら便利であるといえるでしょう。 これを行うにはExplorerBrowserに結果フォルダというものを作成し、 そこにアイテムを明示的に追加することになります。 結果フォルダを作成するには、FillFromObjectを呼び出します。

HRESULT IExplorerBrowser::FillFromObject(
  IUnknown *punk,
  EXPLORER_BROWSER_FILL_FLAGS dwFlags
);

punkは、結果フォルダの初期値を与えるためのインターフェースを指定します。 空の結果フォルダを作成する場合は、NULLで構いません。 dwFlagsは、結果フォルダに関する定数を指定します。 EBF_NODROPTARGETを指定した場合は、外部のファイルを結果フォルダにドロップできなくなります。

結果フォルダにアイテムを追加するには、IExplorerBrowser::GetCurrentViewを呼び出してIFolderViewを取得し、 さらにQueryInterfaceでIResultsFolderを取得します。 そして、このインターフェースのAddIDListを呼び出します。

HRESULT IResultsFolder::AddIDList(
  PCIDLIST_ABSOLUTE pidl,
  PITEMID_CHILD *ppidlAdded
);

pidlは、追加したいアイテムのPIDLを指定します。 ppidlAddedは、追加されたアイテムのPIDLを受け取る変数のアドレスを指定します。 不要な場合はNULLで構いません。

結果フォルダのカラムは決まった形をとることになっていますが、 これは自由に変更することができます。 具体的には、IFolderViewからIColumnManagerを取得してSetColumnsを呼び出します。

HRESULT IColumnManager::SetColumns(
  const PROPERTYKEY *rgkeyOrder,
  UINT cVisible
);

propkeyは、カラムを識別するPROPERTYKEYの配列を指定します。 pcmciは、rgkeyOrderの要素数を指定します。

IResultsFolder::AddIDListに指定するPIDLをどこから取得するかは全くの自由です。 たとえば、2つのフォルダからIEnumIDListを取得し、 IEnumIDList::Nextで取得したそれぞれのPIDLを指定してもよいでしょう。 今回は、階層をたどってアイテムを列挙するINamespaceWalkの使い方を取り上げます。

HRESULT INamespaceWalk::Walk(
  IUnknown *punkToWalk,
  DWORD dwFlags,
  int cDepth,
  INamespaceWalkCB *pnswcb
);

punkToWalkは、列挙されるアイテムを格納したインターフェースを指定します。 通常は、IShellFolderを指定すると思われます。 dwFlagsは、列挙に関する定数を指定します。 cDepthは、列挙する階層をゼロベースで指定します。 pnswcbは、列挙の経過を受け取るオブジェクトのアドレスを指定します。 このオブジェクトはINamespaceWalkCBを実装している必要があります。

今回のプログラムは、デスクトップから2階層の位置に存在するテキストファイルを表示します。 列挙にはある程度の時間が掛かります。

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

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

BOOL Create(HWND hwnd, IExplorerBrowser **ppExplorerBrowser);
BOOL AddItems(IExplorerBrowser *pExplorerBrowser);
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 IExplorerBrowser *pExplorerBrowser = NULL;

	switch (uMsg) {

	case WM_CREATE:
		OleInitialize(NULL);
		if (!Create(hwnd, &pExplorerBrowser))
			return -1;
		AddItems(pExplorerBrowser);
		return 0;

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

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

	default:
		break;

	}

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

BOOL Create(HWND hwnd, IExplorerBrowser **ppExplorerBrowser)
{
	RECT             rc;
	HRESULT          hr;
	FOLDERSETTINGS   fs;
	IExplorerBrowser *pExplorerBrowser;

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

	*ppExplorerBrowser = pExplorerBrowser;

	return TRUE;
}

BOOL AddItems(IExplorerBrowser *pExplorerBrowser)
{
	UINT             i, uItemCount;
	TCHAR            szPath[256];
	HRESULT          hr;
	IShellFolder     *pDesktopFolder;
	INamespaceWalk   *pNamespaceWalk;
	IFolderView      *pFolderView;
	IColumnManager   *pColumnManager;
	IResultsFolder   *pResultsFolder;
	PIDLIST_ABSOLUTE *ppidl;
	PROPERTYKEY      propKey[] = {PKEY_ItemNameDisplay, PKEY_Size, PKEY_ItemTypeText, PKEY_DateModified};
	
	hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pNamespaceWalk));
	if (FAILED(hr))
		return FALSE;
	
	SHGetDesktopFolder(&pDesktopFolder);
	pNamespaceWalk->Walk(pDesktopFolder, 0, 1, NULL);
	pNamespaceWalk->GetIDArrayResult(&uItemCount, &ppidl);
	pNamespaceWalk->Release();
	pDesktopFolder->Release();

	pExplorerBrowser->FillFromObject(NULL, EBF_NODROPTARGET);
	pExplorerBrowser->GetCurrentView(IID_PPV_ARGS(&pFolderView));

	pFolderView->QueryInterface(IID_PPV_ARGS(&pColumnManager));
	pColumnManager->SetColumns(propKey, 4);
	pColumnManager->Release();
	
	pFolderView->GetFolder(IID_PPV_ARGS(&pResultsFolder));
	for (i = 0; i < uItemCount; i++) {
		if(SHGetPathFromIDList(ppidl[i], szPath)) {
			if (StrCmp(PathFindExtension(szPath), TEXT(".txt")) == 0) {
				pResultsFolder->AddIDList(ppidl[i], NULL);
			}
		}
		CoTaskMemFree(ppidl[i]);
	}

	CoTaskMemFree(ppidl);
	pResultsFolder->Release();
	pFolderView->Release();

	return TRUE;
}

今回は前節までのようにCExplorerBrowserHostというクラスを定義していませんが、 これはExplorerBrowserからの通知を受け取るつもりがないからです。 ExplorerBrowserからの通知を受け取らないということは、 何らかのインターフェースを実装したオブジェクトをExplorerBrowserに渡さないことを意味するため、 オブジェクトのためのクラスも不要になるわけです。 もちろん、クラスの利用した開発を好む場合はクラスを使用しても問題ありません。

WM_CREATEで呼び出しているCreateは前節までの内容と似ていますが、 BrowseToIDListで特定のフォルダの中身を表示していません。 理由は、任意のアイテムをExplorerBrowserに表示するのが今回の目的だからです。 アイテムの追加はAddItemsで行われます。

BOOL AddItems(IExplorerBrowser *pExplorerBrowser)
{
	UINT             i, uItemCount;
	TCHAR            szPath[256];
	HRESULT          hr;
	IShellFolder     *pDesktopFolder;
	INamespaceWalk   *pNamespaceWalk;
	IFolderView      *pFolderView;
	IColumnManager   *pColumnManager;
	IResultsFolder   *pResultsFolder;
	PIDLIST_ABSOLUTE *ppidl;
	PROPERTYKEY      propKey[] = {PKEY_ItemNameDisplay, PKEY_Size, PKEY_ItemTypeText, PKEY_DateModified};
	
	hr = CoCreateInstance(CLSID_NamespaceWalker, NULL, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&pNamespaceWalk));
	if (FAILED(hr))
		return FALSE;
	
	SHGetDesktopFolder(&pDesktopFolder);
	pNamespaceWalk->Walk(pDesktopFolder, 0, 1, NULL);
	pNamespaceWalk->GetIDArrayResult(&uItemCount, &ppidl);
	pNamespaceWalk->Release();
	pDesktopFolder->Release();

	pExplorerBrowser->FillFromObject(NULL, EBF_NODROPTARGET);
	pExplorerBrowser->GetCurrentView(IID_PPV_ARGS(&pFolderView));

	pFolderView->QueryInterface(IID_PPV_ARGS(&pColumnManager));
	pColumnManager->SetColumns(propKey, 4);
	pColumnManager->Release();
	
	pFolderView->GetFolder(IID_PPV_ARGS(&pResultsFolder));
	for (i = 0; i < uItemCount; i++) {
		if(SHGetPathFromIDList(ppidl[i], szPath)) {
			if (StrCmp(PathFindExtension(szPath), TEXT(".txt")) == 0) {
				pResultsFolder->AddIDList(ppidl[i], NULL);
			}
		}
		CoTaskMemFree(ppidl[i]);
	}

	CoTaskMemFree(ppidl);
	pResultsFolder->Release();
	pFolderView->Release();

	return TRUE;
}

INamespaceWalkを取得するには、CoCreateInstanceにCLSID_NamespaceWalkerを指定します。 これが成功したらWalkを呼び出すことで、第1引数のインターフェースのアイテムを列挙することができます。 今回はデスクトップを表すIShellFolderを取得していることから、列挙の対象がデスクトップ以下になり、 さらに第3引数が1であることから2階層まで列挙されることになります。 つまり、デスクトップ直下のアイテムと、デスクトップ直下のフォルダのさらに直下のアイテムが列挙されることになります。 列挙によって取得したPIDLとその数は、GetIDArrayResultから取得することができます。 続いて、ExplorerBrowserに空の結果フォルダを作成するためにFillFromObjectを呼び出し、 さらにGetCurrentViewからIFolderViewを取得します。 このIFolderViewがあれば、IColumnManagerやIResultsFolderが取得できます。 SetColumnsによってpropKeyの値に応じたカラムが設定されることになりますが、 PKEY_Sizeに関しては何故かファイルのサイズが表示されないようです。 GetFolderでIResultsFolderを取得したら、アイテムを結果フォルダに追加するループに入ります。 今回は追加するアイテムをテキストファイルだけにしたいため、 まずSHGetPathFromIDListでPIDLからアイテムのフルパスを取得し、 PathFindExtensionが返す値がテキストファイルの拡張子と一致するかを調べます。 これが一致する場合は、そのアイテムのPIDLをAddIDListに指定します。 なお、ExplorerBrowserにICommDlgBrowserを実装したオブジェクトを渡していても、 AddIDListでICommDlgBrowser::IncludeObjectが呼ばれることはありません。

INamespaceWalk::Walkは、第3引数に指定した階層の値が大きいほど処理に時間が掛かってしまいます。 この待ち時間をできるだけ感じさせないためには、どのようにすればよいのでしょうか。 たとえば、Walkの第2引数にNSWF_SHOW_PROGRESSを指定すればダイアログが表示されるため、 ユーザーは列挙の経過を確認しながら待つことができます。 ただし、このダイアログは親ウインドウを設定できないためか、 列挙が終了した場合にトップレベルウインドウが最小化されてしまいます。 待ち時間そのものをなくしたい場合は、NSWF_ASYNCを指定することができます。 これを指定するとWalkが直ちに制御を返すことになり、 アプリケーションはINamespaceWalkCB2::WalkCompleteで列挙終了の通知を受け取ることになります。 ただし、WalkCompleteでGetIDArrayResultを呼び出しても、S_FALSEが返ってPIDLを取得することができないように思えます。 こうした事を踏まえると、単純にスレッドを作成してそこでWalkを呼び出すのが最も分かりやすいのかもしれません。


戻る