hozugawa.net · A neutral informational page

GetOpenFileNameでファイル選択ダイアログを実装する

Windowsアプリケーションでユーザーにファイルを選択させる場合、Win32 APIのGetOpenFileNameは長く利用されてきた標準的な方法です。既存のWindowsデスクトップアプリケーションに組み込みやすく、ファイル名の入力、拡張子による絞り込み、既存ファイルの確認などを一つのダイアログで扱えます。

この関数は、ダイアログの設定をOPENFILENAME構造体に格納してから呼び出します。選択結果は呼び出し側が用意した文字バッファへ書き込まれるため、構造体の初期化、バッファサイズ、文字コードの扱いを正しく設定することが重要です。

.NETやMFCを使わず、純粋なWindows APIで実装したい場合にも適しています。ここでは、単一ファイルの選択を基本に、ファイルフィルター、Unicode対応、キャンセル時の処理、複数選択の注意点まで順に説明します。

OPENFILENAME構造体の基本

GetOpenFileNameは、必要な設定を引数として個別に受け取るのではなく、OPENFILENAME構造体のポインターを受け取ります。最低限、lStructSize、hwndOwner、lpstrFilter、lpstrFile、nMaxFileを設定します。

hwndOwnerには親ウィンドウのハンドルを指定します。メインウィンドウを所有者にすると、ファイル選択ダイアログが親ウィンドウの前面に表示され、モーダルダイアログとして自然に動作します。所有者が不要な場合はNULLでも実行できます。

OPENFILENAMEW ofn{};
wchar_t fileName[MAX_PATH] = L"";

ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = fileName;
ofn.nMaxFile = static_cast<DWORD>(std::size(fileName));
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

構造体はゼロ初期化しておくと、使用しないメンバーが不定値になる問題を避けられます。nMaxFileには文字数を指定し、バイト数を設定しない点にも注意が必要です。

ファイルフィルターを設定する

拡張子の一覧をダイアログに表示するには、lpstrFilterへフィルター文字列を指定します。この文字列は「表示名」と「検索パターン」の組み合わせで構成され、各要素をヌル文字で区切り、最後に二重ヌル文字を置きます。

static const wchar_t filter[] =
    L"テキストファイル (*.txt)\0*.txt\0"
    L"すべてのファイル (*.*)\0*.*\0\0";

ofn.lpstrFilter = filter;
ofn.nFilterIndex = 1;

nFilterIndexは最初に選択されるフィルター番号です。番号は1から始まります。ファイルの種類を限定したい場合でも、利用者が別形式を選べるように「すべてのファイル」を用意しておくと操作性が向上します。フィルターの区切りを通常の改行や単一の終端文字にすると、表示が崩れたり読み取り範囲が不定になったりします。

拡張子の自動補完を制御するにはlpstrDefExtを設定します。たとえばtxtを指定すると、利用者が拡張子を省略した場合に補われます。ファイルの選択ダイアログに関する補足資料を探す際は、Windows API資料のような技術情報サイトも参照できます。

ダイアログを表示して結果を受け取る

設定を終えたらGetOpenFileNameW(&ofn)を呼び出します。戻り値がTRUEなら選択に成功し、fileNameに完全なパスが格納されます。FALSEの場合は、利用者がキャンセルしたケースと、API内部でエラーが発生したケースを区別します。

if (GetOpenFileNameW(&ofn)) {
    // fileName に選択されたファイルのパスが入る
    MessageBoxW(hwnd, fileName, L"選択結果", MB_OK);
} else {
    DWORD errorCode = CommDlgExtendedError();

    if (errorCode != 0) {
        // コモンダイアログのエラーを記録する
    }
    // errorCode が 0 なら通常はキャンセル
}

CommDlgExtendedErrorが0を返す場合、ユーザーがキャンセルした可能性が高く、通常はエラーメッセージを表示しません。0以外なら、バッファ不足や構造体設定の不備などを調査します。選択後にファイルを開く処理を行う場合も、まずパスが返されたことを確認し、その後でCreateFileや標準C++のファイルストリームを使います。

Unicodeとバッファサイズの扱い

新規のWindows APIコードでは、GetOpenFileNameWを使ってUnicode版を明示する設計が安全です。OPENFILENAMEWとwchar_t配列を組み合わせれば、日本語のファイル名やユーザー名を扱えます。GetOpenFileNameAは現在のコードページに依存するため、文字化けが起きる可能性があります。

MAX_PATHは一般的なパスには便利ですが、長いパスを扱うアプリケーションでは十分でない場合があります。OPENFILENAMEの仕様やWindowsの長いパス対応を確認し、必要ならより大きなバッファを確保します。ただし、バッファを大きくするだけで長いパスが必ず利用できるわけではなく、アプリケーション側やOS側の対応も関係します。

std::vector<wchar_t> buffer(32768, L'\0');

OPENFILENAMEW ofn{};
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hwnd;
ofn.lpstrFile = buffer.data();
ofn.nMaxFile = static_cast<DWORD>(buffer.size());
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

if (GetOpenFileNameW(&ofn)) {
    std::wstring selectedPath(buffer.data());
}

nMaxFileの型はDWORDなので、極端に大きなコンテナーを渡す場合は値の変換にも注意します。また、パスを取得した後に別の処理へ渡す際は、同じUnicode系APIを選ぶと変換処理を減らせます。

複数ファイル選択と実装上の注意

複数ファイルを選択可能にするにはOFN_ALLOWMULTISELECTを指定します。この場合、選択結果の形式が単一選択時と変わります。複数選択時は、バッファの先頭にフォルダーのパス、その後ろにファイル名がヌル文字区切りで並び、最後に二重ヌル文字が置かれます。

ofn.Flags = OFN_EXPLORER |
            OFN_ALLOWMULTISELECT |
            OFN_FILEMUSTEXIST |
            OFN_PATHMUSTEXIST;

OFN_EXPLORERを指定すると、現在のエクスプローラー形式に対応した結果を扱いやすくなります。複数選択ではMAX_PATH程度のバッファでは不足しやすいため、十分な領域を確保し、終端文字を頼りに順番に解析します。単一選択だけなら、戻り値の文字列をそのまま完全パスとして利用できます。

また、GetOpenFileNameは古いAPIであり、Windowsの新しいUIや高度な非同期処理が必要なアプリケーションでは、ファイルオープンピッカーなど別のAPIが適する場合もあります。ただし、Win32の既存コードへ小さく導入する用途では、依存関係が少なく、動作実績のある選択肢です。エラー時には戻り値だけで判断せず、CommDlgExtendedErrorの値をログに残すと保守が容易になります。

実装では、構造体をゼロ初期化し、Unicode版の関数を選び、十分な文字バッファを用意することが基本になります。単一ファイルならOFN_FILEMUSTEXISTとOFN_PATHMUSTEXISTを組み合わせ、成功時だけ返されたパスを後続処理へ渡せば、安全なファイル選択処理を構築できます。