C# の DataGridView で カスタムデータ を ソート可能 にする方法

0 件のコメント

前回の記事 では、カスタムデータを利用して DataGridView を表示する方法をまとめましたが、 今回はその続きで DataGridView で カスタムデータ を ソート可能にする 方法 を見ていきます。


目次


概要

DataGridView で ソート可能 にすることは簡単で、 以下に載せる SortableBindingList を DataGridView.DataSource へ設定するだけです。

なんのひねりもないのでサクッとサンプルコードを見ていきましょう。

ソート可能なバインディングリスト

何と言ってもこのソースコードが必要! これさえあればカスタムデータも DataGridView でソートできる! そんな魔法のようなリストオブジェクトのサンプルコードを以下に載せます。

SortableBindingList.cs

namespace Sample
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;

    /// <summary>
    /// ソート可能なバインディングリストクラス
    /// </summary>
    /// <typeparam name="T">リスト内容</typeparam>
    public class SortableBindingList<T> : BindingList<T>
        where T : class
    {
        /// <summary>
        /// ソート済みかどうか
        /// </summary>
        private bool isSorted;

        /// <summary>
        /// 並べ替え操作の方向
        /// </summary>
        private ListSortDirection sortDirection = ListSortDirection.Ascending;

        /// <summary>
        /// ソートを行う抽象化プロパティ
        /// </summary>
        private PropertyDescriptor sortProperty;

        /// <summary>
        /// SortableBindingList クラス の 新しいインスタンス を初期化します。
        /// </summary>
        public SortableBindingList()
        {
        }

        /// <summary>
        /// 指定した リストクラス を利用して SortableBindingList クラス の 新しいインスタンス を初期化します。
        /// </summary>
        /// <param name="list">SortableBindingList に格納される System.Collection.Generic.IList</param>
        public SortableBindingList(IList<T> list)
            : base(list)
        {
        }

        /// <summary>
        /// リストがソートをサポートしているかどうかを示す値を取得します。
        /// </summary>
        protected override bool SupportsSortingCore
        {
            get { return true; }
        }

        /// <summary>
        /// リストがソートされたかどうかを示す値を取得します。
        /// </summary>
        protected override bool IsSortedCore
        {
            get { return this.isSorted; }
        }

        /// <summary>
        /// ソートされたリストの並べ替え操作の方向を取得します。
        /// </summary>
        protected override ListSortDirection SortDirectionCore
        {
            get { return this.sortDirection; }
        }

        /// <summary>
        /// ソートに利用する抽象化プロパティを取得します。
        /// </summary>
        protected override PropertyDescriptor SortPropertyCore
        {
            get { return this.sortProperty; }
        }

        /// <summary>
        /// ApplySortCore で適用されたソートに関する情報を削除します。
        /// </summary>
        protected override void RemoveSortCore()
        {
            this.sortDirection = ListSortDirection.Ascending;
            this.sortProperty = null;
            this.isSorted = false;
        }

        /// <summary>
        /// 指定されたプロパティおよび方向でソートを行います。
        /// </summary>
        /// <param name="prop">抽象化プロパティ</param>
        /// <param name="direction">並べ替え操作の方向</param>
        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            // ソートに使う情報を記録
            this.sortProperty = prop;
            this.sortDirection = direction;

            // ソートを行うリストを取得
            var list = Items as List<T>;
            if (list == null)
            {
                return;
            }

            // ソート処理
            list.Sort(this.Compare);

            // ソート完了を記録
            this.isSorted = true;

            // ListChanged イベントを発生させます
            this.OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
        }

        /// <summary>
        /// 比較処理を行います。
        /// </summary>
        /// <param name="lhs">左側の値</param>
        /// <param name="rhs">右側の値</param>
        /// <returns>比較結果</returns>
        private int Compare(T lhs, T rhs)
        {
            // 比較を行う
            var result = this.OnComparison(lhs, rhs);
            
            // 昇順の場合 そのまま、降順の場合 反転させる
            return (this.sortDirection == ListSortDirection.Ascending) ? result : -result;
        }

        /// <summary>
        /// 昇順として比較処理を行います。
        /// </summary>
        /// <param name="lhs">左側の値</param>
        /// <param name="rhs">右側の値</param>
        /// <returns>比較結果</returns>
        private int OnComparison(T lhs, T rhs)
        {
            object lhsValue = (lhs == null) ? null : this.sortProperty.GetValue(lhs);
            object rhsValue = (rhs == null) ? null : this.sortProperty.GetValue(rhs);

            if (lhsValue == null)
            {
                return (rhsValue == null) ? 0 : -1;
            }

            if (rhsValue == null)
            {
                return 1;
            }

            if (lhsValue is IComparable)
            {
                return ((IComparable)lhsValue).CompareTo(rhsValue);
            }
            
            if (lhsValue.Equals(rhsValue))
            {
                return 0;
            }

            return lhsValue.ToString().CompareTo(rhsValue.ToString());
        }
    }
}

実装サンプル

フォームには dataGridView1 という DataGridView が配置されている前提で SortableBindingList の使用例を以下に載せます。

Form1.cs

public partial class Form1 : Form
{
    public Form1()
    {
        this.InitializeComponent();

        var list = new SortableBindingList<UserModel>()
        {
            new UserModel()
            {
                Id = "0010",
                FirstName = "Kurihara",
                LastName = "Yuki",
                Email = "yuki.kurihara@gmail.com",
                Password = "*****"
            },
            new UserModel()
            {
                Id = "0020",
                FirstName = "Momotsuki",
                LastName = "Shinya",
                Email = "shinya.momotsuki@gmail.com",
                Password = "*****"
            }
        };

        this.dataGridView1.DataSource = list;
    }
}

ソートできない状態にする場合は BindingSource を挟んで DataGridView.DataSource へリストを設定していましたが、 SortableBindingList を利用する場合はそのまま DataGridView.DataSource へリストを設定できます。

これを実行すると、以下のようにカラムタイトルを選択してソートできるようになり、ソート方向を示す三角アイコンが表示されるようになります。

参考記事