🧚

C# で IFileDialog(ファイルを開くダイアログなど)を使う

に公開

はじめに

C# で Win32 API の IFileDialog(ファイルを開く、フォルダーを開く、名前を付けて保存ダイアログ)を使う方法のまとめです。

各種 UI フレームワークには予め標準ダイアログが用意されているため、通常は Win32 API よりもそちらを使う方が簡単です。

フレームワーク ファイルを開くダイアログ
WinForms(フォーム) OpenFileDialog
WPF OpenFileDialog
WinUI 3(Windows App SDK) FileOpenPicker

ただ、WinUI 3 のダイアログは他と比べて機能が少ないため(例:フィルターインデックスを指定できない)、フル機能を使いたい場合は Win32 API を使う必要があります。

サンプルプログラムは GitHub に上げてあります。

名前を付けて保存

ファイルを開くダイアログより簡単な、名前を付けて保存ダイアログについて先に見ていきます。

ダイアログの表示

名前を付けて保存ダイアログは IFileDialog インターフェースを持っており、COM コンポーネントとして作成します。

名前を付けて保存ダイアログを表示するだけなら、わずか 2 行で簡単です。

PInvoke.CoCreateInstance(typeof(FileSaveDialog).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out IFileDialog* saveDialog);
HRESULT result = saveDialog->Show(HWND.Null);

結果の取得

ユーザーがキャンセルボタンをクリックした場合、Show() の返値が Failed になります。

if (result.Failed)
{
	return;
}

そうでない場合は保存パス名(ファイル名)を取得することになりますが、これが少々厄介です。というのも、直接ファイル名の取得ができず、IShellItem 経由になるためです。

IShellItem* shellResult = null;
saveDialog->GetResult(&shellResult);
String? path = ShellItemToPath(shellResult);

IShellItem からパス名(文字列)に変換する関数は以下です。

private unsafe String? ShellItemToPath(IShellItem* shellResult)
{
	PWSTR pathPwstr;
	shellResult->GetDisplayName(SIGDN.SIGDN_FILESYSPATH, &pathPwstr);
	return pathPwstr.ToString();
}

以上で、名前を付けて保存ダイアログの表示~保存パス名の取得ができました。

なお、各種 COM オブジェクト(I なんとか)や GetDisplayName() でアロケートされたメモリは解放が必要です。本稿に埋め込まれているコードは、本筋をわかりやすくするために解放や返値チェックを省略しているので注意してください。

解放や返値チェックについてはサンプルプログラムの MainPageViewModel.cs ShowFileSaveDialog() をご覧ください。

オプション

以下のように、より細かな制御も可能です。

GUID

名前を付けて保存ダイアログでは最後にアクセスしたフォルダーの位置などは自動的に記憶されますが、GUID を指定することにより、GUID ごとに記憶されるようになります。

例えば、プロジェクトを保存するダイアログと画像を保存するダイアログで、それぞれ別々の GUID を指定することにより、別々のフォルダーが記憶されるようになります。

Guid guid = new("FF2B2DC3-654E-4FA8-9730-3BBC47A32522");
saveDialog->SetClientGuid(guid);

FILEOPENDIALOGOPTIONS

FILEOPENDIALOGOPTIONS フラグを指定することでダイアログの挙動を制御できます。デフォルトでは以下が設定されています。

フラグ 意味
FOS_OVERWRITEPROMPT 上書き時に確認します。
FOS_NOCHANGEDIR[2] 作業ディレクトリを変更しません。
FOS_PATHMUSTEXIST フォルダーは存在していなければなりません。
FOS_NOREADONLYRETURN 読み取り専用アイテムは返しません。

多くの場合デフォルトのままで困らないかと思いますので、明示的に設定する必要はありません。

FILEOPENDIALOGOPTIONS options = FILEOPENDIALOGOPTIONS.FOS_NOCHANGEDIR | FILEOPENDIALOGOPTIONS.FOS_PATHMUSTEXIST
	| FILEOPENDIALOGOPTIONS.FOS_OVERWRITEPROMPT | FILEOPENDIALOGOPTIONS.FOS_NOREADONLYRETURN;
saveDialog->SetOptions(options);

拡張子フィルター

拡張子(に限りませんが)のフィルターは COMDLG_FILTERSPEC 構造体で指定します。

COMDLG_FILTERSPEC メンバー 意味
pszName フィルターの説明。 "JPEG 画像"
pszSpec フィルターパターン。
通常はワイルドカードと拡張子。
; で区切って複数指定可能。
"*.jpg;*.jpeg"

nint specs = Marshal.AllocCoTaskMem(Marshal.SizeOf<COMDLG_FILTERSPEC>());
fixed (Char* filterNamePtr = "JPEG 画像", filterSpecPtr = "*.jpg;*.jpeg")
{
	COMDLG_FILTERSPEC spec = new() { pszName = filterNamePtr, pszSpec = filterSpecPtr };
	Marshal.StructureToPtr(spec, specs, false);
	saveDialog->SetFileTypes(1, (COMDLG_FILTERSPEC*)specs);
}

