This article is currently in the process of being translated into Polish (~99% done).
How-to: ListView with column sorting
W ostatnim rozdziale widzieliśmy, w jaki sposób możemy łatwo sortować ListView z poziomu Code-behind, i choć w niektórych przypadkach jest to wystarczające, nie pozwala to użytkownikowi końcowemu na decydowanie o sortowaniu. Poza tym, nie było wskazania, według której kolumny posortowany został ListView. W systemie Windows i ogólnie w wielu interfejsach użytkownika ogólnie rzecz biorąc, często ilustruje się kierunki sortowania na liście, rysując trójkąt obok nazwy kolumny aktualnie używanej do sortowania.
W tym artykule przedstawię praktyczne rozwiązanie, które daje nam wszystkie powyższe, ale należy pamiętać, że część kodu tutaj wykracza nieco poza to, czego nauczyliśmy się do tej pory - dlatego etykieta "jak to zrobić". wykracza poza to, czego nauczyliśmy się do tej pory - dlatego ma etykietę "jak to zrobić".
Ten artykuł opiera się na poprzednim, ale nadal będę wyjaśniał każdą część w miarę postępów. Oto nasz cel - widok listy z sortowaniem kolumn, w tym wizualne wskazanie pola i kierunku sortowania. Użytkownik po prostu klika kolumnę do sortowania, a jeśli ta sama kolumna zostanie kliknięta ponownie, kierunek sortowania zostanie odwrócony. Oto jak to wygląda:

