「XamDataGrid レコードのドラッグアンドドロップ」を応用させて 2 つの XamDataGrid 間のドラッグ アンド ドロップを実装します。
0. 作成するアプリケーション
登録されている選手一覧から選抜選手を選択する画面を作ります。登録選手一覧用の XamDataGrid と選抜選手リスト用の XamDataGrid の間でドラッグ アンド ドロップを実装します。

1. 必要な NuGet パッケージ
- Infragistics.WPF.DataGrids
- Infragistics.WPF.DragDrop
2. MainWindow.xaml
ControlTemplate のコピー
GitHub のリポジトリから XamDataGrid の DataRecordPresenter および RecordListControl の ControlTemplate を作成中のアプリケーションにコピーしてきます。
DataRecordPresenter
GitHub レポジトリ: https://github.com/Infragistics/wpf-resources/blob/main/DefaultStyles/DataPresenter/DataPresenterAero_Express.xaml
GitHub レポジトリの該当箇所は下のスクリーンショット部分、DataRecordPresenter を TargetType とした Style コードです。この Style コードの全てをコピーしてくる必要はありません。必要なのは Style 生成コード(赤い枠の部分)と Template プロパティの Value に ControlTemplate を設定している Setter の開始タグから終了タグまでだけです。これらを Window のリソース ディクショナリに登録します。

<!-- MainWindow.xaml -->
<Window.Resources>
<!-- _________________________ DataRecordPresenter __________________________________________ -->
<!-- 👇必要なのは、この Style 生成コードと... -->
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<!-- 👇この Template プロパティの値に ControlTemplate を設定している Setter コード -->
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igDP:DataRecordPresenter}">
<igWindows:CardPanel x:Name="baseGrid" RenderTransform="{TemplateBinding FixedNearElementTransform}" Background="{TemplateBinding Background}">
<Border
Visibility="Collapsed"
Background="{DynamicResource {ComponentResourceKey TypeInTargetAssembly={x:Type igDP:XamDataGrid}, ResourceId=AddRowBackground}}" ... />
<Grid Margin="0" RenderTransform="{TemplateBinding ScrollableElementTransform}">
<Grid.ColumnDefinitions ...>
<Grid.RowDefinitions ...>
<!-- MD 6/10/10 - ChildRecordsDisplayOrder feature - Shifted all columns and rows down and over by 1 and increased spans by 1-->
<Border x:Name="OrientationHorizontalSep" BorderThickness="0,0,1,0" Grid.ColumnSpan="6" Background="#FFFAFAFA" BorderBrush="#FFE7E7E7" Visibility="Collapsed" .../>
<!-- ... (以下略) ... -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
RecordListControl
GitHub リポジトリ: https://github.com/Infragistics/wpf-resources/blob/main/DefaultStyles/DataPresenter/DataPresenterGeneric_Express.xaml
下のスクリーンショットは GitHub 上の RecordListControl の Style コード部分です。赤枠で囲ったコードを Window のリソース ディクショナリに登録します。

