ファイルオペレーション

 さて、アイテムIDリストのことが理解できた! じゃぁどうする? ってことで、取得したファイルを操作してみましょう。
 ちょっと注意して欲しいこと。関数の引数として渡されるLPITEMIDLISTは、特に指定がない限り特定のファイルを示すフルパスのアイテムIDリストです。つまりIEnmuIDList::Next()で取得しただけのものはダメ! 望んだ結果をもたらさないでしょう。前回のを読んで、ちゃんとフルパスのアイテムIDリストを渡してください。

実行する!!
 ファイルの実行(エクスプローラーでのダブルクリック)にはShellExecuteEx()というAPIを使用します。実際には次のような感じです。


BOOL CT_Filer2View::DoDefaultCmd( LPITEMIDLIST p_pidlFile )
{
	BOOL			bRes;
	SHELLEXECUTEINFO	stExeInfo;

	stExeInfo.cbSize = sizeof( SHELLEXECUTEINFO );
	stExeInfo.fMask = SEE_MASK_INVOKEIDLIST;
	stExeInfo.hwnd = GetSafeHwnd();
	stExeInfo.lpVerb = NULL;
	stExeInfo.lpFile = NULL;
	stExeInfo.lpParameters = NULL;
	stExeInfo.lpDirectory = NULL;
	stExeInfo.nShow = SW_SHOWNORMAL;
	stExeInfo.hInstApp = NULL;
	stExeInfo.lpIDList = (LPVOID)p_pidlFile;

	bRes = ::ShellExecuteEx( &stExeInfo );

	return bRes;
}
	

 これだけで、関連づけでの「標準」コマンドが実行されます。それ以外のコマンドを使いたい場合にはlpVerbメンバにそのコマンドの文字列(例えば"Open"とか"開く"とか)を入れてください。
 (追加)それと、nShowメンバにはSW_SHOWNORMALを入れてください。どうやら、これ以外だと望んだウィンドウ表示を行わないアプリケーションが多いようです(例えば、最大化したままで終了したのに、次回起動時に標準のサイズになっていたり)。

コンテキストメニューの表示
 コンテキストメニュー(エクスプローラーでの右クリックメニュー)を表示するにはIContextMenuというインターフェイスを使用します。これは結構めんどくさいです。


BOOL CT_Filer2View::ShowContextMenu( LPITEMIDLIST p_pidlFile )
{
	int			iCommand;
	HRESULT			hRes;
	HMENU			hMenu;
	LPCONTEXTMENU		pContMenu;
	CMINVOKECOMMANDINFO	stInvokeInfo;
	CPoint			cMenuPt;

	// 前準備。
	::GetCursorPos( &cMenuPt );	//デスクトップ上でのカーソル位置を取得します。
	hMenu = ::CreatePopupMenu();
	ASSERT( hMenu );

	// IContextMenuを取得します。
	// m_pDFolderはデスクトップフォルダを示すIShellFolderです。
	hRes = m_pDFolder->GetUIObjectOf( GetSafeHwnd(), 1, (LPCITEMIDLIST *)&p_pidlFile
		, IID_IContextMenu, 0, (LPVOID *)&pContMenu );
	if( hRes != NOERROR ) 
		return FALSE;

	// メニューをインサートします。
	hRes = pContMenu->QueryContextMenu( hMenu, 0, 1, 0xffff, CMF_NORMAL );
	if( hRes == NULL )
		goto CleanUp;

	// メニューを表示します。
	iCommand = ::TrackPopupMenu( hMenu
		, TPM_LEFTALIGN | TPM_BOTTOMALIGN  | TPM_RETURNCMD | TPM_LEFTBUTTON
		, cMenuPt.x, cMenuPt.y, 0, GetSafeHwnd(), NULL );
	if( iCommand == NULL )
		goto CleanUp;

	// コマンドを実行します。
	stInvokeInfo.cbSize = sizeof( CMINVOKECOMMANDINFO );
	stInvokeInfo.fMask = 0;
	stInvokeInfo.hwnd = GetSafeHwnd();
	stInvokeInfo.lpVerb = MAKEINTRESOURCE( iCommand - 1 );
	stInvokeInfo.lpParameters = NULL;
	stInvokeInfo.lpDirectory = NULL;
	stInvokeInfo.nShow = SW_SHOWNORMAL;
	stInvokeInfo.dwHotKey = 0;
	stInvokeInfo.hIcon = NULL;

	hRes = pContMenu->InvokeCommand( &stInvokeInfo );

CleanUp:
	// 後処理です。
	::DestroyMenu( hMenu );
	pContMenu->Release();

	return TRUE;
}
	

 まず最初に、IShellFolder::GetUIObjectOf()を使ってIContextMenuを取得します。このメソッドはIShellFolder::BindToObject()に近いので分かりやすいでしょう。
 次にIContextMenu::QueryContextMenu()を用いて、すでに作ってあるポップアップメニューへとコンテキストメニューをインサートします。上の例では空のメニューにインサートしているので、ただコンテキストメニューだけが表示されますが、メニューハンドルがあるのでこの前後で好き勝手にメニューを操作できるということもできます。ただし「ファイルに対する拡張」を行いたい場合にこの機能を使うのは邪道でしょう。だって、他のファイラーやエクスプローラーじゃ見られないってことになりますから。そういう場合にはレジストリの操作やシェルネームスペースの登録などを行ってください(この系統のサンプルって結構あるし)。

 ここまでで「メニューの表示」は終了しました。ただ表示するだけなら、これだけで十分です。が、もちろん選択されたコマンドを実行しなければなりません
 コマンドの実行にはIContextMenu::InvokeCommand()を使用します。構造体のメンバは、前述のShellExecuteEx()に似ていますし、多くのメンバはNULLで構わないのでそれほど難しくないでしょう。

 注意して欲しいことふたつ。これは一種の動的なメニュー作製なので、MFCベースだとすべて淡色表示されると思います。これはCFrameWnd::m_bAutoMenuEnableFALSEを入れてしまえば解決します。詳しくはMFCチップスのメニューを動的に追加する(後編)を参考にしてください。

 もうひとつの注意して欲しいこと。このメニューには送るがありません(ポップアップメニューはあるけど、空です)。これは解決するとかそういうことじゃなくて、アプリケーション側で勝手にインプリメントしてねってことだと思います。多分……。