Oto XAML
Pierwszą rzeczą, której potrzebujemy, jest trochę XAML, aby zdefiniować nasz interfejs użytkownika. Obecnie wygląda on następująco:
<Window x:Class="WpfTutorialSamples.ListView_control.ListViewColumnSortingSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ListViewColumnSortingSample" Height="200" Width="350">
<Grid Margin="10">
<ListView Name="lvUsers">
<ListView.View>
<GridView>
<GridViewColumn Width="120" DisplayMemberBinding="{Binding Name}">
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Name" Click="lvUsersColumnHeader_Click">Name</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding="{Binding Age}">
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Age" Click="lvUsersColumnHeader_Click">Age</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
<GridViewColumn Width="80" DisplayMemberBinding="{Binding Sex}">
<GridViewColumn.Header>
<GridViewColumnHeader Tag="Sex" Click="lvUsersColumnHeader_Click">Sex</GridViewColumnHeader>
</GridViewColumn.Header>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
Zwróć uwagę, jak określiłem nagłówki dla każdej z kolumn, używając elementu GridViewColumnHeader, zamiast po prostu określać ciąg znaków. Dzięki temu mogę ustawić dodatkowe właściwości - w tym przypadku właściwość Tag jak i zdarzenie Click.
Właściwość Tag jest używana do przechowywania nazwy pola, które będzie używane do sortowania, jeśli ta konkretna kolumna zostanie kliknięta. Odbywa się to w przypadku wydarzenia lvUsersColumnHeader_Click, którego subskrybuje każda z kolumn.
To były kluczowe koncepcje XAML. Poza tym wiążemy się z naszymi właściwościami Code-behind Nazwa, Wiek i Płeć, które teraz omówimy.
Kod źródłowy
W Kodzie źródłowym dzieje się całkiem sporo rzeczy. Używam w sumie trzech klas, które normalnie można by podzielić na osobne pliki, ale dla wygody trzymam je w tym samym pliku, co daje nam w sumie ~100 linii. Najpierw kod, a następnie wyjaśnię, jak to działa:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Media;
namespace WpfTutorialSamples.ListView_control
{
public partial class ListViewColumnSortingSample : Window
{
private GridViewColumnHeader listViewSortCol = null;
private SortAdorner listViewSortAdorner = null;
public ListViewColumnSortingSample()
{
InitializeComponent();
List<User> items = new List<User>();
items.Add(new User() { Name = "John Doe", Age = 42, Sex = SexType.Male });
items.Add(new User() { Name = "Jane Doe", Age = 39, Sex = SexType.Female });
items.Add(new User() { Name = "Sammy Doe", Age = 13, Sex = SexType.Male });
items.Add(new User() { Name = "Donna Doe", Age = 13, Sex = SexType.Female });
lvUsers.ItemsSource = items;
}
private void lvUsersColumnHeader_Click(object sender, RoutedEventArgs e)
{
GridViewColumnHeader column = (sender as GridViewColumnHeader);
string sortBy = column.Tag.ToString();
if(listViewSortCol != null)
{
AdornerLayer.GetAdornerLayer(listViewSortCol).Remove(listViewSortAdorner);
lvUsers.Items.SortDescriptions.Clear();
}
ListSortDirection newDir = ListSortDirection.Ascending;
if(listViewSortCol == column && listViewSortAdorner.Direction == newDir)
newDir = ListSortDirection.Descending;
listViewSortCol = column;
listViewSortAdorner = new SortAdorner(listViewSortCol, newDir);
AdornerLayer.GetAdornerLayer(listViewSortCol).Add(listViewSortAdorner);
lvUsers.Items.SortDescriptions.Add(new SortDescription(sortBy, newDir));
}
}
public enum SexType { Male, Female };
public class User
{
public string Name { get; set; }
public int Age { get; set; }
public string Mail { get; set; }
public SexType Sex { get; set; }
}
public class SortAdorner : Adorner
{
private static Geometry ascGeometry =
Geometry.Parse("M 0 4 L 3.5 0 L 7 4 Z");
private static Geometry descGeometry =
Geometry.Parse("M 0 0 L 3.5 4 L 7 0 Z");
public ListSortDirection Direction { get; private set; }
public SortAdorner(UIElement element, ListSortDirection dir)
: base(element)
{
this.Direction = dir;
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
if(AdornedElement.RenderSize.Width < 20)
return;
TranslateTransform transform = new TranslateTransform
(
AdornedElement.RenderSize.Width - 15,
(AdornedElement.RenderSize.Height - 5) / 2
);
drawingContext.PushTransform(transform);
Geometry geometry = ascGeometry;
if(this.Direction == ListSortDirection.Descending)
geometry = descGeometry;
drawingContext.DrawGeometry(Brushes.Black, null, geometry);
drawingContext.Pop();
}
}
}
Pozwólcie, że zacznę od dołu, a następnie przejdę do góry, wyjaśniając, co się dzieje. Ostatnią klasą w pliku jest klasa Adorner o nazwie SortAdorner. Wszystko, co robi ta mała klasa, to rysowanie trójkąta skierowanego w górę lub w dół, w zależności od kierunku sortowania. WPF używa aby umożliwić malowanie elementów na innych kontrolkach i właśnie tego tutaj chcemy: Możliwość narysowania trójkąta sortowania na górze naszego nagłówka kolumny ListView.
SortAdorner działa poprzez zdefiniowanie dwóch obiektów Geometry, które są zasadniczo używane do opisywania kształtów 2D - w tym przypadku trójkąt z końcówką skierowaną w górę i jeden z końcówką skierowaną w dół. Metoda Geometry.Parse() wykorzystuje listę punktów do narysowania trójkątów, co zostanie zostanie dokładniej wyjaśnione w późniejszym artykule.
SortAdorner jest świadomy kierunku sortowania, ponieważ musi narysować odpowiedni trójkąt, ale nie jest świadomy pola, które zamawiamy według - jest to obsługiwane w warstwie interfejsu użytkownika.
Klasa User jest tylko podstawową klasą informacyjną, używaną do przechowywania informacji o użytkowniku. Niektóre z tych informacji są wykorzystywane w warstwie UI gdzie wiążemy się z właściwościami Nazwa, Wiek i Płeć.
W klasie Window , mamy dwie methody: Konstruktor, w którym tworzymy listę użytkowników i przypisaliśmy do niego ItemsSource naszej ListView a później bardziej interesująca obsługa zdarzenia kliknięcia, która zostanie uruchomiona, gdy użytkownik kliknie kolumnę. W górnej części klasy zdefiniowaliśmy dwie zmienne prywatne: listViewSortCol i listViewSortAdorner.Pomogą nam one śledzić, według której kolumny aktualnie sortujemy, oraz ozdobnik, który umieściliśmy, aby to wskazać.
W obsłudze zdarzenia lvUsersColumnHeader_Click zaczynamy od uzyskania odniesienia do kolumny, którą kliknął użytkownik. Dzięki temu możemy zdecydować, którą w klasie User, po prostu patrząc na właściwość Tag, którą zdefiniowaliśmy w XAML. Następnie sprawdzamy, czy już sortujemy według kolumny - jeśli tak, to usuwamy ją. Jeśli tak, usuwamy adorner i czyścimy bieżące opisy sortowania.
Następnie możemy zdecydować o kierunku sortowania. Domyślnie jest on rosnący, ale sprawdzamy, czy już sortujemy według kolumny, którą kliknął użytkownik - jeśli tak, zmieniamy kierunek na malejący. kliknął użytkownik - jeśli tak jest, zmieniamy kierunek na malejący.
Na koniec tworzymy nowy SortAdorner, przekazując kolumnę, na której ma być renderowany, a także kierunek. Dodajemy to do warstwy AdornerLayer nagłówka kolumny nagłówka kolumny, a na samym końcu dodajemy SortDescription do ListView, aby poinformować go, według której właściwości ma sortować i w jakim kierunku.
Podsumowanie
Gratulacje, masz teraz w pełni sortowalny ListView z wizualnym wskazaniem kolumny i kierunku sortowania. Jeśli chcesz dowiedzieć się więcej o niektórych pojęć użytych w tym artykule, takich jak wiązanie danych, geometria lub ogólnie ListView, zapoznaj się z innymi artykułami, w których każdy z tych tematów został dogłębnie omówiony. tematy są omówione dogłębnie.