TOC

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

ListView kontrollen:

Hvordan: ListView med kolonnesortering

I sidste afsnit så vi hvordan, vi nemt kunne sortere et ListView fra code-behind, og mens dette er nok i nogle tilfælde, tillader det ikke slutbrugeren at betemme sortereingen. Ud over det, var der ingen indikation om hvilken kolonne, ListView var sorteret efter. I Windows, og i mange brugerinterfaces generelt, er det almindeligt at illustrere sorteringsordnen i en liste ved at tegne en trekant ved siden af kolonnenavnet, der sorteres efter.

I denne hvordan-artikel vil jeg give dig en praktisk løsning, der giver alt ovenstående, men vær opmærksom på, at koden her kommer et stykke ud over det, vi har lært indtil videre - det er derform den har "hvordan" etiketten.

Denne artikel udbygger den foregående, men jeg vil stadig forklare hver del undervejs. Her er vores mål - et ListView med kolonnesortering inklusive visuel indikation af sorteringsfelt og -række. Brugeren klikker bare på en kolonne, der skal sorteres efter, og hvis samme kolonne klikkes igen, skiftes sorteringsrækken. Her er hvordan, det ser ud:

XAML

Det første, vi behøver, er noget XAML til at definere vores brugerinterface. Det ser således ud for øjeblikket:

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

Bemærk hvordan jeg har angivet overskrifter for hver af kolonnerne ved hjælp af et egentligt GridViewColumnHeader element i stedet for bare at angive en streng. Dette er for, at jeg kan sætte yderligere egenskaber, i dette tilfælde Tag egenskaben såvel som Click hændelsen.

Tag egenskaben bruges til at indeholde feltnavnet, der vil blive brugt til sortering, hvis den enkelte kolonne klikkes. Dette gøres i lvUsersColumnHeader_Click hændelsen, som hver af kolonnerne abonnerer på.

Det var det vigtige koncept i XAML, Ud over det, binder vi vores code-behind egenskaber Name, Age og Sex, hvilket vi vil diskuttere nu.

Code-behind

I code-behind sker der ret mange ting. Jeg bruger i alt tre klasser, som du normalt ville dele op i individuelle filer, men for nemheds skyld, har jeg beholdt dem i samme fil, hvilket giver samlet omkring 100 linjer. Først koden, og derefter forklarer jeg hvordan, den virker:

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

Tillad mig at starte fra bunden og bevæge mig opad mens, jeg forklarer hvad, der sker. Den sidste klasse i filen er en Adorner klasse kaldet SortAdorner. Alt, denne lille klasse gør, er at tegne en trekant, der peger enten op eller ned afhængig af sorteringsrækken. WPF bruger adorner-konceptet til at lade dig tegne ting oven på andre kontroller, og det er nøjagtig, hvad vi ønsker her: Muligheden for at tegne en sorteringstrekant ovenpå vores ListView kolonneoverskrift.

SortAdorner virker ved at definere to Geometry objekter, der grundlæggende bruges til at beskrive 2D former - i dette tilfælde en trekant med spidsen opad og en med spidsen nedad. Geometry.Parse() metoden bruger listen med punkter til at tegne trekanterne, hvilket vil blive forklaret mere detaljeret i en senere artikel.

SortAdorner er klar over sorteringsrækken, fordi den skal tegne den korrekte trekant, men den er ikke klar over feltet, vi sorterer efter - dette håndteres af UI laget.

The User class is just a basic information class, used to contain information about a user. Some of this information is used in the UI layer, where we bind to the Name, Age and Sex properties.

In the Window class, we have two methods: The constructor where we build a list of users and assign it to the ItemsSource of our ListView, and then the more interesting click event handler that will be hit when the user clicks a column. In the top of the class, we have defined two private variables: listViewSortCol and listViewSortAdorner. These will help us keep track of which column we're currently sorting by and the adorner we placed to indicate it.

In the lvUsersColumnHeader_Click event handler, we start off by getting a reference to the column that the user clicked. With this, we can decide which property on the User class to sort by, simply by looking at the Tag property that we defined in XAML. We then check if we're already sorting by a column - if that is the case, we remove the adorner and clear the current sort descriptions.

After that, we're ready to decide the direction. The default is ascending, but we do a check to see if we're already sorting by the column that the user clicked - if that is the case, we change the direction to descending.

In the end, we create a new SortAdorner, passing in the column that it should be rendered on, as well as the direction. We add this to the AdornerLayer of the column header, and at the very end, we add a SortDescription to the ListView, to let it know which property to sort by and in which direction.

Summary

Congratulations, you now have a fully sortable ListView with visual indication of sort column and direction. In case you want to know more about some of the concepts used in this article, like data binding, geometry or ListViews in general, then please check out some of the other articles, where each of the subjects are covered in depth.


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!