ファイル選択ダイアログ

GetOpenFileName関数

プログラムからファイルを読み書きするには、そのファイルの場所の情報(パス)が必要になります。
ユーザーに任意のファイルを選択させたい場合、手動でパス文字列を入力させていたのでは不便ですし、入力ミスのおそれがあります。

既存のファイルを指定するにはファイル選択ダイアログを使用すると便利です。
これはあらかじめWindowsが用意しているダイアログで、これをプログラム上から呼び出すだけでファイルの選択が可能になります。
ファイルを開くダイアログの例

ファイルを開くダイアログはGetOpenFileName関数を使用します。

BOOL GetOpenFileNameW(
 LPOPENFILENAMEW unnamedParam1
);
ファイルを開くダイアログを作成する。
ユーザーが「OK」ボタンを選択しダイアログを閉じると戻り値はゼロ以外になる。
その他の方法で閉じるかエラーが発生した場合はゼロが返される。

この関数はOPENFILENAME構造体のポインタを引数に取ります。
具体的な動作はこの構造体にセットした値により決定されます。

typedef struct tagOFNW {
 DWORD lStructSize;
 HWND hwndOwner;
 HINSTANCE hInstance;
 LPCWSTR lpstrFilter;
 LPWSTR lpstrCustomFilter;
 DWORD nMaxCustFilter;
 DWORD nFilterIndex;
 LPWSTR lpstrFile;
 DWORD nMaxFile;
 LPWSTR lpstrFileTitle;
 DWORD nMaxFileTitle;
 LPCWSTR lpstrInitialDir;
 LPCWSTR lpstrTitle;
 DWORD Flags;
 WORD nFileOffset;
 WORD nFileExtension;
 LPCWSTR lpstrDefExt;
 LPARAM lCustData;
 LPOFNHOOKPROC lpfnHook;
 LPCWSTR lpTemplateName;
 LPEDITMENU lpEditInfo;
 LPCSTR lpstrPrompt;
 void *pvReserved;
 DWORD dwReserved;
 DWORD FlagsEx;
} OPENFILENAMEW, *LPOPENFILENAMEW;
GetOpenFileName関数で使用する情報を格納する構造体。

メンバが非常に多いですが、使用するメンバにだけ値をセットします。

lStructSizeメンバはこの構造体のサイズの指定です。
つまりsizeof(OPENFILENAME)を指定します。

hwndOwnerメンバはこのダイアログボックスの所有者ウィンドウのハンドルを指定します。
NULLにすることも可能です。

hInstanceメンバはFlagsメンバにOFN_ENABLETEMPLATEHANDLEフラグが含まれる場合、ダイアログテンプレートを含むメモリオブジェクトへのハンドルを指定します。
FlagsメンバにOFN_ENABLETEMPLATEフラグが含まれる場合、ダイアログテンプレートを含むモジュールへのハンドルを指定します。
どちらのフラグも含まれない場合、このメンバは無視されます。

lpstrFilterメンバはファイルのフィルター文字列の指定です。
(ダブルNULL終端文字列)
特定の拡張子を持つファイルだけをダイアログ上に表示する場合に使用します。
NULLを指定するとダイアログにフィルターは表示されません。
使い方は後述するサンプルコードを参照してください。

lpstrCustomFilterメンバはユーザーが選択したフィルターパターン(ワイルドカード)文字列が格納される文字列バッファの指定です。
文字列はフィルターの説明文とフィルターパターン文字列のペアで構成されます。
それぞれの文字列はNULL文字で区切られ、終端はダブルNULLで終了します。
ユーザーがファイルを選択すると、選択されたフィルターパターンが文字列ペアの後半部分にコピーされます。
NULLを指定するとファイルター文字列は保存されません。
(古いダイアログ用?)

nMaxCustFilterメンバはlpstrCustomFilterメンバのサイズ(文字数)の指定です。
少なくとも40文字分以上の長さが必要です。
lpstrCustomFilterメンバがNULLの場合は無視されます。

nFilterIndexメンバはフィルター文字列のインデックスです。
1を指定すると最初のフィルター文字列が使用されます。
0を指定するとlpstrCustomFilterメンバで指定されているフィルター文字列が使用されます。
lpstrCustomFilterメンバがNULLの場合は最初のフィルター文字列が使用されます
ファイルが選択されると自動的に更新されます。

lpstrFileメンバはユーザーが選択したファイルの絶対パスが格納される文字列バッファの指定です。
関数呼び出し前に文字列をセットしておくと、ダイアログの初期値として使用されます。
(このファイルが選択された状態でダイアログが開かれる)
初期値を使用しない場合はバッファを空にしておく必要があります。

nMaxFileメンバはlpstrFileメンバのサイズ(文字数)の指定です。
少なくとも256文字分以上の長さが必要です。

