« BCB6::プロジェクトマネージャーでのファイルのドラッグドロップ | トップページ | 仮想リストビューのカラム幅の自動調節 »

2005年11月24日 (木)

ファイルを開くダイアログでフォルダも開く

GetOpenFileNameでファイルを開くダイアログを出すことができますが、あくまでファイルを開くダイアログなのでファイルしか開けません。
また、SHBrowseForFolderでフォルダを開くダイアログを出すことができますが、こちらはフォルダしか開けません。

ではファイルとフォルダを開きたい場合はどうすればいいか?と考えて、とりあえず今回ファイルを開くダイアログを改造してみました。

以下サンプルソースとポイントです。

TBFileDialog051124.zip

・通常GetOpenFileNameに渡したOPENFILENAME構造体のlpstrFileメンバがユーザーが選択したファイルパスになります。
 フォルダを選択できるようにするなら順当に考えればGetOpenFileNameの処理中にlpstrFileに選択しているフォルダへのパスも入れればいいということになります。
 ただ、GetOpenFileNameが処理を返すときにはダイアログが閉じてしまっています。
 ユーザーの選択しているフォルダの情報を得るならlpfnHookメンバを利用してフックプロシージャを使うことになるでしょう。

 このフックプロシージャ内でGetOpenFileNameが終了したときのlpstrFileを設定できればベストなんですが、しばらく調べても設定する方法がわかりませんでした。
 CommDlg_OpenSave_SetControlTextでダイアログ終了時にエディットボックスにパスを入れてみたりOFNOTIFYのOPENFILENAMEのlpstrFileにパスを入れてCDN_FILEOKを自分で送ってみたりとか…。
 仕方ないのでフォルダも選択できるようにするときはlpstrFileを使わず、別の変数に選択されている項目を保存することにしました。