リネーム
 ファイル名の変更はIShellFolder::SetNameOf()を使用します。


BOOL CT_Filer2View::Rename( CString p_cStr, LPSHELLFOLDER p_pFolder
		   , LPITEMIDLIST p_pidlOld, LPITEMIDLIST *p_ppidlNew)
{
	HRESULT	hRes;
	OLECHAR	ochNewName[MAX_PATH];

	// ユニコードに変換します。
	::MultiByteToWideChar( CP_ACP, MB_PRECOMPOSED, p_cStr, -1,
                ochNewName, MAX_PATH );

	hRes = p_pFolder->SetNameOf( GetSafeHwnd(), p_pidlOld, ochNewName
		, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS | SHCONTF_INCLUDEHIDDEN
		, p_ppidlNew );

	if( hRes != NOERROR )
		return FALSE;

	return TRUE;
}
	

 第2引数には親フォルダ、第3引数にはファイル名orフォルダ名だけです。よーするにIEnumIDList::Next()で取得したものをそのまま渡してください。あと、渡す文字列はユニコードでないといけないので、ユニコードに変換してから渡しましょう。

その他の操作
 さて最後です。その他のファイルの操作にはSHFileOperation()を使用します。が、実はこれ、アイテムIDリストを使用しません。普通のパスの文字列です。だから、実際にはDeleteFile()とかCopyFile()といったAPIでも十分です。
 ただ、一部の機能はSHFileOperation()で行う必要があります。例えばゴミ箱に削除とか。
(以下のコードと解説、 SHFILEOPSTRUCT::pTo が抜けていたので直しました)


BOOL CT_Filer2View::DeleteFile( LPCTSTR p_pchPath )
{
	SHFILEOPSTRUCT	stFile;

	stFile.hwnd = GetSafeHwnd();
	stFile.wFunc = FO_DELETE;
	stFile.pFrom = p_pchPath;
	stFile.pTo = NULL;				// これないとダメ。
	stFile.fFlags = FOF_ALLOWUNDO | FOF_NOCONFIRMATION;

	::SHFileOperation( &stFile );	//ファイルを削除します。

	return TRUE;
}
	

 こうすることで、 SHFILEOPSTRUCT::pFrom で指定したファイルをごみ箱に削除します。その他、コピーや削除を行っている間のプログレス表示ダイアログも表示させたりできますし、「元に戻す」機能も付けられます(削除の場合には、「元に戻す」からこそゴミ箱行きで、そうでない場合には完全削除になります)。これらについてはフラグを色々と試してみてください。

 注意して欲しいのは渡すパスの文字列。まずリファレンスには「SetCurrentDirectory()とか使ってね」って書かれてますが、こうでなくフルパスを渡した方がいいみたいです。少なくとも、フルパスを渡さなかった場合には「ゴミ箱行き」にはなりませんでした(つまり「完全削除」)
 また、渡す文字列はdouble null-terminated、つまり\0\0で閉じる必要があります。これは複数のファイルを同時に扱えるからです。ところが、CString常に文字列の長さに合わせてメモりサイズを変更するとゆーことを行っているので最後に\0を追加することができません。そのため、普通のchar型文字列を使用してください。

 さて、このAPIはファイルのパスを扱っているわけですが、じゃぁアイテムIDリストでしか示せないファイルはどうすればいいんだ?ということになるでしょう。
 が、ちょっと考えてみましょう。そういうファイルはコピーや移動ができないようなっています。例えばコントロールパネルのアイテムをデスクトップにコピーしたりしないでしょう。また、デスクトップのショートカットはC:\Windows\デスクトップにあります。つまり、普通のパスとして扱えるというわけです。
 デスクトップ上の移動できないファイル(マイコンピュータとかゴミ箱とか)でも、名前は変えられます。これは、先ほどのIShellFolder::SetNameOf()を使用すればOKです。また、ダイヤルアップネットワークなどの削除はレジストリの操作で可能になるでしょう。
 というわけで、ここまででたいがいの操作はできるというわけです。

 ただ……ゴミ箱の中身を空にするのってどうすればいいの?
(SHEmptyRecycleBin() という API で空にできることが分かりました。情報を提供してくれたAさんありがとう!)

他に操作ってあるかな?
 今回はAPIやインターフェイスを駆使して色々と試してみました。言ってみれば応用編です。でも、実際には難しい部分なんてほとんどないでしょう。
 次回は「アトリビュート」、つまり「属性」の取得方法について見てみることにします。

(C)KAB-studio 1998 ALL RIGHTS RESERVED.