Фильтрация коллекций и списков с помощью пользовательского вводаFiltering collections and lists through user input

Если в коллекции отображается много элементов или они сильно привязаны к взаимодействию пользователя, внедрение фильтрации будет полезным решением.If your collection displays many items or is heavily tied to user interaction, filtering is a useful feature to implement. Фильтрацию с помощью метода, описанного в этой статье, можно внедрить в большинство элементов управления коллекции, включая ListView, GridView и ItemsRepeater.Filtering using the method described in this article can be implemented to most collection controls, including ListView, GridView, and ItemsRepeater. Для фильтрации коллекции можно использовать множество типов пользовательского ввода, таких как флажки, переключатели и ползунки, но в этой статье основное внимание будет уделено получению пользовательского ввода на основе текста, а также его использованию для обновления ListView в режиме реального времени в соответствии с результатами поиска пользователя.Many types of user input can be used to filter a collection - such as checkboxes, radio buttons, and sliders - but this article will be focusing on taking text-based user input and using it to update a ListView in real time, according to the user's search.

Примечание

В этой статье представлена фильтрация с помощью ListView.This article will focus on filtering with a ListView. Обратите внимание, что метод фильтрации можно также применить к другим элементам управления коллекциями, например GridView, ItemsRepeater или TreeView.Please be aware that the filtering method can also be applied to other collections controls such as GridView, ItemsRepeater, or TreeView.

Настройка пользовательского интерфейса и XAML для фильтрацииSetting up the UI and XAML for filtering

Для реализации фильтрации ваше приложение должно иметь ListView, который должен появиться рядом с текстовым полем или другим элементом управления, позволяющим вводить данные.To implement filtering, your app should have a ListView should appear alongside a TextBox or other control that allows for user input. В качестве фильтра будет использоваться текст, который пользователь вводит в текстовое поле, т. е. будут отображаться только результаты, содержащие запрос ввода/поиска текста.The text that the user types into the TextBox will be used as the filter, i.e. only results containing their text input/search query will appear. По мере ввода пользователем текста в текстовое поле ListView будет постоянно обновляться с помощью отфильтрованных результатов, в частности при каждом изменении текста в текстовом поле, даже если изменена только одна буква, ListView будет проходить через элементы и выполнять фильтрацию с помощью этого условия.As the user types into the TextBox, the ListView will constantly update with filtered results - specifically, everytime the text in the TextBox changes, even if by one letter, the ListView will go through its items and filter with that term.

В приведенном ниже коде показан пользовательский интерфейс с простым ListView и его DataTemplate, а также прилагаемое текстовое поле.The code below shows a UI with a simple ListView and its DataTemplate, along with an accompanying TextBox. В этом примере в ListView представлено коллекцию объектов Person.In this example, the ListView displays a collection of Person objects. Person — это класс, определенный в коде программной части (не показанный в примере кода ниже). Каждый объект Person имеет следующие свойства: FirstName, LastName и Company.Person is a class defined in the code-behind (not shown in code sample below), and each Person object has the following properties: FirstName, LastName, and Company.

Используя текстовое поле, пользователи могут ввести термин для поиска/фильтрации, чтобы отфильтровать список объектов Person по фамилиям.Using the TextBox, users can type a search/filtering term to filter the list of Person objects by last name. Обратите внимание, что текстовое поле привязано к определенному имени (FilterByLName) и имеет собственное событие TextChanged (FilteredLV_LNameChanged).Note that the TextBox is bound to a specific name (FilterByLName) and has its own TextChanged event (FilteredLV_LNameChanged). Привязка имени позволяет получить доступ к содержимому/тексту текстового поля в коде программной части, а событие TextChanged будет срабатывать при каждом вводе текста в текстовое поле, что позволяет выполнять операцию фильтрации после получения пользовательского ввода.The bound name allows us to access the TextBox's content/text in the code-behind, and the TextChanged event will fire whenever the user types in the TextBox, allowing us to perform a filtering operation upon recieving user input.

