C#ドラッグアンドドロップ完全ガイド|ファイルパス取得からDragEnter・DragDropの実装までサンプルコードで解説

はじめに

C#でデスクトップアプリを作成していると、「ファイルを画面にドラッグアンドドロップして、そのファイルパスを取得したい」という場面がよくあります。

たとえば、画像ビューア、CSV読み込みツール、ログ解析アプリ、ファイル変換ツールなどでは、ユーザーにファイル選択ダイアログを開かせるよりも、対象ファイルを直接ドロップできた方が直感的で使いやすくなります。

C#のドラッグアンドドロップ処理は、基本的には次の2つのイベントを理解すれば実装できます。

C#
DragEnter
DragDrop

DragEnterでは「ドロップを許可するかどうか」を判定し、DragDropでは「実際にドロップされたデータを受け取る」処理を行います。

この記事では、C#のWindows Formsを中心に、ファイルパス取得、複数ファイル対応、TextBox・ListBox・PictureBoxへの表示、フォルダ判定、拡張子チェック、WPFでの実装方法まで、サンプルコード付きで解説します。

1. C#のドラッグアンドドロップでできることと基本の流れ

1-1. 「C# ドラッグアンドドロップ」で検索するユーザーの目的

「C# ドラッグアンドドロップ」で調べている人の多くは、次のような実装をしたいケースが多いです。

・ファイルをフォームにドロップしてパスを取得したい
・TextBoxにファイルパスを表示したい
・ListBoxに複数ファイルを追加したい
・画像ファイルをPictureBoxに表示したい
・CSVやテキストファイルをドロップして読み込みたい
・フォルダをドロップして中身を処理したい
・Windows FormsまたはWPFで実装したい

C#のドラッグアンドドロップは、一見難しそうに見えますが、基本の流れはシンプルです。

1. ドロップを受け付けたいコントロールのAllowDropをtrueにする
2. DragEnterイベントでドロップ可能か判定する
3. DragDropイベントでドロップされたデータを取得する
4. 取得したファイルパスや文字列を画面表示・読み込み処理に使う

特にファイルを扱う場合は、DataFormats.FileDropを使うのが基本です。

1-2. ファイル・フォルダ・テキストをドロップする代表的な用途

C#のドラッグアンドドロップでは、ファイルだけでなく、フォルダやテキストも扱えます。

代表的な用途は次のとおりです。

ファイルをドロップする例
・画像ファイルを表示する
・CSVファイルを読み込む
・ログファイルを解析する
・PDFやExcelファイルのパスを取得する

フォルダをドロップする例
・フォルダ内のファイル一覧を取得する
・一括変換対象のフォルダを指定する
・バックアップ元フォルダを選択する

テキストをドロップする例
・URLをドロップして取得する
・メモ帳などから文字列をドロップする
・他アプリから文字情報を貼り付ける

ファイルやフォルダを扱う場合は、ドロップされたデータを文字列配列として取得できます。

C#
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

この配列の中に、ドロップされたファイルやフォルダのパスが入ります。

1-3. DragEnter・DragDropイベントの役割

C#のドラッグアンドドロップで重要なのが、DragEnterイベントとDragDropイベントです。

DragEnterは、マウスでドラッグ中のデータがコントロールの上に入ったときに発生します。

ここでは、ドロップを許可するかどうかを判定します。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

DragDropは、実際にマウスボタンを離してデータがドロップされたときに発生します。

ここでファイルパスなどを取得します。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
Console.WriteLine(file);
}
}

つまり、役割を分けると次のようになります。

DragEnter:ドロップできるデータか確認する
DragDrop :ドロップされたデータを受け取って処理する

1-4. ドラッグアンドドロップ処理の全体像

C#でファイルのドラッグアンドドロップを実装する基本形は次のとおりです。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

this.AllowDrop = true;
this.DragEnter += Form1_DragEnter;
this.DragDrop += Form1_DragDrop;
}

private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
MessageBox.Show(path);
}
}
}

このコードでは、フォーム全体に対してファイルをドロップできます。

ファイルをドロップすると、ドロップされたファイルパスがMessageBoxで表示されます。

2. C#でドラッグアンドドロップを実装する事前準備

2-1. Windows Formsで実装する場合の基本

Windows Formsでドラッグアンドドロップを実装する場合は、フォームまたはコントロールに対して設定を行います。

たとえば、次のようなコントロールにドロップ処理を追加できます。

・Form
・TextBox
・ListBox
・PictureBox
・Label
・Panel
・Button

基本的には、ドロップを受け付けたい対象に対して、次の3つを設定します。

・AllowDrop = true
・DragEnterイベント
・DragDropイベント

フォーム全体にドロップさせたい場合はthis.AllowDrop = true;を設定します。

TextBoxやListBoxなど特定のコントロールにドロップさせたい場合は、対象コントロールのAllowDroptrueにします。

C#
textBox1.AllowDrop = true;
listBox1.AllowDrop = true;
pictureBox1.AllowDrop = true;

2-2. AllowDropプロパティをtrueに設定する

C#でドラッグアンドドロップが動かない原因として非常に多いのが、AllowDropプロパティをtrueにしていないケースです。

AllowDropfalseのままだと、DragEnterDragDropイベントを用意していても、期待どおりにドロップできません。

コードで設定する場合は次のようにします。

C#
this.AllowDrop = true;

TextBoxにドロップしたい場合は次のようにします。

C#
textBox1.AllowDrop = true;

ListBoxにドロップしたい場合は次のようにします。

C#
listBox1.AllowDrop = true;

