通过用户输入筛选集合和列表

如果集合显示许多项或紧密绑定到用户交互,则筛选是一项很有用的可以实施的功能。 使用本文所述方法进行的筛选可以实施到大多数集合控件,其中包括 ListViewGridViewItemsView。 许多类型的用户输入(例如复选框、单选按钮、滑块)可以用来筛选集合,但本文演示如何使用基于文本的用户输入,根据用户的搜索用它来实时更新 ListView。

设置 UI 以便进行筛选

若要实施文本筛选,应用程序将需要 ListViewTextBox 或其他允许用户输入的控件。 用户在 TextBox 中键入的文本用作筛选器;也就是说,只有包含用户文本输入的结果才会显示在 ListView 中。 当用户在 TextBox 中键入时,ListView 会不断更新筛选的结果。

注意

本文演示如何使用 ListView 进行筛选。 然而,演示的筛选也可应用到其他集合控件,例如 GridView、ItemsView 或 TreeView。

以下 XAML 显示一个 UI,其中包含一个简单的 ListView 以及随附的 TextBox。 在本示例中,ListView 显示 Contact 对象的集合。 Contact 是代码隐藏中定义的类,每个 Contact 对象具有以下属性:FirstNameLastNameCompany

用户可以在 TextBox 中键入筛选词,以按姓氏筛选 Contact 对象列表。 TextBox 具有其 x:Name 属性集 (FilterByLastName),因此可以在代码隐藏中访问 TextBox 的 Text 属性。 还可以处理其 TextChanged 事件 (OnFilterChanged)。 每当用户在 TextBox 中键入时,就会发生 TextChanged 事件,以便在接收用户输入时执行筛选操作。

若要进行筛选,ListView 必须有一个可以在代码隐藏中进行操作的数据源,例如 ObservableCollection<T>。 在本例中,我们在代码隐藏中将 ListView 的 ItemsSource 属性分配给了 ObservableCollection<Contact>

提示

这是 WinUI 库应用 的 ListView 页中示例的简化版本。 使用 WinUI 库应用运行和查看完整代码,包括 ListView 的 DataTemplateContact 类。

<Grid>
    <StackPanel Width="300" Margin="24"
                HorizontalAlignment="Left">
        <TextBox x:Name="FilterByLastName"
                 Header="Filter by Last Name"
                 TextChanged="OnFilterChanged"/>
        <ListView x:Name="FilteredListView"
             ItemTemplate="{StaticResource ContactListViewTemplate}"/>
    </StackPanel>
</Grid>

筛选数据

Linq 查询允许对集合中的某些项进行分组、排序和选择操作。 若要筛选列表,请构造一个 Linq 查询,该查询仅选择与用户在 FilterByLastName TextBox 中输入的筛选词匹配的项。 查询结果可以分配给 IEnumerable<T> 集合对象。 有了该集合以后,可以使用它与原始列表进行比较,删除不匹配的项,将确实匹配的项添加回来(在使用了退格键的情况下)。

注意

若要让 ListView 能够在添加和删除项时以最直观的方式进行动画显示,必须在 ListView 的 ItemsSource 集合本身中添加和删除项,而不是创建一个包含已筛选对象的新集合并将其分配给 ListView 的 ItemsSource 属性。

若要开始,您将需要在单独的集合(例如 List<T>)中初始化原始数据源。 本示例中有一个调用 allContactsList<Contact>,其中包含可能显示在 ListView 中的所有 Contact 对象。

还需要一个集合来保存已筛选的数据。每次应用筛选器时,该集合就会更改。 为此,您将使用 ObservableCollection<T>,以便在集合发生更改时通知 ListView 更新。 在本示例中,它是一个调用 contactsFilteredObservableCollection<Person>,并且是 ListView 的 ItemsSource。 初始化时,它将具有与 allContacts 相同的内容。

