1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

C#定石 - フォルダ選択 - Drag & Drop とフォルダ選択ダイアログ

Last updated at Posted at 2025-02-02

はじめに

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 からの利用
      • プロジェクトファイル(.csproj)を開き、UseWindowsForms を追加
        WPF-Windows.Forms-02.png
  • 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 の場合

FolderSelect-01.png

Windows Forms - .NET 8(AutoUpgradeEnabled = true)の場合

FolderSelect-02.png

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;
}

FolderSelect-03.png

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;
  }
}

FolderSelect-04.png

フォルダ選択イベントハンドラ

Drag & Drop 可能なテキストボックス、フォルダ選択ダイアログ表示ボタンのペアで、フォルダ選択する UI を良く利用していました。
このようなペアが複数存在するケースでは、イベントハンドラを共有させると効率的です。

FolderSelect.png

テキストボックスとフォルダ選択ダイアログ表示ボタンとして、下記をデザイナーで配置した場合のサンプルコードを、いくつかのパターンで以降に記載します。

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;
    }
  }
}
1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?