Visual Studioのデザイナーから設定する場合は、対象のフォームまたはコントロールを選択し、プロパティウィンドウでAllowDropTrueに変更します。

2-3. イベントハンドラーを追加する方法

イベントハンドラーは、コードで追加する方法と、Visual Studioのデザイナーから追加する方法があります。

コードで追加する場合は、コンストラクタで次のように記述します。

C#
public Form1()
{
InitializeComponent();

this.AllowDrop = true;
this.DragEnter += Form1_DragEnter;
this.DragDrop += Form1_DragDrop;
}

イベントメソッドは次のように作成します。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
}

Visual Studioのデザイナーから追加する場合は、対象コントロールを選択し、プロパティウィンドウの雷アイコンからDragEnterDragDropをダブルクリックします。

どちらの方法でも問題ありませんが、サンプルコードでは処理の流れが分かりやすいように、コードでイベントを登録する方法を中心に解説します。

2-4. サンプルで使用するフォーム構成

この記事のサンプルでは、主に次のようなフォーム構成を想定します。

Form1
├ TextBox textBox1
├ ListBox listBox1
├ PictureBox pictureBox1
├ Label label1
└ Panel panel1

すべてのコントロールを使う必要はありません。

まずはフォームにTextBoxまたはListBoxを1つ配置し、ファイルパスを表示するところから始めると理解しやすいです。

たとえば、TextBoxにファイルパスを表示するだけなら、最小構成は次のとおりです。

Form1
└ TextBox textBox1

複数ファイルを一覧表示したい場合は、ListBoxを使うと便利です。

Form1
└ ListBox listBox1

画像ファイルをドロップして表示したい場合は、PictureBoxを使用します。

Form1
└ PictureBox pictureBox1

3. ファイルパスを取得する最小サンプルコード

3-1. DataFormats.FileDropでファイル情報を取得する

C#でファイルのドラッグアンドドロップを扱う場合は、DataFormats.FileDropを使います。

DataFormats.FileDropは、ドロップされたデータがファイルまたはフォルダであるかを判定するときに使用します。

C#
e.Data.GetDataPresent(DataFormats.FileDrop)

このメソッドがtrueを返す場合、ドロップ対象にはファイルまたはフォルダの情報が含まれています。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

DragEnterでこの判定を行わないと、マウスカーソルが禁止マークのままになったり、DragDropが発生しなかったりすることがあります。

3-2. ドロップされたファイルパスを文字列配列で受け取る

ドロップされたファイルパスは、string[]として取得できます。

C#
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

1つのファイルだけをドロップした場合でも、戻り値は文字列配列です。

たとえば、最初のファイルだけ取得したい場合は次のように書けます。

C#
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files.Length > 0)
{
string filePath = files[0];
MessageBox.Show(filePath);
}

複数ファイルに対応する場合は、foreachで処理します。

C#
foreach (string file in files)
{
MessageBox.Show(file);
}

3-3. TextBoxやListBoxにファイルパスを表示する

TextBoxにドロップされたファイルパスを表示する最小サンプルは次のとおりです。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

textBox1.AllowDrop = true;
textBox1.DragEnter += TextBox1_DragEnter;
textBox1.DragDrop += TextBox1_DragDrop;
}

private void TextBox1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

private void TextBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files.Length > 0)
{
textBox1.Text = files[0];
}
}
}

複数ファイルをTextBoxに表示したい場合は、改行で連結すると見やすくなります。

C#
private void TextBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

textBox1.Text = string.Join(Environment.NewLine, files);
}

ListBoxに表示する場合は、次のようにします。

C#
private void ListBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}

3-4. 複数ファイルをドラッグアンドドロップする場合

C#のドラッグアンドドロップでは、複数ファイルをまとめて受け取ることができます。

複数ファイルを処理する基本コードは次のとおりです。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
Console.WriteLine(path);
}
}

ListBoxに追加する場合は、次のようにすると分かりやすいです。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

listBox1.Items.Clear();

foreach (string path in paths)
{
listBox1.Items.Add(path);
}
}

複数ファイルに対応する場合は、1件だけを想定したコードにしないことが重要です。

悪い例は次のようなコードです。

C#
string file = ((string[])e.Data.GetData(DataFormats.FileDrop))[0];

この書き方自体は動作しますが、複数ファイルを処理したい場合には不十分です。

複数ファイルを扱う可能性があるなら、最初からforeachで処理する形にしておくと拡張しやすくなります。

4. DragEnterイベントの実装方法

4-1. DragEnterイベントで行う判定処理

DragEnterイベントでは、ユーザーがドラッグしているデータを受け付けるかどうかを判定します。

ファイルのドロップだけを受け付けたい場合は、DataFormats.FileDropが含まれているかを確認します。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

ここでe.EffectDragDropEffects.Copyを設定すると、ドロップ可能な状態になります。

反対に、受け付けないデータであればDragDropEffects.Noneを設定します。

4-2. e.Data.GetDataPresentでファイル形式を確認する

e.Data.GetDataPresentは、ドラッグ中のデータが指定した形式を持っているかを確認するためのメソッドです。

ファイルの場合は次のように使います。

C#
e.Data.GetDataPresent(DataFormats.FileDrop)

テキストの場合は次のように判定できます。

C#
e.Data.GetDataPresent(DataFormats.Text)

ファイルとテキストの両方を受け付けたい場合は、次のように書けます。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop) ||
e.Data.GetDataPresent(DataFormats.Text))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

ただし、ファイル処理とテキスト処理では取得方法が異なるため、DragDrop側でも形式に応じて処理を分ける必要があります。

