web-dev-qa-db-ja.com

各列のWPFDataGrid CustomSort

ObservableCollectionをカプセル化するCollectionViewSourceにバインドされたWPFDataGridがあります。このCollectionViewSourceには、2つの主な目的があります。

1)Tの特定のプロパティで各アイテムをグループ化する。GroupDescriptionでValueConverterを使用して、必要なグループ化動作を取得しています。

2)グリッドをa)主にグループ名(上記で定義)およびb)個々のグループアイテムで並べ替える。これは、カスタムIComparerをCollectionViewSourceの「CustomSort」プロパティにアタッチすることで実現しています。

これはほとんどの場合うまく機能しますが、列ヘッダーがクリックされるとすぐに、並べ替えロジックが上書きされます。並べ替えを無効にしたくないのですが、特定の列にカスタムの並べ替え順序を割り当てることができるかどうか疑問に思っていましたか?

わかりやすくするために、ユーザーが「ColumnA」をクリックしたとします。現時点では、CustomSorterによってカプセル化された並べ替えロジックが上書きされ、DataGridがそのプロパティで並べ替えられます。選択したプロパティで並べ替えるのではなく、CustomSorterのロジックを逆にします。

15
trilson86

この問題を処理する添付プロパティをいくつか作成しました。これが誰かに役立つことを願っています!

まず、指向性比較器用のシンプルなインターフェース。これはIComparerを拡張しますが、もう1つのプロパティ(SortDirection)を提供します。実装では、これを使用して要素の正しい順序を決定する必要があります(そうでなければ失われていたはずです)。

public interface ICustomSorter : IComparer
{
    ListSortDirection SortDirection { get; set; }
}

次は添付の動作です-これは2つのことを行います:1)カスタムソートロジック(AllowCustomSort = true)を使用するようにグリッドに指示し、b)このロジックを列ごとのレベルで設定する機能を提供します。

public class CustomSortBehaviour
{
    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", typeof(ICustomSorter), typeof(CustomSortBehavior));

    public static ICustomSorter GetCustomSorter(DataGridColumn gridColumn)
    {
        return (ICustomSorter)gridColumn.GetValue(CustomSorterProperty);
    }

    public static void SetCustomSorter(DataGridColumn gridColumn, ICustomSorter value)
    {
        gridColumn.SetValue(CustomSorterProperty, value);
    }

    public static readonly DependencyProperty AllowCustomSortProperty =
        DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool),
        typeof(CustomSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));

    public static bool GetAllowCustomSort(DataGrid grid)
    {
        return (bool)grid.GetValue(AllowCustomSortProperty);
    }

    public static void SetAllowCustomSort(DataGrid grid, bool value)
    {
        grid.SetValue(AllowCustomSortProperty, value);
    }

    private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var existing = d as DataGrid;
        if (existing == null) return;

        var oldAllow = (bool)e.OldValue;
        var newAllow = (bool)e.NewValue;

        if (!oldAllow && newAllow)
        {
            existing.Sorting += HandleCustomSorting;
        }
        else
        {
            existing.Sorting -= HandleCustomSorting;
        }
    }

    private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
    {
        var dataGrid = sender as DataGrid;
        if (dataGrid == null || !GetAllowCustomSort(dataGrid)) return;

        var listColView = dataGrid.ItemsSource as ListCollectionView;
        if (listColView == null)
            throw new Exception("The DataGrid's ItemsSource property must be of type, ListCollectionView");

        // Sanity check
        var sorter = GetCustomSorter(e.Column);
        if (sorter == null) return;

        // The guts.
        e.Handled = true;

        var direction = (e.Column.SortDirection != ListSortDirection.Ascending)
                            ? ListSortDirection.Ascending
                            : ListSortDirection.Descending;

        e.Column.SortDirection = sorter.SortDirection = direction;
        listColView.CustomSort = sorter;
    }
}

これを使用するには、ICustomComparer(パラメーターなしのコンストラクターを使用)をXAMLに実装します。

<UserControl.Resources>
    <converters:MyComparer x:Key="MyComparer"/>
    <!-- add more if you need them -->
