より良いエンジニアを目指して

1日1つ。良くなる!上手くなる!

WPFのドラッグ&ドロップの実装方法はコントロールごとに異なる?

WPFでのドラッグアンドドロップを実装する際に、他のサイトを参考にさせていただいたのですが、色々な実装があったので自分なりに確認してみました。

特に紛らわしいと感じたのが

  • PreviewDragOver
  • DragOver

と似て非なるようなイベントが存在する点です。

とりあえず手っ取り早くTextBoxにドラッグアンドドロップを実装する

私がドラッグアンドドロップを実装する場合によく行う目的は

  1. ファイルをテキストボックスにドラッグアンドドロップ
  2. ファイルのパスをテキストボックスに代入

です。

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) ・・・ バブルイベント

下記の記事にて紹介されています。

www.dotnetforall.com

バブルだけに泡のように下から上にルーティングされ、トンネルは上から下に呼び出されていくというところから来ているのでしょうか。

以下のような順で呼び出されるようです。

  1. 親のコントロールのトンネル(Preview)イベント
  2. 子のコントロールのトンネル(Preview)イベント
  3. 子のコントロールのバブルイベント
  4. 親のコントロールのバブルイベント

Binding出来ない

Drag系のイベントに対してBinding出来たらいいのにと安易に考えたら例外でした。DependencyPropertyではありませんでしたね。

f:id:rimever:20190311222946p:plain

gong-wpf-dragdrop

ListBoxのようなItemsControlでは、下記ライブラリも使えそうです。

github.com