4-3. e.EffectにDragDropEffects.Copyを設定する

ドロップを許可する場合は、e.EffectDragDropEffects.Copyを設定します。

C#
e.Effect = DragDropEffects.Copy;

これにより、ユーザーの画面では「コピーとしてドロップできる」ことを示すカーソル表示になります。

ファイルをドラッグアンドドロップしてパスを取得するだけなら、基本的にはCopyを使えば問題ありません。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
}

Moveを指定することもできますが、通常のファイルパス取得では実際にファイルを移動するわけではないため、Copyの方が自然です。

4-4. ドロップ不可の場合にNoneを設定する

受け付けないデータの場合は、DragDropEffects.Noneを設定します。

C#
e.Effect = DragDropEffects.None;

たとえば、ファイル以外を受け付けない場合は次のようになります。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

拡張子を限定したい場合も、条件に合わなければNoneにします。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.None;
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

bool allCsv = files.All(file =>
Path.GetExtension(file).Equals(".csv", StringComparison.OrdinalIgnoreCase));

e.Effect = allCsv ? DragDropEffects.Copy : DragDropEffects.None;
}

この例では、ドロップ対象がすべてCSVファイルの場合だけドロップを許可しています。

4-5. カーソル表示が変わらないときの確認ポイント

ドラッグ中にカーソルが禁止マークのままで、ドロップできない場合は、次の点を確認してください。

・AllowDropがtrueになっているか
・DragEnterイベントが正しく登録されているか
・DragEnter内でe.Effectを設定しているか
・GetDataPresentの判定条件が間違っていないか
・管理者権限の違いがないか
・ドロップ対象のコントロールが別のコントロールに隠れていないか

特に多いのは、AllowDropの設定漏れと、e.Effectの設定漏れです。

次のようにDragEnter内で必ずe.Effectを設定しましょう。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

5. DragDropイベントの実装方法

5-1. DragDropイベントで実際にデータを受け取る

DragDropイベントは、ユーザーがファイルやデータを実際にドロップしたときに発生します。

ここでは、ドロップされたファイルパスを取得して、画面に表示したり、ファイルを読み込んだりします。

基本形は次のとおりです。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
Console.WriteLine(path);
}
}

DragEnterで判定している場合でも、DragDrop側でも念のためGetDataPresentを確認しておくと安全です。

5-2. ドロップされたファイルパスを取得するコード

ドロップされたファイルパスを取得してTextBoxに表示するコードは次のとおりです。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files.Length == 0)
{
return;
}

textBox1.Text = files[0];
}

複数ファイルを取得してListBoxに表示する場合は次のようにします。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

listBox1.Items.Clear();

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}

取得できるパスは、たとえば次のような文字列です。

C:\Users\user\Desktop\sample.txt
C:\Users\user\Pictures\image.png
C:\Work\Data\test.csv

5-3. ファイル名・拡張子・フォルダパスを分けて取得する

ドロップされたファイルパスから、ファイル名、拡張子、フォルダパスを取得するには、System.IO.Pathクラスを使います。

C#
string filePath = @"C:\Work\Data\sample.csv";

string fileName = Path.GetFileName(filePath);
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(filePath);
string extension = Path.GetExtension(filePath);
string directory = Path.GetDirectoryName(filePath);

ドラッグアンドドロップと組み合わせると、次のようになります。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
string fileName = Path.GetFileName(file);
string nameOnly = Path.GetFileNameWithoutExtension(file);
string extension = Path.GetExtension(file);
string folderPath = Path.GetDirectoryName(file);

listBox1.Items.Add($"ファイル名: {fileName}");
listBox1.Items.Add($"拡張子: {extension}");
listBox1.Items.Add($"フォルダ: {folderPath}");
listBox1.Items.Add("--------------------");
}
}

これにより、単にパスを表示するだけでなく、ファイルの種類に応じた処理をしやすくなります。

5-4. フォルダがドロップされた場合の判定

DataFormats.FileDropでは、ファイルだけでなくフォルダも取得できます。

ファイルかフォルダかを判定するには、File.ExistsDirectory.Existsを使います。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
if (File.Exists(path))
{
listBox1.Items.Add($"ファイル: {path}");
}
else if (Directory.Exists(path))
{
listBox1.Items.Add($"フォルダ: {path}");
}
else
{
listBox1.Items.Add($"存在しないパス: {path}");
}
}
}

フォルダがドロップされた場合に、その中のファイル一覧を取得したい場合は次のようにします。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
if (Directory.Exists(path))
{
string[] files = Directory.GetFiles(path);

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}
}
}

サブフォルダも含めて取得したい場合は、SearchOption.AllDirectoriesを指定します。

C#
string[] files = Directory.GetFiles(path, "*.*", SearchOption.AllDirectories);

ただし、サブフォルダを含めるとファイル数が多くなる可能性があるため、処理時間や例外処理に注意が必要です。

5-5. 例外処理を入れて安全に実装する

ドラッグアンドドロップでファイルを扱う場合、次のような理由で例外が発生することがあります。

・ファイルが存在しない
・アクセス権限がない
・ファイルが別プロセスで使用中
・フォルダ内の一部にアクセスできない
・パスが長すぎる
・想定外の形式のデータがドロップされた

