TOC

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

El control ListView:

How-to: ListView with column sorting

En el último capítulo vimos cómo podríamos clasificar fácilmente un ListView desde Código subyacente, y aunque esto será suficiente para algunos casos, no permite que el usuario final decidir sobre la clasificación. Además de eso, no había ninguna indicación en qué columna se ordenó ListView. En Windows y en muchas interfaces de usuario en general, es común ilustrar las direcciones de clasificación en una lista dibujando un triángulo al lado del nombre de la columna utilizada actualmente para clasificar.

En este artículo, daré una solución práctica que nos brinda todo lo anterior, pero tenga en cuenta que parte del código aquí va un poco más allá de lo que hemos aprendido hasta ahora, es por eso que tiene la etiqueta de "cómo hacerlo".

Este artículo se basa en el anterior, pero aún explicaré cada parte a medida que avancemos. Este es nuestro objetivo: un ListView con clasificación de columnas, que incluye indicación visual del campo de clasificación y dirección. El usuario simplemente hace clic en una columna para ordenar y si se vuelve a hacer clic en la misma columna, la dirección de clasificación es invertido Así es como se ve:

El XAML

Lo primero que necesitamos es algo de XAML para definir nuestra interfaz de usuario. Actualmente se ve así:

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

Observe cómo he especificado encabezados para cada una de las columnas usando un elemento GridViewColumnHeader real en lugar de solo especificar una cadena. Esto está hecho para poder establecer propiedades adicionales, en este caso la propiedad Etiqueta y el evento Clic .

La propiedad Etiqueta se usa para contener el nombre del campo que se usará para ordenar, si se hace clic en esta columna en particular. Esto se realiza en el evento lvUsersColumnHeader_Click al que se suscribe cada una de las columnas.

Ese fue el concepto clave del XAML. Además de eso, nos unimos a nuestras propiedades de Código subyacente Nombre, Edad y Sexo, que discutiremos ahora.

El Código subyacente

En el código subyacente, están sucediendo algunas cosas. Utilizo un total de tres clases, que normalmente se dividirían en archivos individuales, pero por  conveniencia, los he mantenido en el mismo archivo, dándonos un total de ~ 100 líneas. Primero el código y luego explicaré cómo funciona:

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

Permítame comenzar desde abajo y luego subir mientras explico lo que sucede. La última clase en el archivo es una clase Adorner llamada SortAdorner . Todo lo que hace esta pequeña clase es dibujar un triángulo, ya sea apuntando hacia arriba o hacia abajo, dependiendo de la dirección de clasificación. WPF usa el concepto de adornos para permitirle pintar cosas sobre otros controles, y esto es exactamente lo que queremos aquí: la capacidad de dibujar un triángulo de clasificación en la parte superior de nuestro encabezado de columna ListView.

El SortAdorner funciona definiendo dos objetos Geometry , que se utilizan básicamente para describir formas 2D, en este caso un triángulo con la punta apuntando hacia arriba y otro con la punta apuntando hacia abajo. El método Geometry.Parse () usa la lista de puntos para dibujar los triángulos, que se explicará más a fondo en un artículo posterior.

El SortAdorner es consciente de la dirección de clasificación, porque necesita dibujar el triángulo apropiado, pero no es consciente del campo que pedimos, por esto se maneja en la capa de IU.

La clase Usuario es solo una clase de información básica, utilizada para contener información sobre un usuario. Parte de esta información se usa en la interfaz de la capa usuario, donde nos unimos a las propiedades Nombre, Edad y Sexo.

En la clase Window, tenemos dos métodos: el constructor donde construimos una lista de usuarios y la asignamos a ItemsSource de nuestro ListView, y luego el controlador de eventos de clic más interesante que se aplicará cuando el usuario haga clic en una columna. En la parte superior de la clase, hemos definido dos variables privadas: listViewSortCol y listViewSortAdorner . Esto nos ayudará a realizar un seguimiento de la columna por la que estamos clasificando actualmente y el adorno que he colocado para indicarlo.

En el controlador de eventos lvUsersColumnHeader_Click, comenzamos obteniendo una referencia a la columna en la que hizo clic el usuario. Con esto, podemos decidir qué propiedad en la clase Usuario para ordenar, simplemente mirando la propiedad Etiqueta que definimos en XAML. Luego verificamos si ya estamos ordenando por una columna, si ese es el caso, eliminamos el adorno y borramos las descripciones de clasificación actuales.

Después de eso, estamos listos para decidir la dirección. El valor predeterminado es ascendente, pero hacemos una comprobación para ver si ya estamos ordenando por la columna que el usuario pulsó, si ese es el caso, cambiamos la dirección a descendente.

Al final, creamos un nuevo SortAdorner, pasando la columna en la que se debe representar, así como la dirección. Agregamos esto al AdornerLayer del encabezado de la columna, y al final, agregamos una SortDescription a ListView, para que sepa qué propiedad ordenar y en qué dirección.

Resumen

Enhorabuena, ahora tiene un ListView completamente ordenado con indicación visual de la columna de clasificación y la dirección. En caso de que quieras saber más sobre algunos de los conceptos utilizados en este artículo, como enlace de datos, geometría o ListViews en general, consulte algunos de los otros artículos, donde cada uno de los temas son tratados en profundidad.


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!