lpstrFileTitleメンバはユーザーが選択したファイルのファイル名を格納する文字列バッファの指定です。
(ドライブ名やディレクトリ名は含まれない)
必要なければNULLを指定できます。

nMaxFileTitleメンバはlpstrFileTitleメンバのサイズ(文字数)の指定です。
lpstrFileTitleメンバがNULLの場合は無視されます。

lpstrInitialDirメンバは、ダイアログの初期ディレクトリのパスの指定です。
ただしここに指定したディレクトリが常に初期値として使用されるわけではなく、細かい条件により決定されます。
(詳細は省きます)

lpstrTitleメンバはダイアログボックスのタイトルバーに表示される文字列の指定です。
NULLを指定するとデフォルトのタイトル(「開く」)が使用されます。

Flagsメンバはダイアログボックスの動作を変更するフラグです。
以下の定数の組み合わせを使用できます。
(以下の説明は後述する「名前を付けて保存」ダイアログで使用するものも含みます)
また、ダイアログボックスが閉じられたときに特定の状態を表すためにFlagsメンバは上書きされることがあります。

定数 説明
OFN_READONLY

「読み取り専用ファイルとして開く」ボタンを「ファイルを変更するために開く」に変更する
古いスタイルのダイアログでは、読み取り専用チェックボックスにチェックを入れた状態でダイアログを開く

読み取り専用としてファイルを開くと、このフラグがFlagsメンバにセットされる
変更するためにファイルを開くと、このフラグがFlagsメンバから削除される

OFN_OVERWRITEPROMPT ファイルの保存時、既にファイルが存在する場合に上書き確認ダイアログを表示する
OFN_HIDEREADONLY 読み取り専用チェックボックスを非表示にする
OFN_NOCHANGEDIR ユーザーが別のディレクトリを選択した場合でもカレントディレクトリを移動しない
「開く」ダイアログ(GetOpenFileName関数)では無効
OFN_SHOWHELP 「ヘルプ」ボタンを表示する。
hwndOwnerメンバにNULLを指定してはならず、HELPMSGSTRINGメッセージを処理する必要がある
OFN_ENABLEHOOK lpfnHookメンバーで指定したフック機能を有効にする
OFN_ENABLETEMPLATE ダイアログボックスのテンプレートを有効にする
hInstanceメンバはlpTemplateNameメンバが格納されているモジュールへのハンドルを指定する必要がある
OFN_ENABLETEMPLATEHANDLE ダイアログボックスのテンプレートを有効にする
hInstanceメンバはダイアログボックステンプレートが格納されたメモリオブジェクトを指定する必要がある
(lpTemplateNameメンバは無視される)
OFN_NOVALIDATE ファイル名に、ファイル名として無効な文字を使用できる
(ファイル名のチェックをしない)
OFN_ALLOWMULTISELECT 複数のファイルを選択可能にする
OFN_EXPLORERと同時に指定しない場合、古いスタイルのダイアログになる
OFN_EXTENSIONDIFFERENT lpstrDefExtメンバで指定した拡張子とは異なる拡張子をユーザーが入力した
OFN_PATHMUSTEXIST 有効なパスおよびファイル名しか入力できない
OFN_FILEMUSTEXIST 既存のファイル名しか入力できない
このフラグはOFN_PATHMUSTEXISTフラグも有効にする
「名前を付けて保存」ダイアログ(GetSaveFileName関数)では無効
OFN_CREATEPROMPT ユーザーが存在しないファイル名を指定した場合、ファイルを新規作成する確認ダイアログを表示する
OFN_SHAREAWARE ネットワーク共有違反エラーを無視してファイル名を返す
OFN_NOREADONLYRETURN 読み取り専用ファイルおよびディレクトリは選択できない
OFN_NOTESTFILECREATE ダイアログを閉じるまでファイルを作成しない
OFN_NONETWORKBUTTON 「ネットワーク」ボタンを非表示および無効にする
OFN_NOLONGNAMES 長いファイル名を使用しない
(8.3形式を使用する)
古いスタイルのダイアログボックスでのみ有効
OFN_EXPLORER

OFN_ALLOWMULTISELECTフラグと同時に指定することで、新しいスタイルのダイアログボックスを使用する
OFN_ALLOWMULTISELECTフラグまたはOFN_ENABLEHOOKフラグを指定しない場合、このフラグの有無に関係なく常に新しいスタイルのダイアログボックスが使用される

フックプロシージャまたはカスタムテンプレートを使用する場合に指定する必要がある

OFN_NODEREFERENCELINKS ショートカットファイル(.lnkファイル)を選択した時、そのショートカットファイルのパスを返す
このフラグを指定しない場合、ショートカットファイルが指す先にあるファイルへのパスを返す
OFN_LONGNAMES