そのため、実用的なアプリではtry-catchを使って安全に処理することが大切です。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
try
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
if (File.Exists(path))
{
listBox1.Items.Add($"ファイル: {path}");
}
else if (Directory.Exists(path))
{
listBox1.Items.Add($"フォルダ: {path}");
}
else
{
listBox1.Items.Add($"存在しないパス: {path}");
}
}
}
catch (Exception ex)
{
MessageBox.Show(
$"ドロップ処理中にエラーが発生しました。\n{ex.Message}",
"エラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}
}

ファイルを読み込む処理では、個別のファイルごとにtry-catchを入れると、1つのファイルで失敗しても他のファイルの処理を続けられます。

C#
foreach (string file in files)
{
try
{
string text = File.ReadAllText(file);
listBox1.Items.Add($"{file} を読み込みました");
}
catch (Exception ex)
{
listBox1.Items.Add($"{file} の読み込みに失敗: {ex.Message}");
}
}

6. コントロール別ドラッグアンドドロップ実装例

6-1. TextBoxにファイルパスを表示するサンプル

TextBoxにファイルパスを表示するサンプルです。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

textBox1.AllowDrop = true;
textBox1.Multiline = true;

textBox1.DragEnter += TextBox1_DragEnter;
textBox1.DragDrop += TextBox1_DragDrop;
}

private void TextBox1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void TextBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

textBox1.Text = string.Join(Environment.NewLine, files);
}
}

1つのファイルだけでなく、複数ファイルがドロップされた場合も、改行区切りで表示されます。

ファイルパス入力欄として使う場合は、TextBoxがシンプルで扱いやすいです。

6-2. ListBoxに複数ファイルを追加するサンプル

複数ファイルを一覧表示したい場合は、ListBoxが便利です。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

listBox1.AllowDrop = true;

listBox1.DragEnter += ListBox1_DragEnter;
listBox1.DragDrop += ListBox1_DragDrop;
}

private void ListBox1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void ListBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}
}

同じファイルを重複して追加したくない場合は、次のようにチェックします。

C#
private void ListBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
if (!listBox1.Items.Contains(file))
{
listBox1.Items.Add(file);
}
}
}

6-3. PictureBoxに画像ファイルを表示するサンプル

PictureBoxに画像ファイルをドロップして表示するサンプルです。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

pictureBox1.AllowDrop = true;
pictureBox1.SizeMode = PictureBoxSizeMode.Zoom;

pictureBox1.DragEnter += PictureBox1_DragEnter;
pictureBox1.DragDrop += PictureBox1_DragDrop;
}

private void PictureBox1_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.None;
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

string extension = Path.GetExtension(files[0]).ToLower();

if (extension == ".jpg" || extension == ".jpeg" ||
extension == ".png" || extension == ".bmp" ||
extension == ".gif")
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

private void PictureBox1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files.Length == 0)
{
return;
}

string imagePath = files[0];

try
{
using (FileStream fs = new FileStream(imagePath, FileMode.Open, FileAccess.Read))
{
Image img = Image.FromStream(fs);

pictureBox1.Image?.Dispose();
pictureBox1.Image = new Bitmap(img);
}
}
catch (Exception ex)
{
MessageBox.Show($"画像を読み込めませんでした。\n{ex.Message}");
}
}
}

Image.FromFileを使う方法もありますが、ファイルがロックされる場合があります。

そのため、実用的にはFileStreamで読み込み、BitmapとしてコピーしてからPictureBoxに設定する方法が扱いやすいです。

6-4. LabelやPanelにドロップ領域を作るサンプル

ユーザーに「ここにファイルをドロップしてください」と分かりやすく見せたい場合は、LabelやPanelをドロップ領域として使うと便利です。

Labelを使う例です。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

label1.Text = "ここにファイルをドロップしてください";
label1.TextAlign = ContentAlignment.MiddleCenter;
label1.BorderStyle = BorderStyle.FixedSingle;
label1.AllowDrop = true;

label1.DragEnter += Label1_DragEnter;
label1.DragDrop += Label1_DragDrop;
}

private void Label1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void Label1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

label1.Text = string.Join(Environment.NewLine, files);
}
}

Panelを使う場合も基本は同じです。

C#
panel1.AllowDrop = true;
panel1.DragEnter += Panel1_DragEnter;
panel1.DragDrop += Panel1_DragDrop;

Panel内にLabelやPictureBoxを配置して、見た目を整えることもできます。

ただし、Panelの上に別のコントロールが重なっている場合、実際にはその子コントロール側にドラッグイベントが発生することがあります。

その場合は、子コントロールにもAllowDropやイベントを設定してください。

6-5. フォーム全体にドラッグアンドドロップを設定する方法

フォーム全体でドラッグアンドドロップを受け付けたい場合は、this.AllowDrop = true;を設定します。

C#
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();

this.AllowDrop = true;
this.DragEnter += Form1_DragEnter;
this.DragDrop += Form1_DragDrop;
}

private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

listBox1.Items.Clear();

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}
}

ただし、フォーム上にTextBoxやPanelなどのコントロールが配置されている場合、マウス位置によってはフォームではなく、そのコントロールがドラッグイベントを受け取ります。

フォーム全体で確実に受け付けたい場合は、主要な子コントロールにも同じドラッグアンドドロップ処理を設定する方法があります。

C#
private void SetDragDrop(Control control)
{
control.AllowDrop = true;
control.DragEnter += Common_DragEnter;
control.DragDrop += Common_DragDrop;

foreach (Control child in control.Controls)
{
SetDragDrop(child);
}
}

private void Common_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void Common_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}

コンストラクタで次のように呼び出します。

C#
public Form1()
{
InitializeComponent();

SetDragDrop(this);
}

7. 実用的なドラッグアンドドロップ処理の応用

7-1. 特定の拡張子だけ受け付ける方法