Чтобы фильтрация сработала, ListView необходимо предоставить источник данных, которым можно управлять в коде программной части, например ObservableCollection<>.For filtering to work, the ListView must have a data source that can be manipulated in the code-behind, such as an ObservableCollection<>. В этом случае в коде программной части ObservableCollection<Person> присваивается свойство ItemsSource ListView.In this case, the ListView's ItemsSource property is assigned to an ObservableCollection<Person> in the code-behind.

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="1*"></ColumnDefinition>
        <ColumnDefinition Width="1*"></ColumnDefinition>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
            <RowDefinition Height="400"></RowDefinition>
            <RowDefinition Height="400"></RowDefinition>
    </Grid.RowDefinitions>

    <ListView x:Name="FilteredListView"
                Grid.Column="0"
                Margin="0,0,20,0">

        <ListView.ItemTemplate>
            <DataTemplate x:DataType="local:Person">
                <StackPanel>
                    <TextBlock Style="{ThemeResource BaseTextBlockStyle}" Margin="0,5,0,5">
                        <Run Text="{x:Bind FirstName}"></Run>
                        <Run Text="{x:Bind LastName}"></Run>
                    </TextBlock>
                    <TextBlock Style="{ThemeResource BodyTextBlockStyle}" Margin="0,5,0,5" Text="{x:Bind Company}"/>
                </StackPanel>
            </DataTemplate>
        </ListView.ItemTemplate>

    </ListView>

    <TextBox x:Name="FilterByLName" Grid.Column="1" Header="Last Name" Width="200"
             HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,20"
             TextChanged="FilteredLV_LNameChanged"/>
</Grid>

Фильтрация данныхFiltering the data

Запросы LINQ позволяют группировать, упорядочивать и выбирать определенные элементы в коллекции.Linq queries allow you to group, order, and select certain items in a collection. Для фильтрации списка мы создадим запрос LINQ, который будет выбирать только те условия, которые соответствуют введенному пользователем условию поискового запроса или фильтрации в текстовом поле FilterByLName.For filtering a list, we will be constructing a Linq query that only selects terms that match the user-inputted search query/filtering term, entered in the FilterByLName TextBox. Результат запроса можно назначить объекту коллекции IEnumerable.The query result can be assigned to an IEnumerable collection object. После создания этой коллекции мы можем использовать ее для сравнения с исходным списком, удаляя элементы, которые не совпадают, и добавляя обратно элементы, которые совпадают (при Backspace).Once we have this collection, we can use it to compare with the original list, removing items that don't match and adding back items that do match (in case of a backspace).

Примечание

Чтобы обеспечить анимирование ListView при добавлении и вычитании элементов самым удобным и понятным способом, важно удалять и добавлять элементы в саму коллекцию ItemsSource ListView, а не создавать новую коллекцию отфильтрованных объектов и присваивать ее свойству ItemsSource ListView.In order for the ListView to animate in the most intuitive way when adding and subtracting items, it's important to remove and add items to the ListView's ItemsSource collection itself, rather than create a new collection of filtered objects and assign that to the ListView's ItemsSource property.

Для начала необходимо инициализировать исходный источник данных в отдельной коллекции, например List<T> или ObservableCollection<T>.To start, we'll need to initialize our original data source in a separate collection, such as a List<T> or ObservableCollection<T>. В этом примере у нас есть List<Person>, называемый People, который содержит все объекты Person, показанные в ListView (на фрагменте кода ниже не показано заполнение/инициализацию этого списка).In this example, we have an List<Person> called People, that holds all of the Person objects shown in the ListView (population/initialization of this List is not shown in the code snippet below). Нам также понадобится список для хранения отфильтрованных данных, который будет меняться при каждом применении фильтра.We'll also need a list to hold the filtered data, which will constantly change every time a filter is applied. Этим списком будет ObservableCollection<Person>, вызываемый PeopleFiltered. При инициализации он будет иметь то же содержимое, что и People.This will be an ObservableCollection<Person> called PeopleFiltered, and at initialization will have the same contents as People.

