TreeView でソートをお手軽に実装するには MultiColumnHeader
を利用する
MultiColumnHeader
では ソート用のイベントとして sortingChanged
が用意されており、これにイベントを登録することでどのカラムがソートされたかを判定できる
internal TreeView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader) { multiColumnHeader.sortingChanged += OnSortingChanged; Reload(); }
ソート処理全貌は以下のようになる
internal class SortableTreeView : TreeView { internal SortableTreeView(TreeViewState state, MultiColumnHeader multiColumnHeader) : base(state, multiColumnHeader) { multiColumnHeader.sortingChanged += OnSortingChanged; Reload(); } /// <summary> /// ソート変更 /// </summary> private void OnSortingChanged(MultiColumnHeader multiColumnHeader) { if (GetRows().Count <= 1) return; Sort(GetRows()); } /// <summary> /// ソートを行う /// </summary> private void Sort(IList<TreeViewItem> rows) { // カラム別のソート順を取得する var sortedColumns = multiColumnHeader.state.sortedColumns; if (multiColumnHeader.sortedColumnIndex == -1 || sortedColumns.Length == 0 ) return; // ソートするためにTreeViewItem をキャストする var cast = rootItem.children.Cast<TreeViewItem>(); // 昇順ソートかどうかの判定 var ascending = multiColumnHeader.IsSortedAscending(sortedColumns[0]); // 並べ替えするために 最初のソートカラムを基準にIOrderedEnumerable に変換する var items = InitialOrder(cast, ascending); // 2番以降のカラムをソートしていく for (var i = 1; i < sortedColumns.Length; i++) { // 昇順かどうか ascending = multiColumnHeader.IsSortedAscending(sortedColumns[i]); // ThenBy で更にソートしていく switch (sortedColumns[i]) { case 0: items = ThenBy(items, i => i.displayName, ascending); break; case 1: items = ThenBy(items, i => i.id, ascending); break; } } IOrderedEnumerable<T> ThenBy<T, TKey>(IOrderedEnumerable<T> source, Func<T, TKey> selector, bool ascending) { return @ascending ? source.ThenBy(selector) : source.ThenByDescending(selector); } // ソート後の TreeViewItem を再度子供に登録 rootItem.children = items.Cast<TreeViewItem>().ToList(); // Rows もソートされているためこちらにも並べ替えた結果を追加する rows.Clear(); foreach (var item in rootItem.children) { rows.Add(item); } Repaint(); } /// <summary> /// IOrderedEnumerable 変換 /// カラムによって何をソート対象にするかを記述する /// </summary> private IOrderedEnumerable<DetailTreeViewItem> InitialOrder(IEnumerable<TreeViewItem> items, bool ascending) { switch (multiColumnHeader.state.sortedColumns[0]) { case 1: return Order(items, i => i.id); default: return Order(items, i => i.displayName); } IOrderedEnumerable<T> Order<T, TKey>(IEnumerable<T> source, Func<T, TKey> selector) { return @ascending ? source.OrderBy(selector) : source.OrderByDescending(selector); } } protected override IList<TreeViewItem> BuildRows(TreeViewItem root) { if (!root.hasChildren) return new List<TreeViewItem>(); var rows = base.BuildRows(root); // コンパイル走った際にソートしている必要があるので初回生成時もソートする Sort(rows); return rows; }