実用的なアプリでは、すべてのファイルを受け付けるのではなく、CSV、TXT、PNGなど特定の拡張子だけを許可したい場合があります。

CSVファイルだけを受け付ける例は次のとおりです。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.None;
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

bool isCsv = files.All(file =>
Path.GetExtension(file).Equals(".csv", StringComparison.OrdinalIgnoreCase));

e.Effect = isCsv ? DragDropEffects.Copy : DragDropEffects.None;
}

複数の拡張子を許可したい場合は、許可リストを作ると分かりやすくなります。

C#
private readonly string[] allowedExtensions = { ".csv", ".txt", ".log" };

private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.None;
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

bool allowed = files.All(file =>
allowedExtensions.Contains(
Path.GetExtension(file),
StringComparer.OrdinalIgnoreCase));

e.Effect = allowed ? DragDropEffects.Copy : DragDropEffects.None;
}

このようにすると、許可する拡張子を後から変更しやすくなります。

7-2. ファイルとフォルダを区別して処理する方法

ファイルとフォルダを区別して処理するには、File.ExistsDirectory.Existsを使います。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string path in paths)
{
if (File.Exists(path))
{
ProcessFile(path);
}
else if (Directory.Exists(path))
{
ProcessDirectory(path);
}
}
}

private void ProcessFile(string filePath)
{
listBox1.Items.Add($"ファイルを処理: {filePath}");
}

private void ProcessDirectory(string directoryPath)
{
listBox1.Items.Add($"フォルダを処理: {directoryPath}");
}

フォルダがドロップされたら、その中のファイルをまとめて処理することもできます。

C#
private void ProcessDirectory(string directoryPath)
{
string[] files = Directory.GetFiles(directoryPath, "*.txt");

foreach (string file in files)
{
ProcessFile(file);
}
}

この例では、フォルダ内の.txtファイルだけを処理しています。

7-3. ドロップされたファイルを読み込む方法

ファイルパスを取得した後は、File.ReadAllTextFile.ReadAllLinesを使ってファイル内容を読み込めます。

テキストファイルを読み込んでTextBoxに表示する例です。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files.Length == 0)
{
return;
}

string filePath = files[0];

try
{
string text = File.ReadAllText(filePath, Encoding.UTF8);
textBox1.Text = text;
}
catch (Exception ex)
{
MessageBox.Show($"ファイルの読み込みに失敗しました。\n{ex.Message}");
}
}

行単位で読み込みたい場合は、File.ReadAllLinesを使います。

C#
string[] lines = File.ReadAllLines(filePath, Encoding.UTF8);

ListBoxに1行ずつ表示する場合は次のようにします。

C#
private void LoadTextFile(string filePath)
{
string[] lines = File.ReadAllLines(filePath, Encoding.UTF8);

listBox1.Items.Clear();

foreach (string line in lines)
{
listBox1.Items.Add(line);
}
}

7-4. CSVやテキストファイルを読み込むサンプル

CSVファイルをドロップして読み込むサンプルです。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.None;
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

bool allCsv = files.All(file =>
Path.GetExtension(file).Equals(".csv", StringComparison.OrdinalIgnoreCase));

e.Effect = allCsv ? DragDropEffects.Copy : DragDropEffects.None;
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
LoadCsv(file);
}
}

private void LoadCsv(string filePath)
{
try
{
string[] lines = File.ReadAllLines(filePath, Encoding.UTF8);

foreach (string line in lines)
{
string[] columns = line.Split(',');

listBox1.Items.Add(string.Join(" | ", columns));
}
}
catch (Exception ex)
{
MessageBox.Show($"CSVの読み込みに失敗しました。\n{ex.Message}");
}
}

単純なCSVであればSplit(',')でも読み込めます。

ただし、実際のCSVでは、カンマを含む引用符付き文字列や改行を含むデータが存在することがあります。

そのようなCSVを正確に扱う場合は、CSVパーサーライブラリや専用の読み込み処理を使う方が安全です。

テキストファイルとCSVファイルの両方を受け付ける場合は、拡張子で処理を分けます。

C#
private void ProcessDroppedFile(string filePath)
{
string extension = Path.GetExtension(filePath).ToLower();

switch (extension)
{
case ".txt":
case ".log":
textBox1.Text = File.ReadAllText(filePath, Encoding.UTF8);
break;

case ".csv":
LoadCsv(filePath);
break;

default:
MessageBox.Show("対応していないファイル形式です。");
break;
}
}

7-5. 非同期処理で重いファイル読み込みを行う方法

大きなファイルを同期的に読み込むと、画面が固まったように見えることがあります。

その場合は、asyncawaitを使って非同期で読み込みます。

C#
private async void Form1_DragDrop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

if (files.Length == 0)
{
return;
}

string filePath = files[0];

try
{
textBox1.Text = "読み込み中...";

string text = await File.ReadAllTextAsync(filePath, Encoding.UTF8);

textBox1.Text = text;
}
catch (Exception ex)
{
MessageBox.Show($"ファイルの読み込みに失敗しました。\n{ex.Message}");
}
}

複数ファイルを非同期で読み込む場合は、次のようにできます。

C#
private async void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

listBox1.Items.Clear();

foreach (string file in files)
{
try
{
string text = await File.ReadAllTextAsync(file, Encoding.UTF8);
listBox1.Items.Add($"{Path.GetFileName(file)}: {text.Length}文字");
}
catch (Exception ex)
{
listBox1.Items.Add($"{Path.GetFileName(file)}: 読み込み失敗 - {ex.Message}");
}
}
}