・次に選択されているフォルダの取得の仕方ですが、CDN_SELCHANGEがフックプロシージャに送られてきたタイミングで取得します。
 しかし、CommDlg_OpenSave_GetFilePathを使ってもファイルしかえられません。OFN_ALLOWMULTISELECTを設定してフォルダとファイルをまぜこぜに選択してもきれいにファイルだけ帰ってきます。(--;
 フォルダを得るにはリストビューのハンドルを得て直接項目をリストアップする必要があります。さらにリストビューはフォルダを変更するごとに再構築されるのでハンドルはそのつど取得したほうが賢明なようです。(Ref: http://www2.tba.t-com.ne.jp/tail/prog/files/tlmultiofn1.htm )
 またフォルダが変更されたとき、選択は解除されますがCDN_SELCHANGEは送られません。CDN_FOLDERCHANGEが送られてきます。

・以上で選択しているフォルダとファイルを取得できますが、このままではフォルダを選択した状態で開くボタンを押しても、そのフォルダに移動してしまい、開けません。
 そこでOkボタンをCDN_INITDONEでサブクラス化します。そして何か選択されているときに開くが押されたら問答無用で閉じてしまいます。
 閉じるには本当はSetWindowLongPtrをつかって戻り値を返したりするようですが、今回はEndDialogで無理やり閉じてしまいました。
 閉じるとGetOpenFileNameが処理を返すのでCDN_SELCHANGEやCDN_FOLDERCHANGEで取得した選択中のファイルのリストの情報を使ってその後の処理を行います。

以下は上のアーカイブ内のTBFileDialog.cppのフォルダ選択部分の実装の重要なところの抜粋です。
これだけではコンパイルできませんが、上のポイントを実際に実装する流れをつかむヒントになるかなと。


////////////////////////////////////////////////////////////////////////////////

LRESULT __stdcall	FileDialog::OKWndProcHook(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	FileDialog*	pThis = TB_ENFORCE( NullFail(), reinterpret_cast< FileDialog* >( GetLongPtr( hWnd, GWLP_USERDATA ) ) );
	if( WM_LBUTTONUP == uMsg )
	{
		//通常OKボタンを押したときフォルダだけが選択されていたならそのフォルダに移動する。
		//ただしフォルダも選択するオプションが有効ならその操作をキャンセルして閉じる。

		//基本的にはOkボタンが押されたら閉じるが、もしMustExistフラグが立っていたら作成をユーザーに問い合わせる必要がある。
		//ただし作成を行うのはエディットボックスに何か値が入っていたら。値が入っていなければただOKボタンが押されたのを無視する。
		//これらの操作は基本的にmPathsがemptyのときだけ。
		HWND	hParent = GetParent( hWnd );
		if( pThis->mPaths.empty() )
		{
			//MustExistで、現在選択されてるファイルが存在しない場合は警告を表示
			if( pThis->mIsMustExist )
			{
				TB_STRING	file = CommDlgOpenSaveGetFilePath( hParent );
				if( !IsExist( file ) )
				{
					if( IDYES == MessageBox( hParent, (file + _T( "\nこのファイルは存在しません。\n\n作成しますか?" )).c_str(), GetTitle( hParent ).c_str(), MB_YESNO ) )
					{
						CreateFile( file );
						pThis->mPaths.push_back( file );
						EndDialog( hParent, IDOK );
					}
				}
			}
		}
		else
			EndDialog( GetParent( hWnd ), IDOK );
	}
	return CallWindowProc( TB_ENFORCE( NullFail(), pThis->mOriginalProc ), hWnd, uMsg, wParam, lParam );
}

////////////////////////////////////////////////////////////////////////////////

UINT __stdcall	FileDialog::ShowDialogHook(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	if( WM_NOTIFY == uMsg )
	{
		UINT	code = reinterpret_cast< NMHDR* >( lParam )->code;
		HWND hParent = GetParent( hWnd );
		switch( code )
		{
			case CDN_INITDONE:
				//センタリング
				Move( hParent, Centering( GetScreenRect(), GetWindowRect( hParent ) ) );
			case CDN_SELCHANGE:
			case CDN_FOLDERCHANGE:
			{
				FileDialog*	pThis = TB_ENFORCE( NullFail(), reinterpret_cast< FileDialog* >( reinterpret_cast< LPOFNOTIFY >( lParam )->lpOFN->lCustData ) );
				if( !pThis->mIsFolderSelectable )
					break;
				switch( code )
				{
					case CDN_INITDONE:
					{
						//OKボタンのHWNDを取得
						HWND	hOK = GetChild( hParent, _T( "開く(&O)" ) );
						//OkボタンのユーザーデータにFileDialogのthisを仕込む。
						SetLongPtr( hOK, GWLP_USERDATA, reinterpret_cast< LONG_PTR >( pThis ) );
						//OKボタンをサブクラス化
						pThis->mOriginalProc = SubClass( hOK, OKWndProcHook );
					}
					break;
					case CDN_SELCHANGE:
					{
						//リストビューで選択されている項目のタイトルを取得して、現在選択しているフォルダのパスを先頭にくっつけてmPathsに保存
						pThis->mPaths = ( CommDlgOpenSaveGetFolderPath( hParent ) + _T( "\\" ) ) + GetSelectedItemTitle( TB_ENFORCE( NullFail(), GetChild( hParent, _T( "FolderView" ) ) ) );
					}
					break;
					case CDN_FOLDERCHANGE:
					{
						//フォルダが変更された。
						//何も選択されていないはずなので選択をクリア
						pThis->mPaths.clear();
					}
					break;
				}
			}
			return TRUE;
		}
	}
	return FALSE;
}

/////////////////////////////////////////

bool	FileDialog::ShowDialog( tDialogFunction DialogFunction )
{
	//絶対パスに変換して書き換え可能なバッファに格納(ofn.lpstrFileがconstじゃないので。)
	TB_STRING	temp = GetAbsolutePath( mRelativeBase, GetPrivateProfileString( mIniPath, mSection, mKey, mInitialPath ) );
	std::vector< _TCHAR >	result( temp.c_str(), temp.c_str() + temp.size() + 1 );
	result.resize( MAX_PATH*2, '\0' );

	//デフォルト拡張子を設定する。
	if( mDefaultExtension.empty() )
	{
		//まだ設定されていない
		//ファイルタイプが設定されていればそれの先頭の拡張子を使う。
		if( !mFileTypes.empty() )
			mDefaultExtension = mFileTypes[0].second;
		else if( '\0' != result[0])
		{
			//ファイルタイプはないけど、初期ファイル名があるならその拡張子を使う。
			TB_STRING extension = &result[0];
			TB_STRING::size_type position = extension.rfind( _T( "." ) );
			extension = extension.substr( TB_STRING::npos == position ? 0 : position );
		}
	}
	//ファイルタイプを作成する。
	std::vector< _TCHAR >	filters;
	{
		//全てのファイル(*)を追加。
		AddFileType( _T( "全てのファイル" ), _T( "*" ) );
		//二重NULL終端文字列群を作成
		FileTypes::iterator endIt = mFileTypes.end();
		for( FileTypes::iterator it = mFileTypes.begin(); it != endIt; ++it )
		{
			filters.insert( filters.end(), it->first.c_str(), it->first.c_str() + it->first.length() );
			filters.push_back( '(' );
			filters.insert( filters.end(), it->second.c_str(), it->second.c_str() + it->second.length() );
			filters.push_back( ')' );
			filters.push_back( '\0' );
			filters.insert( filters.end(), it->second.c_str(), it->second.c_str() + it->second.length() + 1 );
		}
		filters.push_back( '\0' );
	}

	OPENFILENAME	ofn = { sizeof( OPENFILENAME ) };
	ofn.Flags = OFN_EXPLORER | OFN_ENABLEHOOK;
	if( mIsMultiSelect )
		ofn.Flags |= OFN_ALLOWMULTISELECT;
	if( mIsMustExist )
		ofn.Flags |= OFN_CREATEPROMPT;
	ofn.lpstrTitle = mTitle.empty() ? NULL : mTitle.c_str();
	ofn.lpstrFile = &result[0];
	ofn.nMaxFile = TB_NUMERIC_CAST< DWORD >( result.size() );
	ofn.lpstrFilter = &filters[0];
	ofn.lpstrDefExt = mDefaultExtension.c_str();
	ofn.hwndOwner = mhWnd;
	ofn.lpfnHook = ShowDialogHook;
	ofn.lCustData = reinterpret_cast< LPARAM >( this );

	if( !DialogFunction(&ofn) )
	{
		TB_ENFORCE( CommonDialog(), CommDlgExtendedError() );
		mPaths.clear();
		return false;
	}

	//FolderSelectableでない場合はresultからファイルを抽出する。
	if( !mIsFolderSelectable && mIsMultiSelect )
	{
		std::vector< _TCHAR >	path;
		_TCHAR*	pIndex2 = &result[0] + lstrlen( &result[0] );
		for( _TCHAR* pIndex = pIndex2+1; pIndex[0] != '\0'; pIndex += lstrlen( pIndex )+1 )
		{
			//まずカレントディレクトリをコピー
			path.assign( &result[0], pIndex2 );
			path.push_back( '\\' );
			path.insert( path.end(), pIndex, pIndex + lstrlen( pIndex ) + 1 );
			mPaths.push_back( &path[0] );
		}
	}

	//MustExistフラグが寝てて、フォルダを切り替えた直後にOkを押した場合、mPathが空。
	//resultの値を使う。
	if( mPaths.empty() )
		mPaths.push_back( &result[0] );

	//Iniに保存
	WritePrivateProfileString( mIniPath, mSection, mKey, mIsRelative ? GetRelativePath( mRelativeBase, mPaths[0] ) : mPaths[0] );

	return true;
}

////////////////////////////////////////////////////////////////////////////////

|

« BCB6::プロジェクトマネージャーでのファイルのドラッグドロップ | トップページ | 仮想リストビューのカラム幅の自動調節 »

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



« BCB6::プロジェクトマネージャーでのファイルのドラッグドロップ | トップページ | 仮想リストビューのカラム幅の自動調節 »