フォルダを選択するダイアログ(前編)

フォルダ選択ダイアログ(これは「ファイル検索」の)

 「フォルダを選択するダイアログ」。似たようなものをよく見かけます。ファイル検索での参照、関連づけされていないファイルをダブルクリックしたとき、IE3.0以上で「お気に入りの追加」を選んだとき、某有名解凍アプリケーションなど、様々なところで似たようなダイアログボックスが現れます。
 ところが、コモンダイアログの欄を見ても、このようなダイアログはありません。それもそのはず、このダイアログは::SHBrowseForFolder()というAPIひとつで呼び出せるからです。
 しかし、ここで大きな壁にぶつかります。それは「インターフェイス」というものです。インターフェイスと聞いてもピンと来ない人も多いのではないでしょうか。でも、OLE2やCOM、ActiveXを知っている方は多いでしょう。インターフェイスは、これらの根幹となっている機能です。
 ところが、今回のように「インターフェイスを使うことで色々な便利なことができる」というのに、インターフェイスそのものについて解説されたドキュメントは少ないのです。ほとんどが、OLE2やActiveXと関連づけられて説明されています。

 っつーても、今はもはやFAQで、これを簡単に使うためのクラスや関数のライブラリが結構出回ってたりします。でも、この関数は逆にCOM関係を知るのには比較的適した例と言えます。
 というわけで、この関数を軸に、「シェルエクステンションの使い方」についてじっくりと見ていくことにしましょう。