重い処理を行う場合は、読み込み中の表示やキャンセル処理も用意すると、ユーザーにとって使いやすいアプリになります。

8. WPFでドラッグアンドドロップを実装する場合

8-1. Windows FormsとWPFの違い

C#でデスクトップアプリを作る場合、Windows FormsだけでなくWPFを使うこともあります。

Windows FormsとWPFでは、ドラッグアンドドロップの基本的な考え方は同じです。

・AllowDropをtrueにする
・DragEnterまたはPreviewDragEnterで判定する
・Dropイベントでデータを受け取る
・DataFormats.FileDropでファイルパスを取得する

ただし、イベント名やプロパティの書き方が少し異なります。

Windows Formsでは次のように書きます。

C#
this.AllowDrop = true;
this.DragEnter += Form1_DragEnter;
this.DragDrop += Form1_DragDrop;

WPFでは、XAMLまたはコードビハインドで設定します。

XML
<Grid AllowDrop="True"
PreviewDragEnter="Grid_PreviewDragEnter"
Drop="Grid_Drop">
</Grid>

WPFでは、見た目のカスタマイズがしやすいため、ドロップ領域のデザインを作り込みたい場合に向いています。

8-2. WPFでAllowDropを設定する方法

WPFでドラッグアンドドロップを受け付けるには、対象コントロールのAllowDropTrueにします。

XAMLで設定する例です。

XML
<Window x:Class="DragDropSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DragDropSample"
Height="300"
Width="500">
<Grid AllowDrop="True"
PreviewDragEnter="Grid_PreviewDragEnter"
Drop="Grid_Drop">
<TextBox x:Name="PathTextBox"
Margin="20"
Text="ここにファイルをドロップしてください"
VerticalScrollBarVisibility="Auto"
AcceptsReturn="True" />
</Grid>
</Window>

コードで設定する場合は次のようにします。

C#
public MainWindow()
{
InitializeComponent();

this.AllowDrop = true;
this.PreviewDragEnter += Window_PreviewDragEnter;
this.Drop += Window_Drop;
}

XAMLで書く方法の方が、WPFらしく見通しがよくなります。

8-3. PreviewDragEnter・Dropイベントの基本

WPFでは、DragEnterの代わりにPreviewDragEnterを使うことがあります。

基本的な実装は次のとおりです。

C#
private void Grid_PreviewDragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Copy;
}
else
{
e.Effects = DragDropEffects.None;
}

e.Handled = true;
}

実際にドロップされたデータは、Dropイベントで取得します。

C#
private void Grid_Drop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

PathTextBox.Text = string.Join(Environment.NewLine, files);
}

Windows Formsではe.Effectですが、WPFではe.Effectsです。

Windows Forms:e.Effect
WPF :e.Effects

この違いに注意してください。

8-4. WPFでファイルパスを取得するサンプルコード

WPFでファイルパスを取得する完全なサンプルです。

XAMLは次のようにします。

XML
<Window x:Class="DragDropSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="C# Drag and Drop Sample"
Height="300"
Width="500">
<Border BorderBrush="Gray"
BorderThickness="2"
Margin="20"
AllowDrop="True"
PreviewDragEnter="Border_PreviewDragEnter"
Drop="Border_Drop">
<TextBox x:Name="PathTextBox"
Text="ここにファイルをドロップしてください"
FontSize="16"
TextWrapping="Wrap"
AcceptsReturn="True"
VerticalScrollBarVisibility="Auto"
Margin="10" />
</Border>
</Window>

コードビハインドは次のようにします。

C#
using System;
using System.IO;
using System.Windows;

namespace DragDropSample
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void Border_PreviewDragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effects = DragDropEffects.Copy;
}
else
{
e.Effects = DragDropEffects.None;
}

e.Handled = true;
}

private void Border_Drop(object sender, DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return;
}

string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

PathTextBox.Text = string.Join(Environment.NewLine, paths);
}
}
}

WPFでも、Windows Formsと同じようにDataFormats.FileDropを使ってファイルパスを取得できます。

9. ドラッグアンドドロップが動かないときの原因と対処法

9-1. AllowDropがtrueになっていない

ドラッグアンドドロップが動かないときに最初に確認すべきなのが、AllowDropです。

Windows Formsでは次のように設定します。

C#
this.AllowDrop = true;
textBox1.AllowDrop = true;
listBox1.AllowDrop = true;

WPFではXAMLで次のように設定します。

XML
<Grid AllowDrop="True">
</Grid>

AllowDropfalseのままだと、イベントが発生しない、またはドロップできない原因になります。

9-2. DragEnterでe.Effectを設定していない

DragEntere.Effectを設定していない場合、ドロップできないことがあります。

Windows Formsでは次のように設定します。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}

WPFでは次のように設定します。

C#
private void Grid_PreviewDragEnter(object sender, DragEventArgs e)
{
e.Effects = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;

e.Handled = true;
}

ファイルのドロップを受け付ける場合は、DragDropEffects.Copyを設定するのが基本です。

9-3. 管理者権限の違いでドロップできない

意外と多い原因が、アプリとエクスプローラーの権限の違いです。

たとえば、C#アプリを管理者権限で実行している場合、通常権限で動作しているエクスプローラーからドラッグアンドドロップできないことがあります。

対処法としては、次の点を確認します。

・Visual Studioを管理者として実行していないか
・作成したアプリを管理者として実行していないか
・エクスプローラーとアプリの権限レベルが異なっていないか

通常のファイルドロップ機能であれば、アプリを管理者権限で起動しない方が扱いやすいです。

