TOC

This article is currently in the process of being translated into Czech (~99% done).

Ovládací prvek ListView:

Jak na řazení sloupců v ListView

V poslední kapitole jsme viděli, jak bychom mohli snadno třídit ListView z Code-behind, což může stačit jen pro některé případy, ale neumožňuje to o třídění rozhodnout koncovému uživateli. Kromě toho nevidíme žádný údaj o tom, podle kterého sloupce ListView byla data seřazena. V systému Windows a v mnoha uživatelských rozhraních obecně je běžné ilustrovat v seznamu stav řazení vykreslením trojúhelníku vedle názvu sloupce, podle kterého se aktuálně řazení používá.

V tomto článku vám ukáži praktické řešení, které zahrnuje všechny výše uvedené postupy, ale upozorňuji, že některý kód jde trochu nad rámec toho, co jsme se až doposud naučili - a to je důvod, proč má tento článek název "jak-na ...".

Tento článek navazuje na předchozí, ale ještě jednou vysvětlím každou část znova, co znamená. Zde je náš výsledek - ListView s řazením sloupců, včetně vizuální indikace třídícího pole a směru třídění. Uživatel jednoduše klikne na sloupec, podle kterého se řadí, a pokud na stejný sloupec klepne opět, směr řazení se obrátí. Zde je návod, jak to udělat:

XAML

První věc, kterou potřebujeme, je nějaký XAML kód, kde bychom definovali naše uživatelské rozhraní. V současné době to vypadá takto:

<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>

Všimněte si, jak jsem zadal záhlaví pro každý sloupec pomocí elementu GridViewColumnHeader namísto pouhého zadání řetězce. To proto, abych mohl nastavit další vlastnosti, v tomto případě vlastnost Tag, a stejně tak událost Click.

Vlastnost Tag slouží k uložení názvu pole, podle kterého budeme třídit, pokud klepneme na konkrétní sloupec. Toto zajistí událost lvUsersColumnHeader_Click v případě, že se k ní každý ze sloupců přihlásí.

Toto je klíčový koncept XAML. Kromě toho v Code behind navážeme vlastnosti Jméno, věk a sex, což si ukážeme nyní.

Kód na pozadí

V Code-behind se děje poměrně málo věcí. Používám celkem tři třídy, které byste normálně rozdělit do jednotlivých souborů, ale pro zjednodušení, jsem je nechal ve stejném souboru, o délce cca ~ 100 řádků. Nejprve se podíváme na kód a pak vysvětlím, jak to funguje:

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();
		}
	}
}

Dovolte mi začít s vysvětlováním od spodu a pak se pomalu pracovat nahoru s popisem, co se kde děje. Poslední třída v souboru je třída Adorner s názvem SortAdorner. Vše, co tato malá třída dělá, je nakreslit trojúhelník, buď směřující nahoru nebo dolů, v závislosti na směru řazení. WPF používá koncept ozdoby, který umožní malovat věci přes jiné ovládací prvky, a to je přesně to, co zde chceme: Schopnost vykreslit trojúhelník řazení na vrcholu našeho záhlaví sloupce ListView.

SortAdorner funguje tak, že definuje dva geometrické objekty, které se v podstatě používají k popisu 2D obrazců - v tomto případě trojúhelník s hrotem směřujícím nahoru a jeden s hrotem směřujícím dolů. Metoda Geometry.Parse() používá seznam bodů k nakreslení trojúhelníků, a to důkladněji vysvětlím v článku později.

SortAdorner si je vědom směru řazení, protože potřebuje nakreslit správný trojúhelník, ale neví, podle kterého pole třídíme - to zpracovává vrstva řízení.

Třída User je pouze základní třída obsahující informace o uživateli. Některé z těchto informací se používají ve vrstvě uživatelského rozhraní, kde se vážeme na vlastnosti Jméno, Věk a Pohlaví.

Ve třídě Window máme dvě metody: Konstruktor, kde vytvoříme seznam uživatelů a přiřadíme jej ItemsSource našeho ListView a pak zajímavější obslužnou rutinu události kliknutí, která bude reagovat na uživatelovo kliknutí na sloupec. V horní části třídy jsme definovali dvě soukromé proměnné: listViewSortCol a listViewSortAdorner. Ty nám pomohou sledovat, který sloupec je použit pro třídění a kde jsme umístili patřičnou ozdobu (trojúhelník).

V obslužné rutině události lvUsersColumnHeader_Click začneme tím, že získáme odkaz na sloupec, na který uživatel klikl. S tímto způsobem zjistíme, podle které vlastnosti třídy User se budou položky třídit, a to jednoduše tak, že se podíváme vlastnost Tag, kterou jsme definovali v XAML. Pak zkontrolujeme, zda již podle daného sloupce třídíme, a pokud tomu tak je, odstraníme ozdobu (trojúhelník) a vyčistíme aktuální značky řazení.

Následně jsme připraveni určit směr třídění. Výchozí hodnota je „vzestupně“, ale i tak provedeme kontrolu, zda již seřadíme podle sloupce, na který uživatel klikl - pokud tomu tak je, změníme směr na „sestupně“.

Nakonec vytvoříme nový SortAdorner, předáme jej sloupci, na kterém má být vykreslen směr třídění. Přidáme jej do záhlaví sloupce AdornerLayer a na samém konci přidáme SortDescription do ListView, aby bylo jasné, podle které vlastnosti se má třídit a ve kterém směru.

Shrnutí

Gratulujeme, nyní máte plně tříditelné ListView s vizuální indikací třídícího sloupce a směru třídění. V případě, že se chcete dozvědět více o některých konceptech použitých v tomto článku, jako jsou datové vazby, geometrie nebo ListView, pak se prosím podívejte na některé z dalších článků, kde probíráme každou z těchto záležitostí více do hloubky.

This article has been fully translated into the following languages: Is your preferred language not on the list? Click here to help us translate this article into your language!