WPFプログラミング備忘録

WPF DataGridの行を入れ替える

WPF

はじめに

この記事ではDataGridの行を任意の位置に入れ替える方法を確認しています。

開発環境は以下のとおりです。

オペレーティングシステムWindows10 x64
Visual StudioMicrosoft Visual Studio Community 2019 Version 16.11.2
.NET Framework4.7.2

サンプルプログラムの動作仕様

行の入れ替えの操作は左端の番号列で行うものとします。

移動したい行の番号セルをマウスの左クリックで選択します。複数行選択も可能です。

選択された行の番号セルには色が付きます。

あとは移動先の行の番号セルを左クリックします。これで行が入れ替わります。

選択行はクリックした位置に移動し、元の行は下にずれます。

ただし、移動先が選択中の行の場合は入れ替えはしないものとします。

単一行の入れ替えは下図の流れのとおりです。

起動直後の画面
移動候補として番号4を選択
番号1を移動先としてクリックしたときの画面

複数行選択も同様です。

番号4、5、6を選択
番号2を移動先としてクリックしたときの画面

プログラムの実装

XAMLとコードビハインドは以下のとおりです。

<Window x:Class="WpfDataGridExchangeRows.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:WpfDataGridExchangeRows"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400">
    
    <Window.Resources>
        <Style x:Key="numberStyle" TargetType="DataGridCell">
            <EventSetter Event="PreviewMouseLeftButtonUp" Handler="DgCellMouseLeftUp"/>
            <EventSetter Event="PreviewMouseLeftButtonDown" Handler="DgCellMouseLeftDown"/>
            <Style.Triggers>
                <Trigger Property="IsSelected" Value="True">
                    <Setter Property="Foreground" Value="Black"/>
                    <Setter Property="Background" Value="Gold"/>
                </Trigger>
            </Style.Triggers>
        </Style>
    </Window.Resources>
    <Grid>
        <DataGrid x:Name="Dg"
                  SelectionMode="Extended"
                  HeadersVisibility="Column"
                  SelectionUnit="Cell"
                  AutoGenerateColumns="False"
                  CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="番号" Binding="{Binding number}" IsReadOnly="True" Width="50" CanUserSort="False" CellStyle="{StaticResource numberStyle}"/>
                <DataGridTextColumn Header="名前" Binding="{Binding name}" IsReadOnly="True" Width="*" CanUserSort="False"/>
                <DataGridTextColumn Header="年齢" Binding="{Binding age}" IsReadOnly="True" Width="60" CanUserSort="False"/>
                <DataGridTextColumn Header="性別" Binding="{Binding gender}" IsReadOnly="True" Width="60" CanUserSort="False"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Collections.ObjectModel;

namespace WpfDataGridExchangeRows
{
    public class GridRecord
    {
        public int number { get; set; }
        public string name { get; set; }
        public int age { get; set; }
        public string gender { get; set; }
    }
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            GridRecords.Add(new GridRecord { number = 1, name = "山田 太郎", age = 28, gender = "男" });
            GridRecords.Add(new GridRecord { number = 2, name = "立花 薫", age = 48, gender = "男" });
            GridRecords.Add(new GridRecord { number = 3, name = "鈴木 花子", age = 32, gender = "女" });
            GridRecords.Add(new GridRecord { number = 4, name = "原田 尚美", age = 26, gender = "女" });
            GridRecords.Add(new GridRecord { number = 5, name = "吉川 雅之", age = 21, gender = "男" });
            GridRecords.Add(new GridRecord { number = 6, name = "寺尾 友紀", age = 50, gender = "女" });