</UserControl.Resources>
<DataGrid behaviours:CustomSortBehaviour.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:CustomSortBehaviour.CustomSorter="{StaticResource MyComparer}" />
    </DataGrid.Columns>
</DataGrid>
27
trilson86

trilson86 の答えは素晴らしいです。ただし、2つのDependencyProperty宣言の3番目のパラメーターが正しくありません。 DataGridとDataGridColumnの代わりに、次のようにCustomSortBehaviourにする必要があります。

public static readonly DependencyProperty AllowCustomSortProperty =
        DependencyProperty.RegisterAttached("AllowCustomSort", 
        typeof(bool),
        typeof(CustomSortBehaviour), // <- Here
        new UIPropertyMetadata(false, OnAllowCustomSortChanged));

    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", 
        typeof(ICustomSorter), 
        typeof(CustomSortBehaviour));  // <- Here

AllowCustomSortプロパティがすでに登録されているという警告が表示され続けました。少し調べてみると、答えがわかりました ここ

とにかくいい答えですので、よろしくお願いします。

4
pennyrave

これが1つの方法です:

using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

public static class DataGridSort
{
    public static readonly DependencyProperty ComparerProperty = DependencyProperty.RegisterAttached(
        "Comparer",
        typeof(IComparer),
        typeof(DataGridSort),
        new PropertyMetadata(
            default(IComparer),
            OnComparerChanged));

    private static readonly DependencyProperty ColumnComparerProperty = DependencyProperty.RegisterAttached(
        "ColumnComparer",
        typeof(ColumnComparer),
        typeof(DataGridSort),
        new PropertyMetadata(default(ColumnComparer)));

    private static readonly DependencyProperty PreviousComparerProperty = DependencyProperty.RegisterAttached(
        "PreviousComparer",
        typeof(IComparer),
        typeof(DataGridSort),
        new PropertyMetadata(default(IComparer)));

    public static readonly DependencyProperty UseCustomSortProperty = DependencyProperty.RegisterAttached(
        "UseCustomSort",
        typeof(bool),
        typeof(DataGridSort),
        new PropertyMetadata(default(bool), OnUseCustomSortChanged));

    public static void SetComparer(DataGridColumn element, IComparer value)
    {
        element.SetValue(ComparerProperty, value);
    }

    public static IComparer GetComparer(DataGridColumn element)
    {
        return (IComparer)element.GetValue(ComparerProperty);
    }

    public static void SetUseCustomSort(DependencyObject element, bool value)
    {
        element.SetValue(UseCustomSortProperty, value);
    }

    public static bool GetUseCustomSort(DependencyObject element)
    {
        return (bool)element.GetValue(UseCustomSortProperty);
    }

