在上一章中,我们看到了如何从Code-behind中轻松地对ListView进行排序,虽然这对某些情况来说已足够,但它不允许最终用户决定排序。除此之外,没有迹象表明ListView排序的列。在Windows和一般的许多用户界面中,通常通过在当前用于排序的列名旁边绘制三角形来说明列表中的排序方向。
在这篇how-to文章中,我将为您提供一个实用的解决方案,为我们提供上述所有内容,但请记住,这里的一些代码有点超出我们迄今为止学到的内容 - 这就是为什么它具有“how-to”的标签。
本文建立在前一篇文章的基础上,但我仍然会在我们进行的过程中解释每个部分。这是我们的目标 - 具有列排序的ListView,包括排序字段和方向的可视指示。用户只需单击要排序的列,如果再次单击相同的列,则排序方向相反。这是它的外观:
我们需要的第一件事是一些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>
请注意我是如何使用实际的GridViewColumnHeader元素为每个列指定标题,而不是仅指定字符串。这样做是为了我可以设置其他属性,在这种情况下是Tag属性以及Click事件。
如果单击此特定列,Tag属性用于保存将用于排序的字段名称。这是在每个列订阅的lvUsersColumnHeader_Click事件中完成的。
这是XAML的关键概念。除此之外,我们绑定到我们现在讨论的Code-behind属性名称,年龄和性别。
在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();
}
}
}
请允许我从底部开始,然后继续努力,同时解释会发生什么。文件中的最后一个类是名为SortAdorner的Adorner类。所有这些小课程都是绘制一个三角形,无论是向上还是向下,取决于排序方向。 WPF使用adorners的概念允许你在其他控件上绘制东西,这正是我们想要的:在ListView列标题之上绘制排序三角形的能力。
SortAdorner通过定义两个Geometry对象来工作,这些对象基本上用于描述2D形状 - 在这种情况下是一个三角形,尖端朝上,另一个尖端朝下。 Geometry.Parse()方法使用点列表绘制三角形,这将在后面的文章中更全面地解释。
SortAdorner知道排序方向,因为它需要绘制正确的三角形,但不知道我们排序的字段 - 这是在UI层中处理的。
User类只是一个基本信息类,用于包含有关用户的信息。其中一些信息在UI层中使用,我们将其绑定到Name,Age和Sex属性。
在Window类中,我们有两个方法:构造函数,我们构建用户列表并将其分配给ListView的ItemsSource,然后是用户单击列时将触及的更有趣的单击事件处理程序。在类的顶部,我们定义了两个私有变量:listViewSortCol和listViewSortAdorner。这些将有助于我们跟踪我们当前正在排序的列和我们放置的装饰器以指示它。
在lvUsersColumnHeader_Click事件处理程序中,我们首先获取用户单击的列的引用。有了这个,我们可以简单地通过查看我们在XAML中定义的Tag属性来决定要对User类中的哪个属性进行排序。然后我们检查我们是否已按列排序 - 如果是这种情况,我们会删除装饰器并清除当前的排序说明。
在那之后,我们准备好决定方向。默认值是升序,但我们会检查是否已经按用户单击的列进行排序 - 如果是这种情况,我们会将方向更改为降序。
最后,我们创建一个新的SortAdorner,传入应该渲染的列以及方向。我们将它添加到列标题的AdornerLayer中,最后,我们向ListView添加一个SortDescription,让它知道要排序的属性和方向。
恭喜,您现在拥有一个完全可排序的ListView,其中包含排序列和方向的可视指示。如果您想更多地了解本文中使用的一些概念,例如数据绑定,几何或ListView,那么请查看其他一些文章,其中深入介绍了每个主题。