デバッグ中だけ動かない場合は、Visual Studioの起動権限も確認してください。

9-4. ファイル形式の判定が間違っている

GetDataPresentの判定が間違っていると、ファイルをドロップしているのに受け付けられないことがあります。

ファイルを受け付ける場合は、次の形式を使います。

C#
DataFormats.FileDrop

正しい例です。

C#
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
e.Effect = DragDropEffects.Copy;
}

テキストを受け付けたい場合は、DataFormats.Textを使います。

C#
if (e.Data.GetDataPresent(DataFormats.Text))
{
e.Effect = DragDropEffects.Copy;
}

ファイルとテキストでは取得方法も異なります。

C#
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
string text = (string)e.Data.GetData(DataFormats.Text);

扱いたいデータ形式に合わせて判定と取得方法を変えましょう。

9-5. イベントが正しく紐づいていない

イベントメソッドを作成していても、イベントに紐づいていなければ実行されません。

コードで登録する場合は、次の記述が必要です。

C#
this.DragEnter += Form1_DragEnter;
this.DragDrop += Form1_DragDrop;

TextBoxの場合は次のようにします。

C#
textBox1.DragEnter += TextBox1_DragEnter;
textBox1.DragDrop += TextBox1_DragDrop;

Visual Studioのデザイナーで登録した場合は、.Designer.csにイベント登録が書かれます。

イベントが発生しない場合は、ブレークポイントを置いて、DragEnterDragDropが呼ばれているか確認すると原因を特定しやすいです。

10. C#ドラッグアンドドロップ実装時の注意点

10-1. セキュリティ面で注意すべきこと

ドラッグアンドドロップで受け取ったファイルは、必ずしも安全とは限りません。

ユーザーが意図せず大きなファイルや不正な形式のファイルをドロップする可能性もあります。

そのため、次の点に注意しましょう。

・拡張子を確認する
・ファイルサイズを確認する
・存在チェックを行う
・アクセス権限エラーに備える
・ファイル内容をそのまま信用しない
・読み込み失敗時の処理を用意する

特に、ファイル内容を解析したり、外部コマンドに渡したりする場合は注意が必要です。

単にファイルパスを取得するだけであっても、存在チェックや例外処理は入れておくと安全です。

10-2. ファイル存在チェックを行う

ドロップされたパスが必ず存在するとは限りません。

処理前にFile.ExistsDirectory.Existsで確認しましょう。

C#
if (File.Exists(path))
{
// ファイルとして処理
}
else if (Directory.Exists(path))
{
// フォルダとして処理
}
else
{
MessageBox.Show("指定されたパスが存在しません。");
}

ファイルサイズを確認したい場合は、FileInfoを使います。

C#
FileInfo fileInfo = new FileInfo(path);

long size = fileInfo.Length;

たとえば、100MBを超えるファイルを拒否する場合は次のようにします。

C#
FileInfo fileInfo = new FileInfo(path);

if (fileInfo.Length > 100 * 1024 * 1024)
{
MessageBox.Show("100MBを超えるファイルは読み込めません。");
return;
}

10-3. ユーザーに分かりやすいドロップ領域を用意する

ドラッグアンドドロップ機能を実装しても、ユーザーが「どこにドロップすればよいか」分からなければ使いにくくなります。

そのため、画面上に分かりやすいドロップ領域を用意しましょう。

・「ここにファイルをドロップしてください」と表示する
・枠線を付ける
・背景色を変える
・ドラッグ中に表示を変える
・対応する拡張子を明記する

たとえば、Labelを使ってドロップ領域を作る場合は次のようにできます。

C#
label1.Text = "CSVファイルをここにドロップしてください";
label1.TextAlign = ContentAlignment.MiddleCenter;
label1.BorderStyle = BorderStyle.FixedSingle;
label1.AllowDrop = true;

ドラッグ中に見た目を変更する場合は、DragEnterDragLeaveを使います。

C#
private void Label1_DragEnter(object sender, DragEventArgs e)
{
label1.Text = "ドロップできます";
e.Effect = DragDropEffects.Copy;
}

private void Label1_DragLeave(object sender, EventArgs e)
{
label1.Text = "CSVファイルをここにドロップしてください";
}

10-4. エラーメッセージを適切に表示する

ファイル読み込みに失敗したときは、何が起きたのかをユーザーに分かりやすく伝えることが大切です。

悪い例です。

C#
catch
{
MessageBox.Show("エラー");
}

これでは、ユーザーも開発者も原因を把握しにくくなります。

改善例です。

C#
catch (Exception ex)
{
MessageBox.Show(
$"ファイルの読み込みに失敗しました。\n{ex.Message}",
"読み込みエラー",
MessageBoxButtons.OK,
MessageBoxIcon.Error);
}

ただし、ユーザー向けの画面に内部情報を出しすぎるのも避けるべきです。

開発中は詳細なエラーを表示し、本番ではログに記録するなど、用途に応じて使い分けるとよいでしょう。

10-5. 保守しやすいコードに分けるポイント

ドラッグアンドドロップ処理をすべてイベント内に書くと、コードが長くなりがちです。

保守しやすくするには、次のように処理を分けます。

・DragEnterでは判定だけ行う
・DragDropでは取得だけ行う
・ファイル処理は別メソッドに分ける
・拡張子チェックも別メソッドにする
・エラー処理を共通化する

例として、次のように分けると見通しがよくなります。

C#
private readonly string[] allowedExtensions = { ".csv", ".txt" };