    private static void OnComparerChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var column = (DataGridColumn)d;
        var columnComparer = new ColumnComparer((IComparer)e.NewValue, column);
        column.SetValue(ColumnComparerProperty, columnComparer);
    }

    private static void OnUseCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = (DataGrid)d;
        if ((bool)e.NewValue)
        {
            WeakEventManager<DataGrid, DataGridSortingEventArgs>.AddHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);
        }
        else
        {
            WeakEventManager<DataGrid, DataGridSortingEventArgs>.RemoveHandler(dataGrid, nameof(dataGrid.Sorting), OnDataGridSorting);
        }
    }

    private static void OnDataGridSorting(object sender, DataGridSortingEventArgs e)
    {
        var column = e.Column;
        var columnComparer = (ColumnComparer)column.GetValue(ColumnComparerProperty);
        var dataGrid = (DataGrid)sender;
        var view = CollectionViewSource.GetDefaultView(dataGrid.ItemsSource) as ListCollectionView;
        if (view == null)
        {
            return;
        }
        if (columnComparer == null)
        {
            view.CustomSort = (IComparer)dataGrid.GetValue(PreviousComparerProperty);
        }
        else
        {
            if (!(view.CustomSort is ColumnComparer))
            {
                dataGrid.SetValue(PreviousComparerProperty, view.CustomSort);
            }

            switch (column.SortDirection)
            {
                case ListSortDirection.Ascending:
                    column.SortDirection = ListSortDirection.Descending;
                    view.CustomSort = columnComparer.Descending;
                    break;
                case null:
                case ListSortDirection.Descending:
                    column.SortDirection = ListSortDirection.Ascending;
                    view.CustomSort = columnComparer.Ascending;
                    break;
                default:
                    throw new ArgumentOutOfRangeException();
            }

            e.Handled = true;
        }
    }

    private class ColumnComparer : IComparer
    {
        private readonly IComparer valueComparer;
        private readonly DataGridColumn column;
        private readonly InvertedComparer inverted;

        public ColumnComparer(IComparer valueComparer, DataGridColumn column)
        {
            this.valueComparer = valueComparer;
            this.column = column;
            inverted = new InvertedComparer(this);
        }

        public IComparer Ascending => this;

        public IComparer Descending => inverted;

        int IComparer.Compare(object x, object y)
        {
            if (x == y)
            {
                return 0;
            }

            if (x == null)
            {
                return -1;
            }

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

            // this can perhaps be a bit slow
            // Not adding caching yet.
            var xProp = x.GetType().GetProperty(column.SortMemberPath);
            var xValue = xProp.GetValue(x);
            var yProp = x.GetType().GetProperty(column.SortMemberPath);
            var yValue = yProp.GetValue(y);
            return valueComparer.Compare(xValue, yValue);
        }

        private class InvertedComparer : IComparer
        {
            private readonly IComparer comparer;

            public InvertedComparer(IComparer comparer)
            {
                this.comparer = comparer;
            }

            public int Compare(object x, object y)
            {
                return comparer.Compare(y, x);
            }
        }
    }
}

使用法:

<DataGrid AutoGenerateColumns="False"
            ItemsSource="{Binding DataItems}"
            local:DataGridSort.UseCustomSort="True">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding Key}"
                            Header="Key"
                            local:DataGridSort.Comparer="{x:Static local:StringLengthComparer.Default}" />
        <DataGridTextColumn Binding="{Binding Value}" Header="Value" />
    </DataGrid.Columns>
</DataGrid>
4
Johan Larsson

これは、OnSortingイベントをオーバーライドして、自分で実装することで行いました。

http://msdn.Microsoft.com/en-us/library/system.windows.controls.datagrid.onsorting.aspx

これは基本的に、ListCollectionViewを再ソートすることを意味しました。

申し訳ありませんが、答えはそれほど深くはありません。

2
Mark Jones

@ trilson86の回答を変更して、DataGrid全体に1つのカスタムソータークラスのみが必要になるようにしました。

最初のインターフェース:

public interface ICustomSorter : IComparer
{
    ListSortDirection SortDirection { get; set; }

    string SortMemberPath { get; set; }
}

次に、DateGridRowではなくDataGridで直接使用できるようにCustomSorterPropertyを定義するBevaviourクラス。 HandleCustomSorting()で、CustomSorterのプロパティSortMemberPathに、クリックした列の実際の値が入力されていない場合、CustomSorterでこの値を使用して、目的の列に対して並べ替えることができます。

public class CustomSortBehaviour
{
    #region Fields and Constants

    public static readonly DependencyProperty CustomSorterProperty =
        DependencyProperty.RegisterAttached("CustomSorter", typeof (ICustomSorter), typeof (CustomSortBehaviour));

    public static readonly DependencyProperty AllowCustomSortProperty =
        DependencyProperty.RegisterAttached("AllowCustomSort",
            typeof (bool),
            typeof (CustomSortBehaviour),
            new UIPropertyMetadata(false, OnAllowCustomSortChanged));



    #endregion

    #region public Methods

    public static bool GetAllowCustomSort(DataGrid grid)
    {
        return (bool) grid.GetValue(AllowCustomSortProperty);
    }


    public static ICustomSorter GetCustomSorter(DataGrid grid)
    {
        return (ICustomSorter)grid.GetValue(CustomSorterProperty);
    }

    public static void SetAllowCustomSort(DataGrid grid, bool value)
    {
        grid.SetValue(AllowCustomSortProperty, value);
    }


    public static void SetCustomSorter(DataGrid grid, ICustomSorter value)
    {
        grid.SetValue(CustomSorterProperty, value);
    }

    #endregion

