WPFでのドラッグアンドドロップを実装する際に、他のサイトを参考にさせていただいたのですが、色々な実装があったので自分なりに確認してみました。
特に紛らわしいと感じたのが
- PreviewDragOver
- DragOver
と似て非なるようなイベントが存在する点です。
とりあえず手っ取り早くTextBoxにドラッグアンドドロップを実装する
私がドラッグアンドドロップを実装する場合によく行う目的は
- ファイルをテキストボックスにドラッグアンドドロップ
- ファイルのパスをテキストボックスに代入
です。
PreviewDragOverとPreviewDropで実装する方法は以下になります。
/// <summary> /// MainWindow.xaml の相互作用ロジック /// </summary> public partial class MainWindow : Window { /// <inheritdoc /> public MainWindow() { InitializeComponent(); TextBlockFilePath.PreviewDragOver += (sender, args) => { args.Effects = (args.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None); // これを忘れずに args.Handled = true; }; TextBlockFilePath.PreviewDrop += (sender, args) => { if (!args.Data.GetDataPresent(DataFormats.FileDrop)) { return; } string[] fileNames = (string[]) args.Data.GetData(DataFormats.FileDrop); if (fileNames == null) { return; } foreach (var fileName in fileNames) { if (!File.Exists(fileName)) { continue; } TextBlockFilePath.Text = fileName; } }; } }
コントロールごとに実装方法が異なる?
PreviewがつかないDragOverとDropでの実装を試したのですが、TextBoxについて反応しませんでした。
- TextBoxはデフォルトでAllowDrop=Trueだと思われるのですが、あえて明示的にAllowDrop=True
- 親コントロールもAllowDrop=True
とかしてみたりしたのですが、DragOverやDropのイベントは発火しませんでした。
比較のためにListBoxについて試して見ると、ListBoxについてはPreviewありなしに関わらず、DragOverもDropも発火します。
Handled=trueなんてしなくてもPreviewDropも発火することが確認できました。
試したコード
MainWindow.cs
public partial class MainWindow : Window { /// <inheritdoc /> public MainWindow() { InitializeComponent(); TextBox1.PreviewDragEnter += (sender, args) => Console.WriteLine("TextBox1.PreviewDragEnter"); TextBox1.PreviewDragOver += (sender, args) => Console.WriteLine("TextBox1.PreviewDragOver"); TextBox1.PreviewDrop += (sender, args) => Console.WriteLine("TextBox1.PreviewDrop"); TextBox1.DragEnter += (sender, args) => Console.WriteLine("TextBox1.DragEnter"); TextBox1.DragOver += (sender, args) => Console.WriteLine("TextBox1.DragOver"); TextBox1.Drop += (sender, args) => Console.WriteLine("TextBox1.Drop"); ListBox1.DragEnter += (sender, args) => Console.WriteLine("ListBox1.DragEnter"); ListBox1.DragOver += (sender, args) => Console.WriteLine("ListBox1.DragOver"); ListBox1.Drop += (sender, args) => Console.WriteLine("ListBox1.Drop"); ListBox1.PreviewDragEnter += (sender, args) => Console.WriteLine("ListBox1.PreviewDragEnter"); ListBox1.PreviewDragOver += (sender, args) => Console.WriteLine("ListBox1.PreviewDragOver"); ListBox1.PreviewDrop += (sender, args) => Console.WriteLine("ListBox1.PreviewDrop"); } }
<Window x:Name="Window1" x:Class="WpfDragAndDrop.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:WpfDragAndDrop" mc:Ignorable="d" Title="MainWindow" Height="450" Width="800"> <Grid x:Name="Grid1"> <TextBox Name="TextBox1" HorizontalAlignment="Left" Height="23" Margin="67,38,0,0" TextWrapping="Wrap" Text="TextBox" VerticalAlignment="Top" Width="120" AllowDrop="True"/> <ListBox x:Name="ListBox1" HorizontalAlignment="Left" Height="100" Margin="67,96,0,0" VerticalAlignment="Top" Width="100" AllowDrop="True"/> </Grid> </Window>
ListBoxに対するドラッグアンドドロップに対して発火するイベント
- PreviewDragEnter
- DragEnter
- PreviewDragOver
- DragOver
- PreviewDrop
- Drop
TextBoxに対するドラッグ&ドロップに対して発火するイベント
- PreviewDragEnter
- PreviewDragOver
※PreviewDropを発火させるには、PreviewDragOverでe.Handled=trueを指定する必要がある。
バブルイベントとトンネルイベント
Previewがつくイベントハンドラ(PreviewDragOver) ・・・ トンネルイベント Previewがつかないイベントハンドラ(DragOver) ・・・ バブルイベント
下記の記事にて紹介されています。
バブルだけに泡のように下から上にルーティングされ、トンネルは上から下に呼び出されていくというところから来ているのでしょうか。
以下のような順で呼び出されるようです。
Binding出来ない
Drag系のイベントに対してBinding出来たらいいのにと安易に考えたら例外でした。DependencyPropertyではありませんでしたね。
gong-wpf-dragdrop
ListBoxのようなItemsControlでは、下記ライブラリも使えそうです。