Drag And Drop Frameworkを使用します。ドラッグさせたいものにDragSourceを指定し、ドロップ先にDropTargetを指定します。Dropイベントのイベントハンドラーで、ドラッグ元からドラックされた要素を取り除き、ドラッグ先に追加します。

以下、例として、ListView間でドラッグ&ドロップするコードを示します。ほかのコントロールの場合でも、同じコントロール同士でも違うコントロール同士でも、同じ考え方で実装できます。

 

実現イメージ

 

DragSource

ドラッグさせたいのは、左側のListViewの項目なので、左側のListViewの項目に対してDragSourceを指定します(14~17行目)。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <!-- 左側のListView -->
    <ListView Grid.Column="0" ItemsSource="{Binding Tasks1}" Margin="10" BorderBrush="Black" BorderThickness="2">
        <ListView.ItemTemplate>
            <DataTemplate>
                <Border BorderBrush="LightGray" Background="White" BorderThickness="2" Margin="0,0,-1,2" >
                    ...
                    <!--DragSourceを追加-->
                    <ig:DragDropManager.DragSource>
                        <ig:DragSource IsDraggable="True" DragChannels="ChannelA" Drop="DragSource_Drop">
                        </ig:DragSource>
                    </ig:DragDropManager.DragSource>
                </Border>
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>

    <!-- 右側のListView -->
    <ListView Grid.Column="1" ItemsSource="{Binding Tasks2}" Margin="10" BorderBrush="Black" BorderThickness="2">
        ...
    </ListView>
</Grid>

 

DropTarget

右側のListViewの上でドロップさせたいので、右側のListViewに対してDropTargetを設定します(18~21行目)。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <!-- 左側のListView -->
    <ListView Grid.Column="0" ItemsSource="{Binding Tasks1}" Margin="10" BorderBrush="Black" BorderThickness="2">
        ...
    </ListView>

    <!-- 右側のListView -->
    <ListView Grid.Column="1" ItemsSource="{Binding Tasks2}" Margin="10" BorderBrush="Black" BorderThickness="2">
        ...
        </ListView.ItemTemplate>

        <!-- DropTargetの追加 -->
        <ig:DragDropManager.DropTarget>
            <ig:DropTarget IsDropTarget="True" DropChannels="ChannelA">
            </ig:DropTarget>
        </ig:DragDropManager.DropTarget>

    </ListView>
</Grid>

DropTargetをListViewの項目ではなくListViewに対して設定していることに注意してください。

DragSourceもDropTargetもビジュアルツリー上にあるUI要素に対して設定する必要があります。ここでもしDropTargetをListViewの項目に設定してしまうと、ListViewの中身が空っぽの時にドロップができなくなります。それを避けるために、ListView自体にDropTargetを設定しています。

 

Dropイベントハンドラー

ドラッグされてきた項目のインスタンス、ドラッグ元とドロップ先の各ListView、ドラッグ元とドロップ先の各ListViewにバインドされたコレクション、をそれぞれ取り出し、ドラッグされた項目をドラッグ元のコレクションから削除し、ドロップ先のコレクションに追加します。

private void DragSource_Drop(object sender, Infragistics.DragDrop.DropEventArgs e)
{
    // ドラッグ元のUI要素(今回の場合Border)と紐づいているTaskItemを取り出す。
    Border sourcePanel = e.DragSource as Border;
    TaskItem sourceTask = sourcePanel.DataContext as TaskItem;

    // ドラッグ元のListViewを取得
    // https://jp.infragistics.com/help/wpf/infragisticswpf~infragistics.windows.utilities~getancestorfromtype(dependencyobject,type,boolean)
    ListView sourceListView = Utilities.GetAncestorFromType(sourcePanel, typeof(ListView), false) as ListView;

    // ドロップ先のListViewを取得
    ListView targetListView = e.DropTarget as ListView;

    // ドラッグ元とドロップ先のListViewにバインドされているコレクションを取得する。
    ObservableCollection<TaskItem> sourceTasks = sourceListView.ItemsSource as ObservableCollection<TaskItem>;
    ObservableCollection<TaskItem> targetTasks = targetListView.ItemsSource as ObservableCollection<TaskItem>;

    // ドラッグされたTaskItemをドラッグ元のコレクションから削除し、ドロップ先のコレクションに追加する。
    sourceTasks.Remove(sourceTask);
    targetTasks.Add(sourceTask);
}

  

今回、使用しているListViewのItemsSourceがビューモデルのコレクションとバインドされているので、ドラッグ元の項目の削除と追加はバインディングソースのコレクションに対して実装しています。

ビュー上のコントロールの位置を単純に入れ替えるだけ、という場合も、同様に実装できます。その場合は、ドラッグされたコントロールを元の親コントロールから削除し、ドロップ先のコントロールに子コントロールとして追加してください。

 

サンプル

 

リファレンス

Infragistics Drag and Drop Framework
https://jp.infragistics.com/help/wpf/drag-and-drop-framework

 

製品について

Ultimate UI for WPF