    #region nonpublic Methods

    private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
    {
        var dataGrid = sender as DataGrid;
        if (dataGrid == null || !GetAllowCustomSort(dataGrid))
        {
            return;
        }

        var listColView = dataGrid.ItemsSource as ListCollectionView;
        if (listColView == null)
        {
            throw new Exception("The DataGrid's ItemsSource property must be of type, ListCollectionView");
        }

        // Sanity check
        var sorter = GetCustomSorter(dataGrid);
        if (sorter == null)
        {
            return;
        }

        // The guts.
        e.Handled = true;

        var direction = (e.Column.SortDirection != ListSortDirection.Ascending)
            ? ListSortDirection.Ascending
            : ListSortDirection.Descending;

        e.Column.SortDirection = sorter.SortDirection = direction;
        sorter.SortMemberPath = e.Column.SortMemberPath;

        listColView.CustomSort = sorter;
    }

    private static void OnAllowCustomSortChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var existing = d as DataGrid;
        if (existing == null)
        {
            return;
        }

        var oldAllow = (bool) e.OldValue;
        var newAllow = (bool) e.NewValue;

        if (!oldAllow && newAllow)
        {
            existing.Sorting += HandleCustomSorting;
        }
        else
        {
            existing.Sorting -= HandleCustomSorting;
        }
    }

    #endregion
}

XAMLでは次のように使用できます。

<Window x:Class="..."
        xmlns:sorter="clr-namespace:...Sorting"
        ...
        >

    <Window.Resources>
        <sorter:CustomSorter x:Key="MySorter"/>
    </Window.Resources>

    <Grid>

        <DataGrid ItemsSource="{Binding ...}"
                  sorter:CustomSortBehaviour.AllowCustomSort="True"
                  sorter:CustomSortBehaviour.CustomSorter="{StaticResource MySorter}" >


            <DataGrid.Columns>
                <DataGridTextColumn Header="Column 1" Binding="{Binding Column1}"/>
                <DataGridTextColumn Header="Column 2" Binding="{Binding Column2}"/>
                <DataGridTextColumn Header="Column 3" Binding="{Binding Column3}"/>
            </DataGrid.Columns>

        </DataGrid>

    </Grid>
</Window>
1
Jens Brand

DataGrid CustomSorting

<DataGrid attached:DataGridHelpers.UseCustomSort="True" ItemsSource="{Binding Items}" AutoGenerateColumns="False">
    <DataGrid.Columns>
        <DataGridTextColumn attached:DataGridHelpers.CustomSorterType="{x:Type comparers:StrLogicalComparer}" Binding="{Binding CodeText}" Header="Code"  />
        <DataGridTextColumn Header="Number" Binding="{Binding Number}" />
    </DataGrid.Columns>
</DataGrid>

ネストされたプロパティをサポート

1
lezhkin11

この答えはtrilson86のソリューションに非常に似ています-それはそれに基づいていました-しかしそれはあなたの比較者に渡される値が行ではなく列の実際の値であるような方法でSortMemberPathを説明します。これにより、ソーターでの再利用が大幅に促進されます。さらに、カスタムソートインターフェイスの必要性を完全に排除します。

DataGridSortBehavior.cs