古いスタイルのダイアログボックスで長いファイル名の使用を強制する
このフラグを指定しない場合、またはOFN_ALLOWMULTISELECTフラグが同時に指定されている場合、スペースが入るファイル名に短いファイル名(8.3形式)が使用される

※MicorSoft Docではこのような説明になっているが、近年のOSではこの通りに機能しないかもしれない

OFN_ENABLEINCLUDENOTIFY ユーザーがフォルダを開いたとき、CDN_INCLUDEITEMメッセージをフックプロシージャに送信する
OFN_ENABLESIZING フックプロシージャまたはカスタムテンプレートの使用時、ダイアログボックスのサイズを変更可能にする
(標準ではこのフラグの有無に関係なくサイズ変更可能)
古いスタイルのダイアログボックスはこのフラグに関係なくサイズ変更はできない
OFN_DONTADDTORECENT 選択したファイルを「最近使ったファイル(ファイル履歴)」に追加しない
OFN_FORCESHOWHIDDEN システムファイルおよび隠しファイルを強制的に表示する
ただし隠しファイルに設定されているシステムファイルは表示しない

nFileOffsetメンバはlpstrFileメンバに格納されるパスの、ファイル名の位置を示すオフセット(先頭からの文字数)です。
先頭の文字は0文字目です。
例えば「C:\abc\file.txt」というパスの場合、このメンバには「7」が格納されます。
lpstrFileメンバに複数のパスが格納されている場合、最初のパスのファイル名の位置を示します。

nFileExtensionメンバはlpstrFileメンバに格納されるパスの、拡張子の位置を示すオフセット(先頭からの文字数)です。
これは「.」(ドット)の次の文字の位置を示します。
例えば「C:\abc\file.txt」というパスの場合、このメンバには「12」が格納されます。
拡張子がない場合は0となります。

lpstrDefExtメンバはデフォルトの拡張子の指定です。
ユーザーが拡張子を入力しなかった場合に、ここで指定した拡張子が追加されます。
文字列には「.」(ドット)は含めません。
何文字の文字列を指定しても、最初の三文字のみが使用されます。
必要がない場合はNULLを指定できます。

lCustDataメンバはフックプロシージャを使用する場合に、フックプロシージャに渡す任意の値の指定です。
フックプロシージャとは、ダイアログの動作をカスタマイズする際に使用するものです。
ここでは説明は省きます。

lpfnHookメンバはフックプロシージャの指定(関数へのポインタ)です。
FlagsメンバにOFN_ENABLEHOOKフラグを含めない場合、このメンバは無視されます。
ここでは説明は省きます。

lpTemplateNameメンバはダイアログテンプレートの指定です。
ダイアログテンプレートは、ダイアログボックスの見た目を変更するものです。
ここでは説明は省きます。

lpEditInfoメンバ、lpstrPromptメンバ、pvReservedメンバは使用しません。

FlagsExメンバはダイアログをカスタマイズするために、OFN_EX_NOPLACESBARという定数を指定することができます。
この定数を指定すると、ダイアログに特定のディレクトリへのクイックアクセスを提供するツリーコントロール(プレースバー)が表示されなくなります。
必要がなければ0を指定します。

サンプルコード

メンバ数が多くて面倒そうですが、単にファイルを選択するだけならば必要なメンバはそれほど多くありません。
以下にサンプルを示します。


#include <windows.h>

int APIENTRY wWinMain(
	HINSTANCE hInstance,
	HINSTANCE hPrevInstance,
	LPWSTR lpCmdLine,
	int nCmdShow)
{
	//ファイルパスを格納する変数
	//0で初期化しておく
	WCHAR filePath[MAX_PATH] = { 0 };
	//変数宣言後の初期化は↓のように先頭にNULL文字を入れるだけでも良い
	//filePath[0] = L'\0';

	OPENFILENAME ofn = { 0 };
	ofn.lStructSize = sizeof(OPENFILENAME);		//構造体のサイズ
	ofn.hwndOwner = NULL;						//オーナーウィンドウのハンドル
	ofn.lpstrFilter = L"全てのファイル (*.*)\0*.*\0"; //拡張子フィルター
	ofn.nFilterIndex = 0;			//フィルターの初期値
	ofn.lpstrFile = filePath;		//選択したファイルパスを受け取るバッファ
	ofn.nMaxFile = MAX_PATH;		//↑のバッファサイズ

	if (GetOpenFileName(&ofn)) {
		MessageBox(NULL, filePath, L"情報", MB_OK);
	}
	else {
		MessageBox(NULL, L"キャンセルされました", L"情報", MB_OK);
	}

	return 0;
}