private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = CanDropFiles(e)
? DragDropEffects.Copy
: DragDropEffects.None;
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (!CanDropFiles(e))
{
return;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
ProcessFile(file);
}
}

private bool CanDropFiles(DragEventArgs e)
{
if (!e.Data.GetDataPresent(DataFormats.FileDrop))
{
return false;
}

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

return files.All(file =>
allowedExtensions.Contains(
Path.GetExtension(file),
StringComparer.OrdinalIgnoreCase));
}

private void ProcessFile(string filePath)
{
if (!File.Exists(filePath))
{
return;
}

listBox1.Items.Add(filePath);
}

このようにしておくと、後から拡張子を増やしたり、読み込み処理を変更したりするときに修正しやすくなります。

11. C#ドラッグアンドドロップのよくある質問

11-1. フォルダのパスも取得できる?

はい、取得できます。

DataFormats.FileDropでは、ファイルだけでなくフォルダのパスも取得できます。

C#
string[] paths = (string[])e.Data.GetData(DataFormats.FileDrop);

ファイルかフォルダかを判定するには、次のようにします。

C#
foreach (string path in paths)
{
if (File.Exists(path))
{
MessageBox.Show("ファイルです");
}
else if (Directory.Exists(path))
{
MessageBox.Show("フォルダです");
}
}

フォルダ内のファイルを取得したい場合は、Directory.GetFilesを使います。

C#
string[] files = Directory.GetFiles(folderPath);

11-2. 複数ファイルを一度に取得できる?

はい、複数ファイルを一度に取得できます。

ドロップされたファイルはstring[]で取得できるため、foreachで処理します。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
listBox1.Items.Add(file);
}
}

ユーザーが複数ファイルを選択してドロップした場合、すべてのパスが配列に入ります。

11-3. ファイル以外のテキストもドロップできる?

はい、テキストもドロップできます。

テキストを受け付ける場合は、DataFormats.Textを使います。

C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.Text))
{
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
string text = (string)e.Data.GetData(DataFormats.Text);

textBox1.Text = text;
}

ファイルとテキストの両方に対応したい場合は、DragDrop内で判定を分けます。

C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
textBox1.Text = string.Join(Environment.NewLine, files);
}
else if (e.Data.GetDataPresent(DataFormats.Text))
{
string text = (string)e.Data.GetData(DataFormats.Text);
textBox1.Text = text;
}
}

11-4. ドラッグ中に見た目を変更できる?

はい、できます。

DragEnterDragLeaveを使うと、ドラッグ中だけ表示を変えられます。

C#
private void Panel1_DragEnter(object sender, DragEventArgs e)
{
if (e.Data.GetDataPresent(DataFormats.FileDrop))
{
panel1.BackColor = Color.LightBlue;
e.Effect = DragDropEffects.Copy;
}
else
{
e.Effect = DragDropEffects.None;
}
}

private void Panel1_DragLeave(object sender, EventArgs e)
{
panel1.BackColor = SystemColors.Control;
}

private void Panel1_DragDrop(object sender, DragEventArgs e)
{
panel1.BackColor = SystemColors.Control;

string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
listBox1.Items.AddRange(files);
}

ドロップ領域の色やテキストを変えると、ユーザーが操作しやすくなります。

11-5. Windows FormsとWPFのどちらで実装すべき?

シンプルな業務ツールや小規模なデスクトップアプリであれば、Windows Formsで十分です。

Windows Formsは実装が分かりやすく、ドラッグアンドドロップのコードも簡単に書けます。

一方で、見た目を作り込みたい場合や、MVVMパターンで設計したい場合はWPFが向いています。

目安としては次のとおりです。

Windows Formsが向いているケース
・シンプルな社内ツール
・短時間で画面を作りたい
・TextBoxやListBox中心の画面
・既存のWindows Formsアプリに機能追加したい

WPFが向いているケース
・UIデザインを作り込みたい
・データバインディングを活用したい
・MVVMで開発したい
・柔軟なレイアウトが必要

ドラッグアンドドロップの基本処理だけであれば、どちらでも実装できます。

初心者がまず試すなら、Windows Formsの方が理解しやすいでしょう。

まとめ

C#でドラッグアンドドロップを実装する基本は、AllowDropDragEnterDragDropの3つです。

ファイルパスを取得する場合は、DataFormats.FileDropを使います。

基本の流れは次のとおりです。

C#
this.AllowDrop = true;
this.DragEnter += Form1_DragEnter;
this.DragDrop += Form1_DragDrop;
C#
private void Form1_DragEnter(object sender, DragEventArgs e)
{
e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop)
? DragDropEffects.Copy
: DragDropEffects.None;
}
C#
private void Form1_DragDrop(object sender, DragEventArgs e)
{
string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

foreach (string file in files)
{
Console.WriteLine(file);
}
}

C#のドラッグアンドドロップでは、ファイルだけでなくフォルダやテキストも扱えます。

また、TextBoxにパスを表示する、ListBoxに複数ファイルを追加する、PictureBoxに画像を表示する、CSVファイルを読み込むなど、さまざまな用途に応用できます。

実装時には、次のポイントを押さえておきましょう。

・AllowDropをtrueにする
・DragEnterでe.Effectを設定する
・DataFormats.FileDropでファイルを判定する
・DragDropでstring[]としてパスを取得する
・File.ExistsやDirectory.Existsで存在確認する
・例外処理を入れて安全に実装する
・必要に応じて拡張子チェックを行う

まずはフォームやTextBoxにファイルパスを表示する最小サンプルから始めると、C#のドラッグアンドドロップ処理の全体像を理解しやすくなります。