Feedback

C# - WPF: ListView per Klick auf Spaltenüberschrift sortieren

Veröffentlicht von am 4/26/2015
(1 Bewertungen)
Der Windows Benutzer ist es gewöhnt mit einem Klick auf eine Spaltenüberschrift deren Daten zu sortieren. Diese angefügten Eigenschaften ermöglichen das auf einfache weiße inkl. Sortierpfeile.

Angegeben werden kann dabei ob die Sortierung per Klick auf eine Spaltenüberschrift möglich ist oder nicht. Dabei kann die Sortierung auch für einzelne Spalten deaktiviert werden.

Testanwendung
Codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
this.TestItems = new ObservableCollection<TestItem>();
this.DataContext = this;
InitializeComponent();

Random rnd = new Random(13);
for (int i = 0; i < 100; ++i)
this.TestItems.Add(new TestItem()
{
ID = i,
Header = rnd.Next() + "_" + i,
});
}

public ObservableCollection<TestItem> TestItems { get; private set; }

private void TextBox_KeyDown(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
this.TestItems.Add(new TestItem()
{
ID = this.TestItems.Count,
Header = (sender as TextBox).Text,
});
}
}
}

public class TestItem
{
public int ID { get; set; }
public string Header { get; set; }
}

XAML
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="287*"/>
</Grid.RowDefinitions>
<StackPanel Margin="5">
<TextBox KeyDown="TextBox_KeyDown" TextWrapping="Wrap" Text="Item hinzufügen (Enter zum einfügen)" Margin="0 0 0 5" />
<CheckBox Name="cb" Content="SortAtHeaderClick" />
<CheckBox Name="cbID" Content="IsSortAtClickEnabled ID" />
<CheckBox Name="cbHeader" Content="IsSortAtClickEnabled Header" />
</StackPanel>

<ListView Margin="5" local:ListViewSort.SortAtHeaderClick="{Binding IsChecked, ElementName=cb}" Grid.Row="1" ItemsSource="{Binding TestItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="ID" DisplayMemberBinding="{Binding ID}" Width="200" local:ListViewSort.IsSortAtClickEnabled="{Binding IsChecked, ElementName=cbID}"/>
<GridViewColumn Header="Titel" DisplayMemberBinding="{Binding Header}" Width="200" local:ListViewSort.IsSortAtClickEnabled="{Binding IsChecked, ElementName=cbHeader}"/>
</GridView>
</ListView.View>
</ListView>
</Grid>

Benötigte Namespaces
System
System.Collections.Generic
System.ComponentModel
System.IO
System.Linq
System.Windows
System.Windows.Controls
System.Windows.Data
System.Windows.Markup
System.Xml
/// <summary>
/// Stellt Hilfsmethoden zum Sortieren der in einer <see cref="System.Windows.Controls.ListView"/> angezeigten Datensätze bereit.
/// </summary>
public class ListViewSort
{
    #region DataTemplates