using System;
using System.Collections;
using System.ComponentModel;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace YourNamespace
{
    public class DataGridSortBehavior 
    {
        public static IComparer GetSorter(DataGridColumn column)
        {
            return (IComparer)column.GetValue(SorterProperty);
        }

        public static void SetSorter(DataGridColumn column, IComparer value)
        {
            column.SetValue(SorterProperty, value);
        }

        public static bool GetAllowCustomSort(DataGrid grid)
        {
            return (bool)grid.GetValue(AllowCustomSortProperty);
        }

        public static void SetAllowCustomSort(DataGrid grid, bool value)
        {
            grid.SetValue(AllowCustomSortProperty, value);
        }

        public static readonly DependencyProperty SorterProperty = DependencyProperty.RegisterAttached("Sorter", typeof(IComparer), 
            typeof(DataGridSortBehavior));
        public static readonly DependencyProperty AllowCustomSortProperty = DependencyProperty.RegisterAttached("AllowCustomSort", typeof(bool), 
            typeof(DataGridSortBehavior), new UIPropertyMetadata(false, OnAllowCustomSortChanged));

        private static void OnAllowCustomSortChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
        {
            var grid = (DataGrid)obj;

            bool oldAllow = (bool)e.OldValue;
            bool newAllow = (bool)e.NewValue;

            if (!oldAllow && newAllow)
            {
                grid.Sorting += HandleCustomSorting;
            }
            else
            {
                grid.Sorting -= HandleCustomSorting;
            }
        }

        public static bool ApplySort(DataGrid grid, DataGridColumn column)
        {
            IComparer sorter = GetSorter(column);
            if (sorter == null)
            {
                return false;
            }

            var listCollectionView = CollectionViewSource.GetDefaultView(grid.ItemsSource) as ListCollectionView;
            if (listCollectionView == null)
            {
                throw new Exception("The ICollectionView associated with the DataGrid must be of type, ListCollectionView");
            }

            listCollectionView.CustomSort = new DataGridSortComparer(sorter, column.SortDirection ?? ListSortDirection.Ascending, column.SortMemberPath);
            return true;
        }

        private static void HandleCustomSorting(object sender, DataGridSortingEventArgs e)
        {
            IComparer sorter = GetSorter(e.Column);
            if (sorter == null)
            {
                return;
            }

            var grid = (DataGrid)sender;
            e.Column.SortDirection = e.Column.SortDirection == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
            if (ApplySort(grid, e.Column))
            {
                e.Handled = true;
            }
        }

        private class DataGridSortComparer : IComparer
        {
            private IComparer comparer;
            private ListSortDirection sortDirection;
            private string propertyName;
            private PropertyInfo property;

            public DataGridSortComparer(IComparer comparer, ListSortDirection sortDirection, string propertyName)
            {
                this.comparer = comparer;
                this.sortDirection = sortDirection;
                this.propertyName = propertyName;
            }

            public int Compare(object x, object y)
            {
                PropertyInfo property = this.property ?? (this.property = x.GetType().GetProperty(propertyName));
                object value1 = property.GetValue(x);
                object value2 = property.GetValue(y);

                int result = comparer.Compare(value1, value2);
                if (sortDirection == ListSortDirection.Descending)
                {
                    result = -result;
                }
                return result;
            }
        }
    }
}

あなたのXaml

これは、trilson86のソリューションにも似ているはずです。

<UserControl.Resources>
    <converters:MyComparer x:Key="MyComparer"/>
</UserControl.Resources>
<DataGrid behaviours:DataGridSortBehavior.AllowCustomSort="True" ItemsSource="{Binding MyListCollectionView}">
    <DataGrid.Columns>
        <DataGridTextColumn Header="Test" Binding="{Binding MyValue}" behaviours:DataGridSortBehavior.Sorter="{StaticResource MyComparer}" />
    </DataGrid.Columns>
</DataGrid>
1
Kirk Woll

これが@ trilson86の拡張です ICustomeSorter

ソーターをより一般的に使用できるようにします

このリソースに基づくNumericComparer

http://www.codeproject.com/Articles/11016/Numeric-String-Sort-in-C

public class GenericNumericComparer : ICustomSorter
{
    private PropertyInfo _propertyInfo;
    private Type _objectType;

    public string SortMemberPath { get; set; }

    private readonly NumericComparer _comparer = new NumericComparer();

    public Type ObjectType
    {
        get { return _objectType; }
        set
        {
            _objectType = value;

            if (_objectType != null) _propertyInfo = ObjectType.GetProperty(SortMemberPath);
        }
    }

    private int CompareHelper(object x, object y)
    {
        if (_propertyInfo != null)
        {
            var value1 = _propertyInfo.GetValue(x);
            var value2 = _propertyInfo.GetValue(y);

            return _comparer.Compare(value1, value2);
        }

        return 0;
    }

    public int Compare(object x, object y)
    {
        var i = CompareHelper(x, y);

        if (SortDirection == ListSortDirection.Ascending)
            return i;

        return i*-1;
    }

    public ListSortDirection SortDirection { get; set; }
}
0
ari