選択したファイルのパスをメッセージボックスで表示するプログラムです。
コードを簡略化するために、ウィンドウの生成等はせずWinMain関数内でいきなりダイアログを呼び出しています。

ファイルパスを受け取るための変数filePathをあらかじめ用意しておきます。
この変数は空文字(先頭要素がNULL文字)である必要があります。
ローカル変数は宣言後の中身は不定なので、0で初期化しておきます。
(先頭要素にNULL文字を代入するだけでも良い)

同じように、OPENFILENAME構造体も0で初期化しておきます。
こちらは不要なメンバに値がセットされないようにする必要があるので、全てのメモリ領域を0で初期化します。

static変数の場合はすべて0で初期化されるのでこの処理は必要ありません。
また、今回は最初にすべて0で初期化しているので、メンバへのNULL0代入は本来は必要ありません。

次に、OPENFILENAME構造体のメンバに値をセットしていきます。

今回は親となるウィンドウがないので、hwndOwnerメンバにはNULLを指定します。

ダイアログ上で選択したファイルパスはlpstrFileメンバに格納されます。
ここにはローカル変数filePathのポインタを指定しているので、この変数にファイルパスが格納されます。

lpstrFilterメンバは、ダイアログ上に表示するファイルの種類の指定です。
ダイアログ上にあるドロップダウンリストから選択することで、ファイル表示をフィルタリングできます。
これはNULL文字を区切り文字とする特殊な文字列を指定します。
例えば「テキストファイル」「HTMLファイル」「全てのファイル」を選択したい場合は以下の文字列を指定します。


ofn.lpstrFilter =
	L"テキストファイル (*.txt)\0*.txt\0"
	L"HTMLファイル (*.htm;*.html)\0*.htm;*.html\0"
	L"全てのファイル (*.*)\0*.*\0";

NULL文字の左側がファイルの種類を表す文字列、右側がそのファイルを示すワイルドカード文字列です。
これらはペアで指定する必要があります。
例えばテキストファイルならば「*.txt」を指定することで、拡張子「.txt」を持つ全てのファイルが指定されます。
(ワイルドカードについてはファイルとフォルダの検索#ワイルドカードを参照)
HTMLファイルは二種類の拡張子が存在します。
この場合はセミコロン(;)で区切ることで同時に複数の種類のワイルドカード文字列を指定できます。
(スペースなどは含めないようにしてください)
「*.*」は拡張子を持つすべてのファイルが対象になります。

なお、上の例ではファイル種類ごとに改行で文字列を分割してますが、連続して記述しても同じです。
(C言語では文字列リテラルを連続して記述するとひとつの文字列リテラルとして扱われる)

必要なメンバをセットしたら、GetOpenFileName関数にOPENFILENAME構造体変数のアドレスを渡して実行します。

複数のファイルを選択

ダイアログ上で複数のファイルを選択可能にするには、FlagsメンバにOFN_ALLOWMULTISELECTフラグを含めます。
ただしこのフラグのみだと古いタイプのダイアログ形式になってしまうので、同時にOFN_EXPLORERフラグもセットします。


OPENFILENAME ofn = { 0 };
ofn.Flags = OFN_ALLOWMULTISELECT | OFN_EXPLORER;

ダイアログ上ではCtrlキーやShiftキー、マウスドラッグなどで複数のファイルを選択可能になります。

取得したパスはlpstrFileメンバに格納されるのですが、得られる文字列は「ディレクトリ名\0ファイル名1\0ファイル名2\0\0」というNULL区切り形式の文字列になります。
例えば「C:\test」フォルダ内にある「123.txt」と「456.html」という二つのファイルを選択すると、


"C:\test\0123.txt\0456.html\0\0"

という文字列になります。
(終端はダブルNULL)
そのまま文字列として使用すると最初のディレクトリ名までしか有効な文字列にならないので注意してください。

ファイルをひとつだけ選択した場合は通常通りのファイルパスが得られます。

GetSaveFileName関数

GetOpenFileName関数はファイルを「開く」ためのダイアログですが、指定の場所にファイルを書き込む場合はGetSaveFileName関数を使用します。

BOOL GetSaveFileNameW(
 LPOPENFILENAMEW unnamedParam1
);
名前を付けてファイルを保存するダイアログを作成する。
ユーザーが「OK」ボタンを選択しダイアログを閉じると戻り値はゼロ以外になる。
その他の方法で閉じるかエラーが発生した場合はゼロが返される。

ダイアログの外観やボタンなどが若干異なりますが、基本的にGetOpenFileName関数と同じです。
使用する構造体も同じですが、一部のフラグの動作が異なります。

どちらのダイアログボックスも選択したファイルのパスを得るもので、そのファイルにどのような処理を行うかはプログラマの自由です。