/*
* Copyright XXXX Co.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
namespace ChatworkBulkSender.Utils
{
/// <summary>
/// カスタムソート機能を提供するクラス。
/// ダブルクリック時にソートを行う場合、SortModeをProgrammaticに設定し、ApplySortを使用してください。
/// </summary>
public class SortableBindingListUtil<T> : BindingList<T>,ITypedList
{
// 各列のソート状態を格納する配列を用意する
private Dictionary<string, ListSortDirection> _columnSortDirections = new Dictionary<string,ListSortDirection>();
private Dictionary<string, ListSortDirection> _defaultSortDirections = new Dictionary<string, ListSortDirection>();
// 初回ソート済みであるかどうかを表すフラグ
private bool _isSorted = false;
// ソート対象プロパティ
private PropertyDescriptor _sortProperty = null;
/// <summary>
/// コンストラクタ
/// </summary>
/// <param name="list"></param>
public SortableBindingListUtil(IList<T> list) : base(list)
{
// 基底クラスで初期化を行うので空
}
// オーバーライドプロパティ
// 基底クラス BindingList<T> は、基本ソート機能設定のデフォルトの戻り値が FALSE なので常時有効にする
protected override bool SupportsSortingCore => true; // ※ 式変形のプロパティ(C#6.0以降の式本体メンバー) プロパティのget {return true} と同等
protected override bool IsSortedCore => _isSorted;
protected override PropertyDescriptor SortPropertyCore => _sortProperty;
//
public ListSortDirection SortDirection =>
_sortProperty != null && _columnSortDirections.ContainsKey(_sortProperty.Name)
? _columnSortDirections[_sortProperty.Name]
: ListSortDirection.Ascending;
// 外部アクセス可のプロパティ
public bool IsSorted => base.IsSortedCore;
public PropertyDescriptor SortProperty => base.SortPropertyCore;
public ListSortDirection SortDescription => base.SortDirectionCore;
// 現在のソート順を取得する
// プロパティが存在すれば、ソート状態を渡す
protected override ListSortDirection SortDirectionCore => _sortProperty != null &&
_columnSortDirections.ContainsKey(_sortProperty.Name) ? _columnSortDirections[_sortProperty.Name] : ListSortDirection.Ascending;
/// <summary>
/// プロパティごとのデフォルトソートを設定する。
/// </summary>
/// <param name="property"></param>
/// <returns></returns>
public ListSortDirection this[PropertyDescriptor property]
{
get => _defaultSortDirections.ContainsKey(property.Name) ? _defaultSortDirections[property.Name] : ListSortDirection.Ascending;
set => _defaultSortDirections[property.Name] = value;
}
/// <summary>
/// ソート処理を行う。
/// </summary>
/// <param name="property"></param>
/// <param name="direction"></param>
protected override void ApplySortCore(PropertyDescriptor property, ListSortDirection direction = ListSortDirection.Ascending)
{
// ソート順の決定
direction = DatermineSortDirection(property,direction);
// プロパティから取得し、List<T>にキャスト(下記でLINQを使用するため)
var items = Items as List<T>;
if (items != null && property != null)
{
var propertyInfo = typeof(T).GetProperty(property.Name);
if (propertyInfo == null) { return; }
// 自然順ソートを用いて、アイテムをソートする
PerformSort(items,propertyInfo,direction);
// ソート後の設定状況を更新する
UpdateSortState(property,direction);
}
}
/// <summary>
/// ソート順を決定する。
/// </summary>
/// <param name="property"></param>
/// <param name="direction"></param>
/// <returns></returns>
private ListSortDirection DatermineSortDirection(PropertyDescriptor property, ListSortDirection direction)
{
// ソート済みの列として含まれているか
if (property != null && _columnSortDirections.ContainsKey(property.Name))
{
// 現在のソート設定と並びが一致するか確認する
if (IsCurrentlySortedBy(property,_columnSortDirections[property.Name]))
{
// 一致していた場合、ソート処理を反転する
return _columnSortDirections[property.Name] == ListSortDirection.Ascending
? ListSortDirection.Descending : ListSortDirection.Ascending;
}
else
{
// 一致していない場合、設定通りにソートする
return _columnSortDirections[property.Name];
}
}
else
{
// 初回または新しい列の場合、デフォルト順でソートする
return this[property];
}
}
/// <summary>
/// 実際に自然順でのソート処理を行う。
/// </summary>
/// <param name="items"></param>
/// <param name="propertyInfo"></param>
/// <param name="direction"></param>
private void PerformSort(List<T> items, PropertyInfo propertyInfo, ListSortDirection direction)
{
// 自然順でソートできるネイティブメソッドのUtilクラスの比較子を用意する
// 降順設定ならTrue
var comparer = new NaturalStringComparerUtil(direction == ListSortDirection.Descending);
// 用意した比較子を用いて、並び替える
var sortedItems = items.OrderBy(x => propertyInfo.GetValue(x), comparer).ToList();
// リストの更新中は変更通知を無効にする
RaiseListChangedEvents = false;
try
{
// 既存のアイテムをクリアする
ClearItems();
// ソート済みのアイテムを追加する
sortedItems.ForEach(Add);
}
finally
{
// 変更通知を有効にする
RaiseListChangedEvents = true;
}
}
/// <summary>
/// ソート後にソート状態を更新し、変更を通知する。
/// </summary>
/// <param name="property"></param>
/// <param name="direction"></param>
private void UpdateSortState(PropertyDescriptor property, ListSortDirection direction)
{
_columnSortDirections[property.Name] = direction;
_isSorted = true;
_sortProperty = property;
// リスト全体が変更されたことを通知する
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1));
}
/// <summary>
/// 外部から呼び出し可能なソート実行メソッド。
/// 列のソート順を判定し、適切なソート順でソートを実行する。
/// </summary>
/// <param name="property">列のプロパティを設定してください</param>
public void ApplySort(PropertyDescriptor property)
{
// ソートをする
ApplySortCore(property);
}
public void ApplySort(PropertyDescriptor property, ListSortDirection direction)
{
// ソートする
ApplySortCore(property,direction);
}
/// <summary>
/// 現在プロパティ列の並びが記録されたソート順と一致するか判別する
/// 他列のソートによって並び順が変化した場合にも対応する
/// </summary>
/// <param name="property">判別対象列のプロパティ</param>
/// <param name="direction">記録されたソート順</param>
/// <returns></returns>
private bool IsCurrentlySortedBy(PropertyDescriptor property,ListSortDirection direction)
{
// 要素が1個以下(ソート対象なし)の場合、ソート済みとみなす
if (Items.Count <= 1) { return true; }
// ソート対象のプロパティをリフレクションで取得する
var propertyInfo = typeof(T).GetProperty(property.Name);
if (propertyInfo == null) { return false; }
// IComparableと文字列の混在、null値を適切に処理するため、自然順のネイティブメソッド比較子を用意する
var comparer = new NaturalStringComparerUtil();
// アイテムのプロパティの値を取得する
var values = Items.Select(item => propertyInfo.GetValue(item)).ToString();
// 隣接する要素のペアを作成し、現在の並びが記録されたソート順と一致するか判別する
return Items.Zip(values.Skip(1), (first,second) =>
{
int comparison = comparer.Compare(first,second);
// 前の値 <= 後ろの値 なら昇順、前の値 >= 後ろの値なら降順
return direction == ListSortDirection.Ascending ? comparison <= 0 : comparison >= 0;
}).All(x => x); // 全てtrueの時のみtrue
}
/// <summary>
/// ソートを解除する。
/// </summary>
protected override void RemoveSortCore()
{
_isSorted = false;
_sortProperty = null;
}
/*
* ITypedList インターフェイスの実装
* DataGridViewがデータ構造を理解するために使用する
*
* ※ データソースを解析する以下の3つで呼び出される
*
* ①.データソースを設定した時。
* ②.AutoGenerateColumnsがTrueの場合
* ③.DataPropertyNameとの紐付け時
*/
/// <summary>
/// プロパティの名前を取得する。
/// </summary>
/// <param name="listAccessors"></param>
/// <returns></returns>
public string GetListName(PropertyDescriptor[] listAccessors)
{
// リストの名前を返す
return typeof(T).Name;
}
/// <summary>
/// リスト内のアイテムが持つプロパティの情報を取得する。
/// </summary>
/// <param name="listAccessors"></param>
/// <returns></returns>
public PropertyDescriptorCollection GetItemProperties(PropertyDescriptor[] listAccessors)
{
// リスト内のプロパティ情報を返す
return TypeDescriptor.GetProperties(typeof(T));
}
}
}