筛选操作通过以下步骤执行,如以下代码所示:

  • 将 ListView 的 ItemsSource 属性设置为 contactsFiltered
  • FilterByLastName TextBox 处理 TextChanged 事件 (OnFilterChanged)。 在此事件处理程序函数中,筛选数据。
  • 若要筛选数据,请通过 FilterByLastName.Text 属性访问用户输入的筛选词。 使用 Linq 查询在 allContacts 中选择其姓包含 FilterByLastName.Text 中该词的项,然后将这些匹配项添加到名为 filtered 的集合中。
  • 将当前的 contactsFiltered 集合与 filtered 中新筛选的项进行比较,根据需要在 contactsFiltered 中删除和添加项,使其匹配 filtered
  • contactsFiltered 中删除和添加项时,ListView 会进行相应的更新和动画显示。
using System.Linq;

public sealed partial class MainPage : Page
{
// Define Contact collection to hold all Contact objects.
IList<Contact> allContacts = new List<Contact>();
// Define an ObservableCollection<Contact> object to serve as the ListView's
// ItemsSource. This collection will get updated after the filters are used:
ObservableCollection<Contact> contactsFiltered;

public MainPage()
{
   this.InitializeComponent();

   // Populate allContacts collection.
   allContacts.Add(new Contact("Kendall", "Collins", "Adatum Corporation"));
   allContacts.Add(new Contact("Victoria", "Burke", "Bellows College"));
   allContacts.Add(new Contact("Preston", "Morales", "Margie's Travel"));
   allContacts.Add(new Contact("Miguel", "Reyes", "Tailspin Toys"));

   // Populate contactsFiltered with all Contact objects (in this case,
   // allContacts holds all of our Contact objects so we copy them into
   // contactsFiltered). Set this newly populated collection as the
   // ItemsSource for the ListView.
   contactsFiltered = new ObservableCollection<Contact>(allContacts);
   Filtereditemscontrol.itemssource = contactsFiltered;
}

// Whenever text changes in the filtering text box, this function is called:
private void OnFilterChanged(object sender, TextChangedEventArgs args)
{
   // This is a Linq query that selects only items that return true after
   // being passed through the Filter function, and adds all of those
   // selected items to filtered.
   var filtered = allContacts.Where(contact => Filter(contact));
   Remove_NonMatching(filtered);
   AddBack_Contacts(filtered);
}

// The following functions are called inside OnFilterChanged:

// When the text in any filter is changed, perform a check on each item in
// the original contact list to see if the item should be displayed. If the
// item passes the check, the function returns true and the item is added to
// the filtered list. Make sure all text is case-insensitive when comparing.
private bool Filter(Contact contact)
{
   return contact.LastName.Contains
       (FilterByLastName.Text, StringComparison.InvariantCultureIgnoreCase);
}

// These functions go through the current list being displayed
// (contactsFiltered), and remove any items not in the filtered collection
// (any items that don't belong), or add back any items from the original
// allContacts list that are now supposed to be displayed. (Adding/removing
// the items ensures the list view uses the desired add/remove animations.)

private void Remove_NonMatching(IEnumerable<Contact> filteredData)
{
   for (int i = contactsFiltered.Count - 1; i >= 0; i--)
   {
       var item = contactsFiltered[i];
       // If contact is not in the filtered argument list,
       // remove it from the ListView's source.
       if (!filteredData.Contains(item))
       {
           contactsFiltered.Remove(item);
       }
   }
}

private void AddBack_Contacts(IEnumerable<Contact> filteredData)
{
   foreach (var item in filteredData)
   {
       // If the item in the filtered list is not currently in
       // the ListView's source collection, add it back in.
       if (!contactsFiltered.Contains(item))
       {
           contactsFiltered.Add(item);
       }
   }
}
}

现在,当用户在 FilterByLastName TextBox 中键入其筛选字符串时,ListView 会立即进行更新,只显示其姓包含筛选字符串的人员。

获取示例代码

WinUI 3 库应用包括大多数 WinUI 3 控件、特性和功能的交互式示例。 通过 Microsoft Store 获取应用,或在 GitHub 上获取源代码

对于 UWP:WinUI 2 库应用包括大多数 WinUI 2 控件、特性和功能的交互式示例。 通过 Microsoft Store 获取应用,或在 GitHub 上获取源代码。