配列で指定することにより複数のフィルターを指定できます。

ただ、アンマネージドメモリイメージを使う必要があるため、任意の配列を処理できるコードを書くとなると、解放も含めて少々煩雑にはなります。

サンプルプログラムでは "JPEG 画像|*.jpg;*.jpeg|PNG 画像|*.png" といった単一文字列で複数のフィルターを指定できるようにしています。

拡張子フィルターの選択

複数のフィルターを指定した場合、何番目のフィルターを選択状態にするかは SetFileTypeIndex() で指定できます。

saveDialog->SetFileTypeIndex(1);

ファイルを開く

ファイルを開くダイアログの使い方は、基本的には名前を付けて保存ダイアログと同様です。

ダイアログの表示~結果の取得

ファイルを開くダイアログは IFileOpenDialog インターフェースを持っており、COM コンポーネントとして作成します。

PInvoke.CoCreateInstance(typeof(FileOpenDialog).GUID, null, CLSCTX.CLSCTX_INPROC_SERVER, out IFileOpenDialog* openDialog);

この作成部分が名前を付けて保存ダイアログと異なるだけで、他は名前を付けて保存ダイアログと基本的に同様の使い方です。

オプション

GUID

GUID については名前を付けて保存ダイアログと同様です。

FILEOPENDIALOGOPTIONS

ダイアログの挙動を制御する FILEOPENDIALOGOPTIONS フラグのデフォルト値は、ファイルを開くダイアログでは以下が設定されています。

フラグ 意味
FOS_NOCHANGEDIR[2:1] 作業ディレクトリを変更しません。
FOS_PATHMUSTEXIST フォルダーは存在していなければなりません。
FOS_FILEMUSTEXIST ファイルは存在していなければなりません。

ファイルを開くダイアログでは FOS_ALLOWMULTISELECT フラグを指定すると複数ファイルを選択できるようになります。この場合、結果の取得プログラムを変更する必要があります(後述)。

拡張子フィルター

フィルターの使い方は名前を付けて保存ダイアログと同様です。

ただし、運用上、一般的には「すべてのファイル」をフィルターとして用意することが多いです(名前を付けて保存ダイアログには「すべてのファイル」は用意しません)。

「すべてのファイル」をフィルターの先頭にするか末尾にするかは流派が分かれます。本家(?)マイクロソフトでも流派が分かれているので、どちらにすればいいのかは悩みます。

  • 先頭派
    • Excel 365 (2508)
    • Word 365 (2508)
    • PowerPoint 365 (2508)
    • Firefox (145)
    • 秀丸 (9.50)
  • 末尾派
    • Visual Studio 2026 (18.0.1)
    • メモ帳 (11.2508.38.0)
  • 選択肢が 1 つのため流派不明
    • Visual Studio Code (1.106.2)
    • Edge (142)
    • Chrome (142)

拡張子フィルターの選択

フィルターの選択状態については名前を付けて保存ダイアログと同様です。

「すべてのファイル」を選択状態にするか否かについては、これまた流派が分かれるようです。

複数結果の取得

FILEOPENDIALOGOPTIONS.FOS_ALLOWMULTISELECT を指定すると複数ファイルを選択できるようになりますが、名前を付けて保存ダイアログと同様の結果取得コードでは先頭の 1 ファイルのみしか取得できません。

選択されたすべてのファイルを取得するには、以下のようにします。

shellItemArray->GetCount(out UInt32 numPathes);
String[] pathes = new String[numPathes];
for (UInt32 i = 0; i < numPathes; i++)
{
	IShellItem* iShellResult;
	shellItemArray->GetItemAt(i, &iShellResult);
	String? path = ShellItemToPath(iShellResult);
	pathes[i] = path;
}

フォルダーを開く

フォルダーを開くダイアログは、実はファイルを開くダイアログそのものです。

FILEOPENDIALOGOPTIONS.FOS_PICKFOLDERS フラグを指定することでフォルダーを開くダイアログになります。

コードはすべてファイルを開くダイアログと同じです。

サンプルプログラム

サンプルプログラム(GitHub に上げてあります)を実行すると、各種ダイアログの動作を確認できます。

比較用に WinUI 3 のダイアログも入れてあります。WinUI 3 のダイアログは新版と旧版の 2 種類あります。

確認環境

項目 環境
OS Windows 11 Pro 25H2
Visual Studio 2026 18.0.1
.NET 10.0
Template Studio for WinUI 5.5
WinUIEx 2.9.0
Windows App SDK 1.8.251106002 (1.8.3)

参考リンク

主な改訂履歴

  • 2025/11/23 初版。
  • 2025/11/27 「脚注」を作成。

脚注
  1. 確認環境で確認しました。 ↩︎ ↩︎ ↩︎

  2. ドキュメントには規定値と明記されていないのですが、実際には規定で指定されているようです。 ↩︎ ↩︎

Discussion