はじめに
C# ソフト開発時に、決まり事として実施していた内容を記載します。
変更履歴
- 2025/02/03 OpenFolderDialog - InitialDirectory プロパティを見落としていたことを、Qiitaコメントで指摘頂いたので修正しました
記載内容
本記事では、下記2つのセクションにわけて、情報を記載することとします。
- フォルダ選択ダイアログの選択肢
- フォルダ選択イベントハンドラ
「フォルダ選択イベントハンドラ」が本記事の主題です。
「フォルダ選択イベントハンドラ」サンプルコードを記載する上で、「フォルダ選択ダイアログの選択肢」も併載しています。
同一フォーマットでファイル選択をまとめた C#定石 - ファイル選択 - Drag & Drop とファイル選択ダイアログ も存在しています。
テスト環境
ここに記載した情報/ソースコードは、Visual Studio Community 2022 を利用した下記プロジェクトで生成したモジュールを Windows 11 24H2 で動作確認しています。
- Windows Forms - .NET Framework 4.8
- Windows Forms - .NET 8
- WPF - .NET Framework 4.8
- WPF - .NET 8
フォルダ選択ダイアログの選択肢
フォルダ選択ダイアログの選択肢として下記が存在します。
- System.Windows.Forms.FolderBrowserDialog
- Windows Forms - .NET Framework / .NET で利用可能
- .NET 5 以降で UI 改善(.NET Framework は対象外)
- AutoUpgradeEnabledプロパティ false で、以前の UI 利用
- WPF - .NETFramewok 4.8 からの利用
- Visual Studio のソリューション エクスプローラーでプロジェクトを選択し、右クリックして「参照の追加」を選択
- 「アセンブリ」タブに移動、「フレームワーク」を選択、「System.Windows.Forms」をチェック
- WPF - .NET 8 からの利用
- Microsoft.Win32.OpenFolderDialog
- WPF - .NET 8 以降で利用可能(WPF - .NET Framework は利用不可)
- Windows Forms から、わざわざ理由するメリットはない
- Microsoft.WindowsAPICodePack.Dialogs.CommonOpenFileDialog
- NuGet Gallery | Microsoft-WindowsAPICodePack-Shell 導入が必要
- Microsoft-WindowsAPICodePack-Shell は、Windows API CodePack の一部で、Windows シェル機能を、.NET Framework / .NET で利用するためのライブラリ
- Dialogs - ダイアログ表示機能
- プロパティで、ファイル選択とフォルダ選択の切り替え可能
- .NET Framework 4.5.2 以降、.NET 6 以降で利用可能
初期値設定可能で、ファイル選択と同等の UI を提供したい場合、下記となります。
- .NET 5 以降の場合
- FolderBrowserDialog、もしくは、CommonOpenFileDialog
- 双方でフォルダ選択 UI が異なります
- FolderBrowserDialog では、上部パス表示 - 対象パスの親フォルダ
- CommonOpenFileDialog では、上部パス表示 - 対象パス
- .NET Framework の場合
- CommonOpenFileDialog
- WPF - .NET 8 以降の場合
- OpenFolderDialog、CommonOpenFileDialog、もしくは、FolderBrowserDialog
それぞれのサンプルコードと、ダイアログ表示形態を以降に記載します。
System.Windows.Forms.FolderBrowserDialog
// 初期選択値
string path = @"C:\Program Files (x86)\Microsoft Visual Studio";
// フォルダ選択ダイアログ
using (var dlg = new FolderBrowserDialog())
{
dlg.Description = "フォルダを選択してください";
//dlg.RootFolder = Environment.SpecialFolder.MyComputer;
dlg.ShowNewFolderButton = true;
// 初期選択値があれば設定
if (!string.IsNullOrEmpty(path))
{
if (Directory.Exists(path))
{
dlg.SelectedPath = path;
}
}
// フォルダ選択ダイアログ表示
if (dlg.ShowDialog() == DialogResult.OK)
{
// 選択値で更新
path = dlg.SelectedPath;
}
}
Windows Forms - .NET Framework 4.8 の場合
Windows Forms - .NET 8(AutoUpgradeEnabled = true)の場合
Microsoft.Win32.OpenFolderDialog
// 初期選択値
string path = @"C:\Program Files (x86)\Microsoft Visual Studio";
// フォルダ選択ダイアログ
var dlg = new Microsoft.Win32.OpenFolderDialog
{
Multiselect = false,
Title = "フォルダを選択してください"
};
// 初期選択値があれば設定
if (!string.IsNullOrEmpty(path))
{
if (Directory.Exists(path))
{
dlg.InitialDirectory = path;
dlg.FolderName = path;
}
}
// フォルダ選択ダイアログ表示
if (dlg.ShowDialog() == true)
{
// 選択値で更新
target.Text = dlg.FolderName;
}
Microsoft.WindowsAPICodePack.Dialogs.CommonOpenFileDialog
NuGet で、NuGet Gallery | Microsoft-WindowsAPICodePack-Shell を導入します。
PM> NuGet\Install-Package Microsoft-WindowsAPICodePack-Shell
CommonOpenFileDialog プロパティについては、下記、公開ソースで確認できます。
using Microsoft.WindowsAPICodePack.Dialogs;
// 初期選択値
string path = @"C:\Program Files (x86)\Microsoft Visual Studio";
// フォルダ選択ダイアログ
using (var dlg = new CommonOpenFileDialog())
{
dlg.IsFolderPicker = true; // true:フォルダ選択 false:ファイル選択
dlg.Multiselect = false;
dlg.Title = "フォルダを選択してください";
// 初期選択値があれば設定
if (!string.IsNullOrEmpty(path))
{
if (Directory.Exists(path))
{
dlg.InitialDirectory = path;
dlg.DefaultFileName = path;
}
}
// フォルダ選択ダイアログ表示
if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
{
// 選択値で更新
path = dlg.FileName;
}
}
フォルダ選択イベントハンドラ
Drag & Drop 可能なテキストボックス、フォルダ選択ダイアログ表示ボタンのペアで、フォルダ選択する UI を良く利用していました。
このようなペアが複数存在するケースでは、イベントハンドラを共有させると効率的です。
テキストボックスとフォルダ選択ダイアログ表示ボタンとして、下記をデザイナーで配置した場合のサンプルコードを、いくつかのパターンで以降に記載します。
TextBox txtFolder1 - Drag & Drop 可能なテキストボックス
Button btnFolder1 - txtFolder1 のフォルダ選択ダイアログ表示ボタン
TextBox txtFolder2 - Drag & Drop 可能なテキストボックス
Button btnFolder2 - txtFolder2 のフォルダ選択ダイアログ表示ボタン
Drag & Drop 操作に関して、Windows Forms と WPF では、イベント / ソースコード記載内容に相違があるので、注意してくだい。
フォルダパスが格納されているテキストボックスと、フォルダ選択ダイアログ表示ボタンの連動は、カスタムコントロール化が正規の手順ですが、、、
フォルダ選択ダイアログ表示ボタン - Click イベントハンドラ「OneFolder_Selection」引数に、対象となるテキストボックスを追加して、連動させる手法を選択しています。
前述「フォルダ選択ダイアログ選択肢」で記載されているフォルダ選択ダイアログと、フレームワークの組み合わせとなりますが、全てのパターンを網羅した記載はしません。
Windows Forms - .NET 8 + FolderBrowserDialog
txtFolder1.AllowDrop = true;
txtFolder1.DragEnter += OneFolder_DragEnter;
txtFolder1.DragDrop += OneFolder_DragDrop;
btnFolder1.Click += (sender, e) => OneFolder_Selection(sender, e, txtFolder1);
txtFolder2.AllowDrop = true;
txtFolder2.DragEnter += OneFolder_DragEnter;
txtFolder2.DragDrop += OneFolder_DragDrop;
btnFolder2.Click += (sender, e) => OneFolder_Selection(sender, e, txtFolder2);
// Drag & Drop - 対象:フォルダ単一
private void OneFolder_DragEnter(object? sender, DragEventArgs e)
{
// 1つのフォルダがドラッグされている場合のみ対象
if (e != null)
{
if (e.Data?.GetDataPresent(DataFormats.FileDrop) == true)
{
var paths = e.Data?.GetData(DataFormats.FileDrop) as string[];
if (paths?.Length == 1)
{
if (Directory.Exists(paths[0]))
{
e.Effect = DragDropEffects.Copy;
}
}
}
}
}
private void OneFolder_DragDrop(object? sender, DragEventArgs e)
{
var paths = e?.Data?.GetData(DataFormats.FileDrop) as string[];
if (paths?.Length == 1)
{
var target = sender as TextBox;
if (target != null)
{
target.Text = paths[0];
}
}
}
// フォルダ選択ダイアログ表示 - テキストボックス反映
private void OneFolder_Selection(object? sender, EventArgs e, TextBox target)
{
// 初期選択値
string path = target.Text.Trim();
// フォルダ選択ダイアログ
using (var dlg = new FolderBrowserDialog())
{
dlg.Description = "フォルダを選択してください";
dlg.ShowNewFolderButton = true;
// 初期選択値があれば設定
if (!string.IsNullOrEmpty(path))
{
if (Directory.Exists(path))
{
dlg.SelectedPath = path;
}
}
// フォルダ選択ダイアログ表示
if (dlg.ShowDialog() == DialogResult.OK)
{
// 選択値で更新
target.Text = dlg.SelectedPath;
}
}
}
WPF - .NET 8 + OpenFolderDialog
txtFolder1.AllowDrop = true;
txtFolder1.PreviewDragOver += OneFolder_PreviewDragOver;
txtFolder1.Drop += OneFolder_Drop;
btnFolder1.Click += (sender, e) => OneFolder_Selection(sender, e, txtFolder1);
txtFolder2.AllowDrop = true;
txtFolder2.PreviewDragOver += OneFolder_PreviewDragOver;
txtFolder2.Drop += OneFolder_Drop;
btnFolder2.Click += (sender, e) => OneFolder_Selection(sender, e, txtFolder2);
// Drag & Drop - 対象:フォルダ単一
private void OneFolder_PreviewDragOver(object sender, DragEventArgs e)
{
// 1つのフォルダがドラッグされている場合のみ対象
if (e != null)
{
e.Effects = DragDropEffects.None;
if (e.Data?.GetDataPresent(DataFormats.FileDrop) == true)
{
var paths = e.Data?.GetData(DataFormats.FileDrop) as string[];
if (paths?.Length == 1)
{
if (Directory.Exists(paths[0]))
{
e.Effects = DragDropEffects.Copy;
}
}
}
e.Handled = true;
}
}
private void OneFolder_Drop(object sender, DragEventArgs e)
{
var paths = e?.Data?.GetData(DataFormats.FileDrop) as string[];
if (paths?.Length == 1)
{
var target = sender as TextBox;
if (target != null)
{
target.Text = paths[0];
}
}
}
// フォルダ選択ダイアログ表示 - テキストボックス反映
private void OneFolder_Selection(object? sender, EventArgs e, TextBox target)
{
// 初期選択値
string path = target.Text.Trim();
// フォルダ選択ダイアログ
var dlg = new Microsoft.Win32.OpenFolderDialog
{
Multiselect = false,
Title = "フォルダを選択してください"
};
// 初期選択値があれば設定
if (!string.IsNullOrEmpty(path))
{
if (Directory.Exists(path))
{
dlg.InitialDirectory = path;
dlg.FolderName = path;
}
}
// フォルダ選択ダイアログ表示
if (dlg.ShowDialog() == true)
{
// 選択値で更新
target.Text = dlg.FolderName;
}
}
WPF - .NET Framework 4.8 + CommonOpenFileDialog
using Microsoft.WindowsAPICodePack.Dialogs;
txtFolder1.AllowDrop = true;
txtFolder1.PreviewDragOver += OneFolder_PreviewDragOver;
txtFolder1.Drop += OneFolder_Drop;
btnFolder1.Click += (sender, e) => OneFolder_Selection(sender, e, txtFolder1);
txtFolder2.AllowDrop = true;
txtFolder2.PreviewDragOver += OneFolder_PreviewDragOver;
txtFolder2.Drop += OneFolder_Drop;
btnFolder2.Click += (sender, e) => OneFolder_Selection(sender, e, txtFolder2);
// Drag & Drop - 対象:フォルダ単一
private void OneFolder_PreviewDragOver(object sender, DragEventArgs e)
{
// 1つのフォルダがドラッグされている場合のみ対象
if (e != null)
{
e.Effects = DragDropEffects.None;
if (e.Data?.GetDataPresent(DataFormats.FileDrop) == true)
{
var paths = e.Data?.GetData(DataFormats.FileDrop) as string[];
if (paths?.Length == 1)
{
if (Directory.Exists(paths[0]))
{
e.Effects = DragDropEffects.Copy;
}
}
}
e.Handled = true;
}
}
private void OneFolder_Drop(object sender, DragEventArgs e)
{
var paths = e?.Data?.GetData(DataFormats.FileDrop) as string[];
if (paths?.Length == 1)
{
var target = sender as TextBox;
if (target != null)
{
target.Text = paths[0];
}
}
}
// フォルダ選択ダイアログ表示 - テキストボックス反映
private void OneFolder_Selection(object sender, EventArgs e, TextBox target)
{
// 初期選択値
string path = target.Text.Trim();
// フォルダ選択ダイアログ
using (var dlg = new CommonOpenFileDialog())
{
dlg.IsFolderPicker = true; // true:フォルダ選択 false:ファイル選択
dlg.Multiselect = false;
dlg.Title = "フォルダを選択してください";
// 初期選択値があれば設定
if (!string.IsNullOrEmpty(path))
{
if (Directory.Exists(path))
{
var info = new FileInfo(path);
dlg.InitialDirectory = path;
dlg.DefaultFileName = info.Name;
}
}
// フォルダ選択ダイアログ表示
if (dlg.ShowDialog() == CommonFileDialogResult.Ok)
{
// 選択値で更新
path = dlg.FileName;
}
}
}