TOC

This article has been localized into Portuguese by the community.

O controle ListView:

ListView com ordenação de coluna

No último capítulo, vimos como poderíamos classificar facilmente um ListView do Code-behind e, embora isso seja suficiente para alguns casos, ele não permite que o usuário final decida sobre a classificação. Além disso, não havia indicação de qual coluna o ListView estava classificado. No Windows e em muitas interfaces de usuário em geral, é comum ilustrar as direções de classificação em uma lista, desenhando um triângulo ao lado do nome da coluna atualmente usado para classificar.

Neste artigo de instruções, darei a você uma solução prática que nos fornece todos os itens acima, mas tenha em mente que alguns dos códigos aqui vão um pouco além do que aprendemos até agora - é por isso que ele tem rótulo "como fazer".

Este artigo baseia-se no anterior, mas ainda explicarei cada parte à medida que avançamos. Aqui está nosso objetivo - um ListView com ordenação de colunas, incluindo indicação visual de campo e direção de classificação. O usuário simplesmente clica em uma coluna para classificar e, se a mesma coluna for clicada novamente, a direção de classificação é invertida. Veja como fica:

O XAML

A primeira coisa que precisamos é de algum XAML para definir nossa interface de usuário. Atualmente, é assim:

<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 como eu especifiquei cabeçalhos para cada uma das colunas usando um elemento GridViewColumnHeader real em vez de apenas especificar uma string. Isso é feito para que eu possa definir propriedades adicionais, neste caso, a propriedade Tag , bem como o evento Click .

A propriedade Tag é usada para manter o nome do campo que será usado para classificar, se esta coluna específica for clicada. Isso é feito no evento lvUsersColumnHeader_Click que cada uma das colunas assina.

Esses foram os principais conceitos do XAML. Além disso, nos vinculamos às propriedades Code-behind Name, Age e Sex, que discutiremos agora.

O código por trás

Em Code-behind, existem algumas coisas acontecendo. Eu uso um total de três classes, que você normalmente dividiria em arquivos individuais, mas por conveniência, eu as mantive no mesmo arquivo, nos dando um total de ~ 100 linhas. Primeiro o código e depois explicarei como 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();
		}
	}
}

Permita-me começar de baixo e, em seguida, subo enquanto explico o que acontece. A última classe no arquivo é uma classe Adorner chamada SortAdorner . Tudo o que essa pequena classe faz é desenhar um triângulo, apontando para cima ou para baixo, dependendo da direção da classificação. O WPF usa o conceito de adornos para permitir que você pinte coisas sobre outros controles, e isso é exatamente o que queremos aqui: A capacidade de desenhar um triângulo de classificação em cima do nosso cabeçalho de coluna ListView.

O SortAdorner funciona definindo dois objetos Geometria , que são basicamente usados para descrever formas 2D - neste caso, um triângulo com a ponta apontando para cima e outro com a ponta apontando para baixo. O método Geometry.Parse () usa a lista de pontos para desenhar os triângulos, que serão explicados mais detalhadamente em um artigo posterior.

O SortAdorner está ciente da direção de classificação, porque ele precisa desenhar o triângulo adequado, mas não está ciente do campo que pedimos - isso é tratado na camada de interface do usuário.

A classe Usuário é apenas uma classe de informação básica, usada para conter informações sobre um usuário. Algumas dessas informações são usadas na camada da interface do usuário, onde nos vinculamos às propriedades Name, Age e Sex.

Na classe Window, temos dois métodos: O construtor onde construímos uma lista de usuários e a atribuímos ao ItemsSource de nosso ListView e, em seguida, o mais interessante manipulador de eventos de clique que será atingido quando o usuário clicar em uma coluna. No topo da classe, definimos duas variáveis privadas: listViewSortCol e listViewSortAdorner . Isso nos ajudará a rastrear qual coluna estamos classificando atualmente e o adorno que colocamos para indicá-la.

No manipulador de eventos lvUsersColumnHeader_Click, começamos obtendo uma referência à coluna em que o usuário clicou. Com isso, podemos decidir qual propriedade na classe User classificar, simplesmente observando a propriedade Tag que definimos em XAML. Em seguida, verificamos se já estamos classificando por uma coluna - se esse for o caso, removemos o adorno e limpamos as descrições de classificação atuais.

Depois disso, estamos prontos para decidir a direção. O padrão é ascendente, mas verificamos se já estamos classificando pela coluna em que o usuário clicou - se esse for o caso, mudamos a direção para descendente.

No final, criamos um novo SortAdorner, passando a coluna na qual ele deve ser renderizado, bem como a direção. Adicionamos isso ao AdornerLayer do cabeçalho da coluna e, no final, adicionamos SortDescription ao ListView, para que ele saiba a qual propriedade classificar e em qual direção.

Resumo

Parabéns, agora você tem um ListView totalmente classificável com indicação visual de coluna e direção de classificação. Caso você queira saber mais sobre alguns dos conceitos usados neste artigo, como vinculação de dados, geometria ou ListViews em geral, verifique alguns dos outros artigos, em que cada um dos assuntos é abordado em profundidade.


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!