2012年10月25日木曜日

DataGridViewとEntity

毎度毎度EntityをDataGridにぶち込んだ際に、ソート処理を実装するので、ある程度のサンプルをこちらに記載。

自分用の備忘録で、未テストの適当コードなので注意。

あとでテスト済みをソリューション毎乗っけるかもしれない?

BindingListの拡張

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
 
namespace EntityFrameworkSample
{
    /// <summary>
    /// BindingListの拡張クラスです。
    /// </summary>
    /// <typeparam name="T">タイプ</typeparam>
    public class BindingListEx<T> : BindingList<T>
    {
        /// <summary>
        /// ソート項目
        /// </summary>
        private PropertyDescriptor _propertyDescriptor;
 
        /// <summary>
        /// ソート方向(昇順・降順)の保持を行います。
        /// </summary>
        private ListSortDirection _listSortDirection;
 
        /// <summary>
        /// ソート済みかを示す値
        /// </summary>
        private bool _isSortedCore = false;
 
        /// <summary>
        /// ソートのサポートを行う事を宣言
        /// </summary>
        protected override bool SupportsSortingCore
        {
            get
            {
                return true;
            }
        }
 
        /// <summary>
        /// リストのソート順
        /// </summary>
        protected override ListSortDirection SortDirectionCore
        {
            get
            {
                return this._listSortDirection;
            }
        }
 
        /// <summary>
        /// ソート対象
        /// </summary>
        protected override PropertyDescriptor SortPropertyCore
        {
            get
            {
                return this._propertyDescriptor;
            }
        }
 
        /// <summary>
        /// ソート済みかを示す値の取得用
        /// </summary>
        protected override bool IsSortedCore
        {
            get
            {
                return this._isSortedCore;
            }
        }
 
        /// <summary>
        /// コンストラクタ
        /// </summary>
        public BindingListEx()
            : base()
        {
        }
 
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="list"></param>
        public BindingListEx(IList<T> list)
            : base(list)
        {
        }
 
        /// <summary>
        /// ソート処理
        /// </summary>
        /// <param name="prop">ソート項目</param>
        /// <param name="direction">ソート方向</param>
        protected override void ApplySortCore(PropertyDescriptor prop, ListSortDirection direction)
        {
            // ソート方向とソートプロパティを格納
            this._propertyDescriptor = prop;
            this._listSortDirection = direction;
 
            // Itemsの存在が無い場合は、処理必要な処理のみ実行して処理を戻します。
            if (base.Items == null || base.Items.Count == 0)
            {
                this._isSortedCore = false;
                base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemMoved, prop));
 
                return;
            }
 
            // Itemsが存在する場合はComparerExを生成して、比較処理を実行します。
            var items = (List<T>)base.Items;
            var comparerEx = new ComparerEx<T>(this._propertyDescriptor, this._listSortDirection);
            items.Sort(comparerEx);
 
            // ソート処理の終了を設定
            this._isSortedCore = true;
            base.OnListChanged(new ListChangedEventArgs(ListChangedType.ItemMoved, prop));
 
            return;
        }
    }
}


IComparerの実装

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Text;
 