まずは下準備
 というわけで、次の2行をStdAfx.hの下の方(「//{{AFX_INSERT_LOCATION}}」って書かれてる部分の上の行くらい)に追加してください。


#include <Shlobj.h>
#pragma comment(lib, "Ole32.lib")
	

 COMインターフェイスを使用するときには、Shlobj.hをインクルードします(確か前は、これ以外にも必要なヘッダーファイルがあったような気がしたんだけど、今はいらないみたいです)。また、COM関係のAPIがエクスポートされているDLLのライブラリファイルOle32.libを、#pragmaを使って「ライブラリ検索リスト」に加えます。

 あと、あらかじめ言っておきますが、今回はインターフェイスそのものについては触れません。今回はCOMについて軽く触れる、まぁ肩慣らしみたいなものです。
(注:前はこのページでIMallocについて解説していましたが、CoTaskMem*()が代わりに使えることを教えていただいたので、こちらをベースに説明することにします。全体を通して、このAPIを使った方が分かりやすいと考えたためです。この情報を教えてくれた方、ありがとうございました!!)
 これで下準備はOK。では実際に使ってみましょう。

とりあえず関数だけ
 というわけで、以下のような関数を作ってみてください。


CString CTestView::BrowseForFolder( HWND p_hWnd, CString p_cSetStr
	, CString p_cRootStr, CString p_cCaptionStr, UINT p_uiFlags )
{
	BOOL		bRes;
	char		chPutFolder[MAX_PATH];
	LPITEMIDLIST	pidlRetFolder;
	BROWSEINFO	stBInfo;
	CString		cRetStr;

	// 構造体を初期化します。
	stBInfo.pidlRoot = NULL;	//ルートフォルダです。
	stBInfo.hwndOwner = p_hWnd;	//表示するダイアログの親ウィンドウのハンドルです。
	stBInfo.pszDisplayName = chPutFolder;	//選択されているフォルダ名を受けます。
	stBInfo.lpszTitle = p_cCaptionStr;	//説明の文字列です。
	stBInfo.ulFlags = p_uiFlags;	//表示フラグです。
	stBInfo.lpfn = NULL;	//ダイアログプロシージャへのポインタです。
	stBInfo.lParam = 0L;	//プロシージャに送るパラメーターです。

	// ダイアログボックスを表示します。
	pidlRetFolder = ::SHBrowseForFolder( &stBInfo );

	// pidlRetFolderにはそのフォルダを表すアイテムIDリストへのポインタが
	// 入っています。chPutFolderには選択されたフォルダ名(非フルパス)だけ
	// しか入っていないので、このポインタを利用します。

	if( pidlRetFolder != NULL )
	{
		// フルパスを取得します。
		bRes = ::SHGetPathFromIDList( pidlRetFolder, chPutFolder );
		if( bRes != FALSE )
			cRetStr = chPutFolder;

		::CoTaskMemFree( pidlRetFolder );
	}

	return cRetStr;
}
	

 見慣れないAPI、プレフィックスがSHの関数が2つにCoの関数がひとつあります。さらに、ただ取得しただけのポインタがクラスのように振る舞っていたり、わざわざ削除していたりとなんか怪しいです。では順に見ていきましょう。

BROWSEINFO
 ::SHBrowseForFolder()にはほとんど中身がありません。すべての鍵はBROWSEINFO構造体にあります。では、各メンバについて見ていきましょう。

 HWND hwndOwnerには、これから表示するダイアログボックスのオーナーウィンドウのハンドルを入れます。この例ではCDialogクラスの関数として作製しているので、そのウィンドウハンドルを渡すのがいいでしょう。

 LPCITEMIDLIST pidlRootには、ルートフォルダの「アイテムIDリスト」を入れます。って、そのアイテムIDリストってなんやねん? と思われるでしょう。このアイテムIDリストについては次回、さらにこのメンバについては次次回に説明します。

 LPSTR pszDisplayNameには、フォルダ名を返すための文字列のポインタを入れておきます。ここから帰ってくるのはフォルダ名なので、フルパスを取得することはできません。フルパスの取得方法はあとで。

 LPCSTR lpszTitleには、タイトルの下に書き込む文字列へのポインタを入れておきます。この名前から見ると、まるでダイアログのタイトルみたいなんですけどねー。

 UINT ulFlagsには、表示についてのフラグを設定します。これが結構くせものです。
 まず、BIF_STATUSTEXTだけ別です。このフラグを立てると、ダイアログのタイトルとツリーコントロールの間が広まって(ってゆーかツリーが狭まって)、テキストを書き込める欄ができます。この部分は次次回に説明します。
 さて、これ以外のフラグは、任意にひとつ選ぶようです。
 BIF_BROWSEFORCOMPUTER。ネットワークだと変わるかも。
 BIF_BROWSEFORPRINTERは、なぜか普通のファイルも表示されます。しかし、なぜか2階層より深いフォルダが開けないので、使い道がないような……。
 BIF_DONTGOBELOWDOMAINはネットワークを表示しないそうですが、筆者の環境はネットワークが継ながっていないので判りません。ですが、このフラグを立てるとすべてのフォルダが選択できるようになります。準標準フラグ?
 BIF_RETURNFSANCESTORS謎2です。何も選択できないし、何に使うの?
 BIF_RETURNONLYFSDIRSはおそらく標準フラグでしょう。ドライブとフォルダが選択できます。マイコンピュータやプリンタフォルダは選択できなくなります。
 これらのフラグを設定しなければ(つまり、0かBIF_STATUSTEXT)すべて選択できる状態になります。

 この部分に関して、「HRSさん」より情報を頂きました。以下に掲載します。情報、ありがとうございました!!

 BIF_BROWSEFORCOMPUTER:このフラグを指定すると、ネットワークコンピュータ内のコンピュータのみが選択できるようです。当然、ワークグループやドメインを展開し、別ドメインに参加しているコンピュータを選択することも可能です。
 BIF_BROWSEFORPRINTER:このフラグを指定すると、ネットワークコンピュータ内で共有プリンタがつながっているコンピュータのみが表示されるようになり、端末にぶら下がっているプリンタを選択することが可能なようです。あと、デスクトップのファイルも表示されていましたが、それはやはり意味不明。プリンタへのショートカットを配置して選択してみましたが、選択出来ませんでした。
 BIF_DONTGOBELOWDOMAIN:このフラグを指定すると、ネットワークコンピュータ内のワークグループおよびドメインを展開しなくなります。またネットワークコンピュータ内にコンピュータが表示されなくなります。マイコンピュータ以下のフォルダを選択させたい場合、有効な手段だと思いました。
 BIF_RETURNFSANCESTORS:このフラグは名前から判断すると、NFS(ネットワークファイル システム)の先祖(直訳)、多分基底という風にとればいいと思うのですが、ネットワークコンピュータ内に表示されるコンピュータしか選択できなくなりました。テスト環境がWin95だった為不明ですが、NT上で動作させると何かが有るかもしれません。

 BFFCALLBACK lpfnには、ダイアログからのメッセージを受け取るウィンドウプロシージャへのポインタを入れます。これは次次回説明します。

 LPARAM lParamには、上で説明したウィンドウプロシージャで使うための任意の32ビット整数を入れられます。これも次次回に説明します。

 int iImageには、選択されたアイテムのアイコンの、システムイメージリスト内のインデックスが返されます。これについては――説明しません(爆)。調査が済んだら説明するかも。

 と、構造体については以上で終わり。今回説明しない部分には、上の例のようにNULLや0を入れておいてください。ぜーったい必要なのはpszDisplayNameulFlagsだけですね。

::SHBrowseForFolder()
 で、::SHBrowseForFolder()関数で、ダイアログを表示します。CDialog::DoModal()のように、ここで一度止まり、ダイアログが閉じてから再び進みます。
 戻り値はアイテムIDリストというものです。あれ? さっき出てきましたねぇ。あれと同じです。このアイテムIDリストというもの、フォルダやファイルのことだと思ってください。でも、フルパスと違って、マイコンピュータやゴミ箱など、特殊なフォルダも取得できます。ダイアログボックスで「キャンセル」ボタンを押したときには、NULLが返ってきます。

 ここで重要なのは、アイテムIDリストのポインタを取得しているということです。
 COMインターフェイス関係はすべてポインタを通して操作を行います。これは「オブジェクト指向」というものと密接に関係しています。

 ウィンドウやデバイスコンテキストを操作するときにはそれぞれHWNDやHDCといった「ハンドル」を使用します。これは一種のポインタで、「どっかにある実体」をこのハンドルを使って操作する仕組みになっています。
 この仕組みこそが「オブジェクト指向」です。もしウィンドウやデバイスコンテキストがメモリ上のどこどこに存在する、みたいなことが分かっていてそれを操作するとしたら危険極まりないでしょう
 「オブジェクト指向」とは、「単なるデータを意味のある物としてとらえる」ことです。つまり、「メモリ上のわけ分かんないデータ」を「ウィンドウ」として操作することこそが、「オブジェクト指向」ということです。そして、それを実現するための重要なシステムのひとつが「ハンドル」ということになるわけです。

 ここでアイテムIDリストを「ポインタ」として取得するということは、同じことを意味します(アイテムIDリストの場合、実は違う。それはのちほど)。これと同じように、他のCOMインターフェイスオブジェクトはすべてポインタとして取得し、それを元に操作します。このことを、頭の片隅にでも置いておいてください。

::SHGetPathFromIDList()
 なんかSHの付く関数が続きます。この関数は、先ほど取得したアイテムIDリストから、フォルダのフルパスを取得することができます。BROWSEINFO構造体pszDisplayNameに返ってくるのはフォルダ名だけなので、この時点でやっとフルパスを取得できるというわけです。

::CoTaskMemFree()
 さて、当初の目的はここで達成しています。ところが、インターフェイスは色々と後始末をしなければならないのです。
 ::SHBrowseForFolder()で取得したアイテムIDリスト、これを削除しなければなりません。この関数から得たポインタは、new演算子malloc()で動的に割り当てたメモリのため、これをプログラムの側で解放しなければならないのです
 このメモリ領域を開放するAPIは::CoTaskMemFree()です。プレフィックスのCoは、Com系の関数を意味しています。これをしないとメモリリークが発生してしまうので注意しましょう(MFCバージョンのnewみたいな検出機構も付いていないしね)。

まとめ
 今回はインターフェイスは出てきませんでしたね。ちょっとシェル系関数とCOM系関数を使ってみただけでした。そんなに難しい部分はないと思います……今のところは。
 次回は「フォルダ選択ダイアログ」から離れて、アイテムIDリストの取得方法や「インターフェイス」について見てみます。さらに次次回では、この後編で、「フォルダ選択ダイアログ」についてさらに突っ込んだ内容にしたいと思います。

(C)KAB-studio 1997, 1998 ALL RIGHTS RESERVED.