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;
}

 

以上で完了です!

 

 

製品について

Ultimate UI for WPF