<!-- MainWindow.xaml -->
<Window.Resources>
<!-- _________________________ DataRecordPresenter __________________________________________ -->
<Style TargetType="{x:Type igDP:DataRecordPresenter}" ...>
<!-- 👇ここ -->
<!-- _________________________ RecordListControl __________________________________________ -->
<Style TargetType="{x:Type igDP:RecordListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igDP:RecordListControl}">
<ScrollViewer ...(省略)...>
<ItemsPresenter RenderTransform="{TemplateBinding ScrollableElementTransform}"/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
DragDropManager のアタッチ
DataRecordPresenter と RecordListControl に DragDropManager をアタッチします。
DataRecordPresenter
ContentPresenter(x:Name=”PART_RecordContentSite”)に DragDropManager の DragSource をアタッチします。DragStart、DragOver、Drop の各イベント ハンドラーも追加します。
<!-- MainWindow.xaml -->
<!-- _________________________ DataRecordPresenter __________________________________________ -->
<Style TargetType="{x:Type igDP:DataRecordPresenter}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igDP:DataRecordPresenter}">
<igWindows:CardPanel x:Name="baseGrid" ...>
<Border .../>
<Grid ...>
<Grid.ColumnDefinitions ...>
<Grid.RowDefinitions ...>
<Border x:Name="OrientationHorizontalSep" .../>
<igWindows:ExpansionIndicator x:Name="ExpansionIndicator" .../>
<igWindows:CardPanel x:Name="SpacerFill" ...>
<Border x:Name="fill" .../>
</igWindows:CardPanel>
<ContentPresenter x:Name="PART_HeaderContentSite" .../>
<Border x:Name="RecordSeparator" .../>
<ContentPresenter x:Name="PART_RecordContentSite" ...>
<!-- 👇DragDropManagerのアタッチ(ここから) -->
<ig:DragDropManager.DragSource>
<ig:DragSource
IsDraggable="True"
DragStart="DragSource_DragStart"
DragOver="DragSource_DragOver"
Drop="DragSource_Drop">
</ig:DragSource>
</ig:DragDropManager.DragSource>
<!-- DragDropManagerのアタッチ(ここまで) -->
</ContentPresenter>
<Border x:Name="PART_NestedContentSite" ...>
<ContentControl .../>
</Border>
</Grid>
</igWindows:CardPanel>
<ControlTemplate.Triggers>
<!-- ... (以下略) ... --->
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
RecordListControl
ScrollViewer に DragDropManager の DropTarget をアタッチします。
<!-- MainWindow.xaml -->
<!-- _________________________ RecordListControl __________________________________________ -->
<Style TargetType="{x:Type igDP:RecordListControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type igDP:RecordListControl}">
<ScrollViewer ...>
<!-- 👇DragDropManagerのアタッチ(ここから) -->
<ig:DragDropManager.DropTarget>
<ig:DropTarget IsDropTarget="True">
</ig:DropTarget>
</ig:DragDropManager.DropTarget>
<!-- DragDropManagerのアタッチ(ここまで) -->
<ItemsPresenter RenderTransform="{TemplateBinding ScrollableElementTransform}"/>
</ScrollViewer>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
3. MainWindow.xaml.cs
各イベント ハンドラーを実装していきます。
DragStart イベント ハンドラー
ドラッグ開始時の処理を実装します。ドラッグ元のデータ レコードからそのレコードに紐づいている選手データ オブジェクト(ViewModel や Model の実装を割愛していますが、このアプリケーションの場合 Player オブジェクトというものを使用しています)を取り出し、ドラッグ データに設定します。
// MainWindow.xaml.cs
// ドラッグ開始時の処理
// ドラッグ元のデータレコードからPlayerオブジェクトを取得し、ドラッグデータに設定する
// ドラッグ元のPlayerオブジェクトが取得できない場合、ドラッグをキャンセルする
private void DragSource_DragStart(object sender, Infragistics.DragDrop.DragDropStartEventArgs e)
{
// 註: GetPlayer、GetDataRecord、GetDataRecordFromAncestorの各メソッドは別途定義・実装しています。
// ここでは説明を割愛しますが、GitHubレポジトリで実装内容を確認できますので、適宜そちらをご覧ください。
var dragSourcePlayer = GetPlayer(GetDataRecord(GetDataRecordPresenterFromAncestor(e.OriginalDragSource as FrameworkElement)));
if (dragSourcePlayer is null)
{
e.Cancel = true;
return;
}
e.Data = dragSourcePlayer;
}
DragOver イベント ハンドラー
DragDropManager の DropTarget をアタッチした RecordListControl は XamDataGrid のレコード部分だけでなく、レコード ヘッダー部分やスクロール バー部分も含めた領域を描画しているコントロールです。そのため、既定ではこれらの領域にマウスオーバーしても、マウス カーソルはドロップ不可能な見た目に変化してくれません。レコード ヘッダー部分やスクロール バー部分ではドロップ不可のマウス カーソルに変わるように実装を追加します。
// MainWindow.xaml.cs
// ドラッグオーバー時の処理
// ドロップ先がスクロール バーまたはヘッダーの場合、ドロップ不可の操作とする
// それ以外の場合、デフォルトの操作とする
private void DragSource_DragOver(object sender, Infragistics.DragDrop.DragDropMoveEventArgs e)
{
var element = VisualTreeHelper.HitTest(this, e.GetPosition(this));
if (element is null)
{
e.OperationType = null;
return;
}
var scrollBar = Utilities.GetAncestorFromType(element.VisualHit, typeof(ScrollBar), false);
if (element.VisualHit is ScrollBar || scrollBar is not null)
{
e.OperationType = Infragistics.DragDrop.OperationType.DropNotAllowed;
return;
}
var recordPresenter = Utilities.GetAncestorFromType(element.VisualHit, typeof(DataRecordPresenter), false) as DataRecordPresenter;
if (recordPresenter is { IsHeaderRecord: true })
{
e.OperationType = Infragistics.DragDrop.OperationType.DropNotAllowed;
return;
}
e.OperationType = null;
}
Drop イベント ハンドラー
まずドロップしてよい操作なのかをチェックし、ドラッグ元およびドロップ先の各種オブジェクトを取得し、制約をチェックし、すべて問題なければ、ドラッグ元の XamDataGrid の Player コレクションとドロップ先の XamDataGrid の Player コレクションを更新します。また、見た目上わかりやすくするために、ドロップしたレコードをアクティブにし、選択した状態にします。
// MainWindow.xaml.cs
// ドロップ時の処理
// ドラッグデータからPlayerオブジェクトを取得し、ドラッグ元とドロップ先のデータソースを更新する
// ドロップ不可の場合、何もしない
// ドラッグ元とドロップ先のPlayerオブジェクトが同じ場合、何もしない
// ドロップ先のPlayerオブジェクトが取得できた場合、その位置に挿入する
// ドロップ先のPlayerオブジェクトが取得できなかった場合、ドロップ先のデータソースの最後に挿入する
// 挿入後、挿入したレコードをアクティブかつ選択状態にする
private void DragSource_Drop(object sender, Infragistics.DragDrop.DropEventArgs e)
{
if(e.OperationType == Infragistics.DragDrop.OperationType.DropNotAllowed)
{
return;
}
// ドラッグ元の各種オブジェクト(DataRecordPresenter、XamDataGrid、DataSource)を取得
// ※ドラッグ元の情報はe.DragSourceを起点に取得できる
var sourceDataRecordPresenter = (e.DragSource as FrameworkElement)?.TemplatedParent as DataRecordPresenter;
if (sourceDataRecordPresenter?.DataPresenter is not XamDataGrid sourceXamDataGrid) return;
if (sourceXamDataGrid.DataSource is not ObservableCollection<Player> sourcePlayerCollection) return;
if (e.Data is not Player draggedPlayer) return;
// ドロップ先の各種オブジェクト(DataRecordPresenter、XamDataGrid、DataSource)を取得
// ※ドロップ先の情報はマウスカーソル位置(e.GetPosition(this))を起点に取得する
var dropTargetVisualHitElement = VisualTreeHelper.HitTest(this, e.GetPosition(this))?.VisualHit;
if (dropTargetVisualHitElement is null) return;
var dropTargetDataRecordPresenter = dropTargetVisualHitElement as DataRecordPresenter
?? GetDataRecordPresenterFromAncestor(dropTargetVisualHitElement as FrameworkElement);
var dropTargetXamDataGrid = Utilities.GetAncestorFromType(dropTargetDataRecordPresenter ?? dropTargetVisualHitElement as FrameworkElement, typeof(XamDataGrid), false) as XamDataGrid;
if (dropTargetXamDataGrid?.DataSource is not ObservableCollection<Player> dropTargetPlayerCollection) return;
var dropTargetPlayer = GetPlayer(GetDataRecord(dropTargetDataRecordPresenter));
if(sourceXamDataGrid == dropTargetXamDataGrid || draggedPlayer == dropTargetPlayer)
{
return;
}
// ドラッグ元とドラッグ先の各XamDataGridのデータソースのコレクションの更新
var removeIdx = sourcePlayerCollection.IndexOf(draggedPlayer);
if (removeIdx != -1 && draggedPlayer != dropTargetPlayer)
{
sourcePlayerCollection.RemoveAt(removeIdx);
}
var insertIdx = dropTargetPlayer is null ? -1 : dropTargetPlayerCollection.IndexOf(dropTargetPlayer);
if (insertIdx >= 0)
{
dropTargetPlayerCollection.Insert(++insertIdx, draggedPlayer);
}
else
{
dropTargetPlayerCollection.Add(draggedPlayer);
insertIdx = dropTargetPlayerCollection.Count - 1;
}
dropTargetXamDataGrid.Records[insertIdx].IsActive = true;
dropTargetXamDataGrid.Records[insertIdx].IsSelected = true;
}
以上で完了です!