TOC

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

The ListView control:

How-to: ListView with column sorting

В останній статті ми побачили, як можна легко сортувати ListView за допомогою програмного коду (Code-behind). Й хоча у деяких випадках цього достатньо, однак це не дозволяє кінцевому користувачеві вказувати параметри сортування, наприклад, колонку за якою йде сортування чи напрямок сортування. У Windows та в багатьох інтерфейсах користувача загалом напрям сортування прийнято ілюструвати трикутником поруч із назвою стовпця, за яким на даний момент виконано сортування.

У цій "Як зробити ..." статті я дам Вам практичне рішення, яке дозволить реалізувати усе вище сказане. Проте майте наувазі, що частина наведеного тут коду виходить за рамки матеріалу вивченого раніше - саме тому стаття має мітку "Як зробити ...".

Ця стаття базується на попередній, але я усе одно пояснюю кожну частину так ніби ми проходимо усе разом. Нашою метою є: ListView із сортування елементів по стовпцях, включаючи візуальну індикацію тих стовпців за якими іде сортування та напряму сортування. Користувач просто клацає мишкою на заголовок стовпця щоб виконати сортування даних по ній і, якщо на цьому ж стовпці клацнути мишкою знову, напрям сортування змінюється на протилежний.

Код XAML

Перше, що ми повинні зробити, це визначити інтерфейс користувача у коді XAML. Цей код виглядає таким чином:

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

Зверніть увагу, як я визначив заголовки для кожного стовпця використовуючи спеціальні елементи GrididViewColumnHeader замість простого текстового визначення у рядку. Це дозволяє встановлювати додаткові властивості, у нашому випадку властивість Tag а також подію Click.

Властивість Tag необхідна для отримання назви поля за яким буде відбуватися сортування, якщо користувач клацне мишкою на цій колонці. Це реалізовано у події lvUsersColumnHeader_Click, на яку підписаний кожен стовпець.

Це є ключовим поняттям XAML. Окрім цього, ми пов'язуємо колонки із програмними компонентами Ім'я (Name), Вік (Age) і Стать (Sex), про які ми поговоримо зараз.

Програмний код (The Code-behind)

У програмному коді є кілька достатньо багато важливих речей. Загалом, я використовую три класи, які можна розділити на окремі файли, але з метою простішого їх представлення вони наведені в одному файлі, що має лише 100 рядків. Спочатку перегляньте код, а потім я поясню, як він працює:

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

Дозвольте розпочати пояснення з кінця й далі продовжувати пояснення як це працює. Останній клас у файлі це клас Adorner названий SortAdorner. Все, що цей маленький клас робить це рисує трикутник з вершиною вверх чи вниз залежно від напрямку сортування. WPF використовує концепцію Adorner, щоб дозволити рисувати речі над іншими елементами керування, й саме це нам необхідне тут: можливість рисувати значок сортування (трикутник) у верхній частині заголовку стовпця ListView.

Клас SortAdorner працює шляхом формування двох об’єктів Geometry , які необхідні для опису двовимірних фігур - в нашому випадку одного трикутника із кінчиком спрямованим вгору та одного із кінчиком спрямованим вниз. Метод Geometry.Parse () використовує список точок (list of points), щоб нарисувати трикутники - це буде пояснено детальніше в наступній статті.

Клас SortAdorner знає напрямок сортування, оскільки це необхідно для правильного рисування трикутника, але він не в курсі за яким стовпцем іде сортування - це обробляється у шарі інтерфейсу користувача (UI layer).

Клас User - це лише базовий інформаційний клас, який містить інформацію про користувача. Частина цієї інформації використовується в шарі інтерфейсу користувача (UI layer), де ми пов'язуємо властивості Name, Age та Sex.

У класі Window нам потрібні два методи: 1. Конструктор, за допомогою якого ми можемо скласти список користувачів та призначити його ItemSource нашого ListView. 2. обробник подій клацання мишки (що дуже цікаво), який буде викликатися коли користувач клацає на заголовку стовпця. У верхній частині класу ми визначили дві приватні змінні: listViewSortCol та listViewSortAdorner . Вони необхідні щоб відслідковувати стовпець, за яким іде сортування, та встановлювати візуальний трикутник (Adorner), щоб вказувати на нього.

В обробнику події lvUsersColumnHeader_Click ми спочатку отримуємо посилання на стовпець, на заголовку якого користувач клацнув мишкою. Завдяки цьому ми можемо визначити за якою властивістю сортувати користувачів - для цього достатньо переглянути властивість Tag, яку ми до цього визначили в XAML. Потім ми перевіряємо, чи вже є сортування за цим стовпцем - якщо це так, видаляємо вказівний трикутник (Adorner) й очищаємо поточні описи сортування.

Після цього визначаємо напрямок сортування. За замовчуванням сортування йде за зростанням, але якщо ми сортуємо стовпець, за яким перед цим вже було виконано сортування, тоді ми змінюємо напрям сортування на протилежний: сортування за спаданням.

На завершення ми створюємо новий вказівний трикутник (SortAdorner), передаючи його стовпчик, на який він повинен вказувати, а також на напрямок. Ми додаємо його до AdornerLayer заголовка стовпця, а в самому кінці додаємо SortDescription до ListView, щоб знати знати за якою властивістю і в якому напрямку виконувати сортування.

Підсумок

Вітаємо, тепер ви отримали ListView з повною підтримкою сортування та з візуальним відображенням стовпця за який іде сортування та напрямку сортування. Якщо ви хочете дізнатися більше про деякі поняття, які використовуються в цій статті, такі як прив'язка даних, геометрія або про ListView в загальному, будь ласка, ознайомтеся з деякими іншими статтями, в яких кожна з цих тем глибоко висвітлена.

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!