            Dg.ItemsSource = GridRecords;
        }

        private List<DataGridCellInfo> DraggedCells = new List<DataGridCellInfo>();

        private bool CanSelectedCellsClear = false;

        private ObservableCollection<GridRecord> GridRecords = new ObservableCollection<GridRecord>();
        private DependencyObject GetParentObject(DependencyObject child)
        {
            if(child == null)
            {
                return null;
            }

            ContentElement contentElement = child as ContentElement;

            if(contentElement != null)
            {
                DependencyObject parent = ContentOperations.GetParent(contentElement);
            
                if(parent != null)
                {
                    return parent;
                }

                FrameworkContentElement frameworkContentElement = contentElement as FrameworkContentElement;

                if(frameworkContentElement != null)
                {
                    return frameworkContentElement.Parent;
                }
                else
                {
                    return null;
                }
            }
            return VisualTreeHelper.GetParent(child);
        }
        private DependencyObject TryFindParent<T>(DependencyObject child) where T : DependencyObject
        {
            DependencyObject parentObject = GetParentObject(child);

            if (parentObject == null)
            {
                return null;
            }

            var parent = parentObject as T;

            if (parent != null)
            {
                return parent;
            }
            else
            {
                // 再帰的に親を探しに行く
                return TryFindParent<T>(parentObject);
            }
        }

        private T TryFindDestinationFromPoint<T>(UIElement uIElement, Point point) where T : DependencyObject
        {
            DependencyObject dependencyObject = uIElement.InputHitTest(point) as DependencyObject;

            if(dependencyObject == null)
            {
                return null;
            }
            else if (dependencyObject is T)
            {
                return (T)dependencyObject;
            }
            else
            {
                return (T)TryFindParent<T>(dependencyObject);
            }
        }
        private bool CanAdd(DataGridCellInfo dataGridCellInfo, List<DataGridCellInfo> dataGridCellInfos)
        {
            GridRecord target = (GridRecord)dataGridCellInfo.Item;

            foreach(var element in dataGridCellInfos)
            {
                GridRecord source = (GridRecord)element.Item;

                if(target.number == source.number)
                {
                    return false;
                }
            }

            return true;
        }
        /// <summary>
        /// 移動したい行を選択する
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DgCellMouseLeftUp(object sender, MouseButtonEventArgs e)
        {
            if (CanSelectedCellsClear)
            {
                Dg.SelectedCells.Clear();
                CanSelectedCellsClear = false;
            }

            foreach (DataGridCellInfo cell in Dg.SelectedCells)
            {
                if(cell.Column.DisplayIndex == 0)
                {
                    // まだ追加されていないセルだけを追加する
                    if (CanAdd(cell, DraggedCells))
                    {
                        DraggedCells.Add(cell);
                    }
                }
            }
        }

        /// <summary>
        /// 移動先の行を決定する
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void DgCellMouseLeftDown(object sender, MouseButtonEventArgs e)
        {
            int draggedCellsCount;

            draggedCellsCount = DraggedCells.Count;

            if(draggedCellsCount <= 0)
            {
                return;
            }

            DataGridRow dataGridRow = TryFindDestinationFromPoint<DataGridRow>((UIElement)Dg, e.GetPosition(Dg)) as DataGridRow;

            if(dataGridRow == null)
            {
                DraggedCells.Clear();
                return;
            }

            GridRecord targetItem = dataGridRow.Item as GridRecord;

            if (targetItem != null)
            {
                bool canMove = true;

                foreach(var elem in DraggedCells)
                {
                    GridRecord gridRecord = elem.Item as GridRecord;
                    
                    if (gridRecord.number == targetItem.number)
                    {
                        // 選択した範囲には移動不可とする
                        canMove = false;
                    }
                }

                if (!canMove)
                {
                    DraggedCells.Clear();
                    return;
                }

                foreach(var cell in DraggedCells)
                {
                    GridRecord gridRecord = cell.Item as GridRecord;
                    GridRecords.Remove(gridRecord);
                }

                int targetIndex = GridRecords.IndexOf(targetItem);

                if (targetIndex < 0)
                {
                    DraggedCells.Clear();
                    return;
                }

                for(int i = draggedCellsCount - 1; i >= 0; i--)
                {
                    GridRecord gridRecord = DraggedCells[i].Item as GridRecord;

                    if(gridRecord != null)
                    {
                        GridRecords.Insert(targetIndex, gridRecord);
                    }
                }

                DraggedCells.Clear();

                CanSelectedCellsClear = true;
            }
        }
    }
}

TryFindDestinationFromPointメソッドがやや複雑ですが、要はDataGridRowが見つかるまで探しているだけです。

今回はDataGridの行を任意の位置に入れ替える方法を確認しました。

DataGridの行を取得する方法は以下の記事で確認しています。

>> WPF DataGridの行を取得する

以上です。

コメント