Приведенный ниже код выполняет операцию фильтрации с помощью следующих шагов, показанных ниже.The code below performs the filtering operation through the following steps, shown in the code below:

  • Присвойте свойству ItemsSource ListView значение PeopledFiltered.Set the ListView's ItemsSource property to PeopledFiltered.
  • Определите событие TextChanged FilteredLV_LNameChanged() для текстового поля FilterByLName.Define the TextChanged event, FilteredLV_LNameChanged(), for the FilterByLName TextBox. Отфильтруйте данные в этой функции.Inside this function, filter the data.
  • Чтобы отфильтровать данные, воспользуйтесь введенным пользователем поисковым запросом/условием фильтрации, используя FilterByLName.Text.To filter the data, access the user-inputted search query/filtering term through FilterByLName.Text. Используйте запрос LINQ, чтобы выбрать элементы в People, чья фамилия содержит термин FilterByLName.Text, и добавьте соответствующие элементы в коллекцию с именем TempFiltered.Use a Linq query to select the items in People whose last name contains the term FilterByLName.Text, and add those matching items into a collection called TempFiltered.
  • Сравните текущую коллекцию PeopleFiltered с только что отфильтрованными элементами в TempFiltered, при необходимости удаляя и добавляя элементы из PeopleFiltered.Compare the current PeopleFiltered collection with the newly filtered items in TempFiltered, removing and adding items from PeopleFiltered where necessary.
  • По мере удаления и добавления элементов из PeopleFilteredListView будет обновляться и выполнять анимирование соответствующим образом.As items are removed and added from PeopleFiltered, the ListView will update and animate accordingly.
using System.Linq;

public MainPage()
{
   // Define People collection to hold all Person objects. 
   // Populate collection - i.e. add Person objects (not shown)
   IList<Person> People = new List<Person>();

   // Create PeopleFiltered collection and copy data from original People collection
   ObservableCollection<Person> PeopleFiltered = new ObservableCollection<Person>(People);

   // Set the ListView's ItemsSource property to the PeopleFiltered collection
   FilteredListView.ItemsSource = PeopleFiltered;

   // ... 
}

private void FilteredLV_LNameChanged(object sender, TextChangedEventArgs e)
{
   /* Perform a Linq query to find all Person objects (from the original People collection)
   that fit the criteria of the filter, save them in a new List called TempFiltered. */
   List<Person> TempFiltered;
   
   /* Make sure all text is case-insensitive when comparing, and make sure 
   the filtered items are in a List object */
   TempFiltered = people.Where(contact => contact.LastName.Contains(FilterByLName.Text, StringComparison.InvariantCultureIgnoreCase)).ToList();
   
   /* Go through TempFiltered and compare it with the current PeopleFiltered collection,
   adding and subtracting items as necessary: */

   // First, remove any Person objects in PeopleFiltered that are not in TempFiltered
   for (int i = PeopleFiltered.Count - 1; i >= 0; i--)
   {
       var item = PeopleFiltered[i];
       if (!TempFiltered.Contains(item))
       {
           PeopleFiltered.Remove(item);
       }
   }

   /* Next, add back any Person objects that are included in TempFiltered and may 
   not currently be in PeopleFiltered (in case of a backspace) */

   foreach (var item in TempFiltered)
   {
       if (!PeopleFiltered.Contains(item))
       {
           PeopleFiltered.Add(item);
       }
   }
}

Теперь, когда пользователь вводит условия фильтрации в текстовое поле FilterByLName, ListView немедленно обновится, чтобы отображались только те пользователи, чьи фамилии содержат условие фильтрации.Now, as the user types in their filtering terms in the FilterByLName TextBox, the ListView will immediately update to only show the people whose last name contains the filtering term.

Дальнейшие действияNext steps

Получение примера кодаGet the sample code

  • Если у вас установлено приложение XAML Controls Gallery, щелкните здесь, чтобы открыть приложение и увидеть более надежный, подробный пример фильтрации списка на странице ListView.If you have the XAML Controls Gallery app installed, click here to open the app and see a more robust, in-depth example of list filtering on the ListView page.
  • Получите приложение XAML Controls Gallery в Microsoft Store.Get the XAML Controls Gallery app (Microsoft Store).