namespace EntityFrameworkSample
{
    /// <summary>
    /// IComparer<T>の実装です。
    /// なお、ソート処理対象のプロパティに配列が使用されているような場合は正常に動作しません。
    /// インデクサも同様です。
    /// もっとも、プロパティに配列を使うなという事を一言記載。
    ///
    /// また、プロパティに指定する型は、必ずIComparableの実装が必要となります。
    /// これは、プロパティの値を比較する際に、ジェネリックメソッドにより、ComparableToにて大小比較を行っている為です。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public class ComparerEx<T> : IComparer<T>
    {
        /// <summary>
        /// ソート対象のプロパティ名
        /// </summary>
        private PropertyDescriptor _propertyDescriptor;
 
        /// <summary>
        /// ソート対象をソートする際のソート方向
        /// </summary>
        private ListSortDirection _listSortDirection;
 
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="propertyDescriptor">ソート対象のプロパティ</param>
        /// <param name="listSortDirecton">ソート方向</param>
        public ComparerEx(PropertyDescriptor propertyDescriptor, ListSortDirection listSortDirection)
        {
            this._propertyDescriptor = propertyDescriptor;
            this._listSortDirection = listSortDirection;
        }
 
        /// <summary>
        /// ソート処理で必要な大小比較を行い、処理結果により
        /// マイナス値~プラス値までを戻します。
        /// 完全な同一値の場合は0を返します。
        /// </summary>
        /// <param name="x">比較対象のオブジェクト1です。</param>
        /// <param name="y">比較対象のオブジェクト2です。</param>
        /// <returns>
        /// (昇順ソートの場合。降順は逆になります。)
        /// xがyより小さい場合は、0未満を返します。
        /// xとyが同一の値の場合は0を戻します。
        /// xがyより大きい場合は、0より大きい値を返します。
        /// </returns>
        public int Compare(T x, T y)
        {
            var xPropertyInfo = this.GetPropertyInfo(x, this._propertyDescriptor.Name);
            var yPropertyInfo = this.GetPropertyInfo(y, this._propertyDescriptor.Name);
 
            var xValue = xPropertyInfo.GetValue(x, null);
            var yValue = yPropertyInfo.GetValue(y, null);
 
            var comparableRet = this.Comparable((IComparable)xValue, (IComparable)yValue);
 
            if (comparableRet != 0)
                return comparableRet;
 
            // 同値の場合、PropertyInfoの属性に追加ソート指定項目が存在するかを確認する。
            var xAttributes = xPropertyInfo.GetCustomAttributes(typeof(SortAttribute), false);
            if (xAttributes == null)
                return comparableRet;
 
            // 属性が存在する場合、内部にSortAttributeが定義されているかを確認する。
            foreach (var xAttribute in xAttributes)
            {
                var sortAttribute = xAttribute as SortAttribute;
 
                if (sortAttribute == null)
                    continue;
 
                // SortAttributeの定義が存在する場合は、対象の属性情報に定義されている比較対象の予備プロパティ名を順番に比較していく。
                foreach (var propertyName in sortAttribute.Properties)
                {
                    comparableRet = this.CompareSub(x, y, propertyName);
                    if (comparableRet != 0)
                        return comparableRet;
                }
            }
 
            // ここまで抜けてきた場合は、完全同値の値となる。
            return 0;
        }
 
        /// <summary>
        /// ソート処理で必要な大小比較を行い、処理結果により
        /// マイナス値~プラス値までを戻します。
        /// 完全な同一値の場合は0を返します。
        /// </summary>
        /// <param name="x">比較対象のオブジェクト1です。</param>
        /// <param name="y">比較対象のオブジェクト2です。</param>
        /// <param name="propertyName">比較対象のプロパティです。</param>
        /// <returns>
        /// (昇順ソートの場合。降順は逆になります。)
        /// xがyより小さい場合は、0未満を返します。
        /// xとyが同一の値の場合は0を戻します。
        /// xがyより大きい場合は、0より大きい値を返します。
        /// </returns>
        private int CompareSub(T x, T y, string propertyName)
        {
            var xPropertyInfo = this.GetPropertyInfo(x, propertyName);
            var yPropertyInfo = this.GetPropertyInfo(y, propertyName);
 
            var xValue = xPropertyInfo.GetValue(x, null);
            var yValue = yPropertyInfo.GetValue(y, null);
 
            return this.Comparable((IComparable)xValue, (IComparable)yValue);
        }
 
        /// <summary>
        /// 指定された型に格納されているPropertyInfoの取得を行います。
        /// </summary>
        /// <param name="target">プロパティの情報を取得する対象</param>
        /// <param name="propertyName">取得対象のプロパティ名</param>
        /// <returns></returns>
        private PropertyInfo GetPropertyInfo(T target, string propertyName)
        {
            var propertyInfo = target.GetType().GetProperty(propertyName);
            if (propertyInfo == null)
                throw new ArgumentException(@"指定されてプロパティ名の取得が行えませんでした。");
 
            return propertyInfo;
        }
 
        /// <summary>
        /// 大小比較用のジェネリックメソッドです。
        /// </summary>
        /// <typeparam name="T">比較する型</typeparam>
        /// <param name="x">比較対象の値1</param>
        /// <param name="y">比較対象の値2</param>
        /// <returns>
        /// (昇順ソートの場合。降順は逆になります。)
        /// xがyより小さい場合は、0未満を返します。
        /// xとyが同一の値の場合は0を戻します。
        /// xがyより大きい場合は、0より大きい値を返します。
        /// </returns>
        private int Comparable<T>(T x, T y) where T : IComparable
        {
            var buf = x.CompareTo(y);
 
            if (this._listSortDirection == ListSortDirection.Ascending)
                return buf;
 
            return buf * -1;
        }
    }
}


複数プロパティでのソートする際に仕様するカスタム属性の作成

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace EntityFrameworkSample
{
    /// <summary>
    /// ソート用のEntityに追加するプロパティです。
    /// 単一プロパティによるソートを行った場合、同値だった場合に指定する別プロパティのソートKeyを定義します。
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
    public class SortAttribute : Attribute
    {
        /// <summary>
        /// ソート対象となるプロパティの格納を行っているフィールド
        /// </summary>
        private List<string> _properties;
 
        /// <summary>
        /// ソート対象のプロパティを取得します。
        /// </summary>
        public List<string> Properties { get { return this._properties; } }
 
        /// <summary>
        /// コンストラクタ
        /// </summary>
        /// <param name="properties">比較に仕様する追加プロパティ名</param>
        public SortAttribute(string[] properties)
        {
            this._properties = properties.ToList<string>();
        }
    }
}

とりあえず、ざっくり記載。
テストした後の修正バージョンを乗っけるかは、要望次第かも?

0 件のコメント:

コメントを投稿