    /// <summary >Ruft das Template für einen GridViewColumnHeader mit einem hoch-pfeil ab.</summary>
    static readonly DataTemplate HeaderTemplateArrowUp = XamlReader.Load(XmlReader.Create(new StringReader(@"
<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
    <DockPanel LastChildFill=""True"" Width=""{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}"">
        <Path StrokeThickness=""1"" Fill=""Gray"" Data=""M 5,10 L 15,10 L 10,5 L 5,10"" DockPanel.Dock=""Right"" Width=""20"" HorizontalAlignment=""Right"" Margin=""5,0,5,0"" SnapsToDevicePixels=""True""/>
        <TextBlock Text=""{Binding }"" />
    </DockPanel>
</DataTemplate>"))) as DataTemplate;
    /// <summary >Ruft das Template für einen GridViewColumnHeader mit einem runter-pfeil ab.</summary>
    static readonly DataTemplate HeaderTemplateArrowDown = XamlReader.Load(XmlReader.Create(new StringReader(@"
<DataTemplate xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
    <DockPanel LastChildFill=""True"" Width=""{Binding ActualWidth, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type GridViewColumnHeader}}}"">
        <Path StrokeThickness=""1"" Fill=""Gray""  Data=""M 5,5 L 10,10 L 15,5 L 5,5"" DockPanel.Dock=""Right"" Width=""20"" HorizontalAlignment=""Right"" Margin=""5,0,5,0"" SnapsToDevicePixels=""True""/>
        <TextBlock Text=""{Binding }"" />
    </DockPanel>
</DataTemplate>"))) as DataTemplate;

    #endregion

    #region Get/Set for DPs

    /// <summary>
    /// Ruft den Wert der angefügten IsSortAtClickEnabled Abhängigkeitseigenschaft für eine bestimmte <see cref="System.Windows.Controls.GridViewColumn"/> ab.
    /// </summary>
    /// <param name="gvc">Die <see cref="System.Windows.Controls.GridViewColumn"/> deren IsSortAtClickEnabled Wert abgerufen werden soll.</param>
    public static bool GetIsSortAtClickEnabled(GridViewColumn gvc)
    {
        return (bool)gvc.GetValue(IsSortAtClickEnabledProperty);
    }

    /// <summary>
    /// Setzt den Wert der angefügten IsSortAtClickEnabled Abhängigkeitseigenschaft für eine bestimmte <see cref="System.Windows.Controls.GridViewColumn"/>.
    /// </summary>
    /// <param name="gvc">Die <see cref="System.Windows.Controls.GridViewColumn"/> deren Wert gesetzt werdne soll.</param>
    /// <param name="value">Der zu setzende Wert. <c>true</c> wenn das Sortierren der Spalte erlaubt ist; andernfalls <c>false</c></param>
    public static void SetIsSortAtClickEnabled(GridViewColumn gvc, bool value)
    {
        gvc.SetValue(IsSortAtClickEnabledProperty, value);
    }

    /// <summary>
    /// Ruft den Wert der angefügten SortAtHeaderClick Abhängigkeitseigenschaft für eine bestimmte <see cref="System.Windows.Controls.ListView"/> ab.
    /// </summary>
    /// <param name="lv">Die <see cref="System.Windows.Controls.ListView"/> deren SortAtHeaderClick Wert abgerufen werden soll.</param>
    public static bool GetSortAtHeaderClick(ListView lv)
    {
        return (bool)lv.GetValue(SortAtHeaderClickProperty);
    }

    /// <summary>
    /// Setzt den Wert der angefügten SortAtHeaderClick Abhängigkeitseigenschaft für eine bestimmte <see cref="System.Windows.Controls.ListView"/>.
    /// </summary>
    /// <param name="lv">Die <see cref="System.Windows.Controls.ListView"/> deren Wert gesetzt werdne soll.</param>
    /// <param name="value">Der zu setzende Wert. <c>true</c> wenn das Sortierren des ListViews erlaubt ist; andernfalls <c>false</c></param>
    public static void SetSortAtHeaderClick(ListView lv, bool value)
    {
        lv.SetValue(SortAtHeaderClickProperty, value);
    }

    #endregion

    #region DependencyProperties

    /// <summary>
    /// Bezeichnet die angefügte IsSortAtClickEnabled Abhängigkeitseigenschaft.
    /// </summary>
    public static readonly DependencyProperty IsSortAtClickEnabledProperty = DependencyProperty.RegisterAttached("IsSortAtClickEnabled", typeof(bool), typeof(ListViewSort), new PropertyMetadata(true, OnIsSortAtClickEnabledChanged));
    /// <summary>
    /// Bezeichnet die angefügte SortAtHeaderClick Abhängigkeitseigenschaft.
    /// </summary>
    public static readonly DependencyProperty SortAtHeaderClickProperty = DependencyProperty.RegisterAttached("SortAtHeaderClick", typeof(bool), typeof(ListViewSort), new PropertyMetadata(false, OnSortAtHeaderClickChanged));

    #endregion

    #region DP Changed handler

    /// <summary>Wird aufgerufen, wenn sich die IsSortAtClickEnabled-Eigenshcat ändert.</summary>
    private static void OnIsSortAtClickEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if ((bool)e.NewValue == false)
        {
            var col = d as GridViewColumn;

            foreach (var item in _lastClickedHeaders)
            {
                if (item.Value != null && item.Value.Column == col)
                {
                    col.HeaderTemplate = null;
                    ClearSortRules(item.Key.Target as ListView);
                }
            }
        }
    }

    /// <summary>Wird aufgerufen, wenn sich die SortAtHeaderClick Eigenschaft ändert</summary>
    private static void OnSortAtHeaderClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var lv = d as ListView;
        if ((bool)e.NewValue == true)
        {
            _lastClickedHeaders.Add(new WeakReference(lv), null);
            _lastDirections.Add(new WeakReference(lv), ListSortDirectionEx.Ascending);
            lv.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnClicked));
        }
        else
        {
            lv.RemoveHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(ColumnClicked));
            var gv = lv.View as GridView;
            if (gv != null)
                foreach (var col in gv.Columns)
                    col.HeaderTemplate = null;
            ClearSortRules(lv);
            _lastClickedHeaders.Remove(_lastClickedHeaders.FirstOrDefault(x => x.Key.Target == lv).Key);
            _lastDirections.Remove(_lastDirections.FirstOrDefault(x => x.Key.Target == lv).Key);
        }
    }

    #endregion

    /// <summary>Enthält eine Liste der zuletzt angeklickten Spaltenüberschriften.</summary>
    static Dictionary<WeakReference, GridViewColumnHeader> _lastClickedHeaders = new Dictionary<WeakReference, GridViewColumnHeader>();
    /// <summary>Enthält eine Liste der gesetzten Ordnungsreihenfolgen.</summary>
    static Dictionary<WeakReference, ListSortDirectionEx> _lastDirections = new Dictionary<WeakReference, ListSortDirectionEx>();

    /// <summary>Wird aufgerufen, wenn der Benutzer auf eine Spaltenüberschrift in einem ListView klickt, für das Sortierung möglich ist.</summary>
    private static void ColumnClicked(object sender, RoutedEventArgs e)
    {
        GridViewColumnHeader headerClicked = e.OriginalSource as GridViewColumnHeader;
        ListSortDirectionEx direction = ListSortDirectionEx.None;
        var lv = sender as ListView;

        var _lastClickedHeader = _lastClickedHeaders.FirstOrDefault(x => x.Key.Target == sender).Value;
        var _lastDirection = _lastDirections.FirstOrDefault(x => x.Key.Target == sender).Value;

        if (headerClicked != null && GetIsSortAtClickEnabled(headerClicked.Column))
        {
            if (headerClicked.Role != GridViewColumnHeaderRole.Padding)
            {
                if (headerClicked != _lastClickedHeader)
                {
                    direction = ListSortDirectionEx.Ascending;
                }
                else
                {
                    switch (_lastDirection)
                    {
                        case ListSortDirectionEx.Ascending:
                            direction = ListSortDirectionEx.Descending;
                            break;
                        case ListSortDirectionEx.Descending:
                            direction = ListSortDirectionEx.None;
                            break;
                        case ListSortDirectionEx.None:
                            direction = ListSortDirectionEx.Ascending;
                            break;
                    }
                }

                if (direction == ListSortDirectionEx.None)
                {
                    ClearSortRules(lv);
                }
                else
                {
                    Sort((headerClicked.Column.DisplayMemberBinding as Binding).Path.Path, (ListSortDirection)direction, lv);
                }

                switch (direction)
                {
                    case ListSortDirectionEx.Ascending:
                        headerClicked.Column.HeaderTemplate = HeaderTemplateArrowUp;
                        break;
                    case ListSortDirectionEx.Descending:
                        headerClicked.Column.HeaderTemplate = HeaderTemplateArrowDown;
                        break;
                    case ListSortDirectionEx.None:
                        headerClicked.Column.HeaderTemplate = null;
                        break;
                }

                // Remove arrow from previously sorted header
                if (_lastClickedHeader != null && _lastClickedHeader != headerClicked)
                {
                    _lastClickedHeader.Column.HeaderTemplate = null;
                }
                _lastClickedHeaders[_lastClickedHeaders.FirstOrDefault(x => x.Key.Target == sender).Key] = headerClicked;
                _lastDirections[_lastDirections.FirstOrDefault(x => x.Key.Target == sender).Key] = direction;
            }
        }
    }

    /// <summary>Sortiert das angegebene ListView nach der angegebenen Eigenschaft und in der angegebenen Reihenfolge.</summary>
    private static void Sort(string sortBy, ListSortDirection direction, ListView lv)
    {
        ICollectionView dataView = CollectionViewSource.GetDefaultView(lv.ItemsSource);

        dataView.SortDescriptions.Clear();
        SortDescription sd = new SortDescription(sortBy, direction);
        dataView.SortDescriptions.Add(sd);
        dataView.Refresh();
    }
    /// <summary>Setzt die Sortierung für das angegebene ListView zurück.</summary>
    private static void ClearSortRules(ListView lv)
    {
        ICollectionView dataView = CollectionViewSource.GetDefaultView(lv.ItemsSource);
        dataView.SortDescriptions.Clear();
        dataView.Refresh();
    }

    #region Types

    /// <summary>Die Möglichen Sortierreihenfolgen.</summary>
    enum ListSortDirectionEx
    {
        Ascending = ListSortDirection.Ascending,
        Descending = ListSortDirection.Descending,
        None,
    }

    #endregion
}

1 Kommentare zum Snippet

Gomi schrieb am 10/4/2017:
Perfekt! Funktioniert super!
 

Logge dich ein, um hier zu kommentieren!