Skip to content

Commit f60dd98

Browse files
authored
edit pass, update to match WinUI Gallery example (#4178)
* edit pass, update to match WinUI Gallery example * update WinUI Gallery tip * fix itemssource link
1 parent 9e6a462 commit f60dd98

File tree

1 file changed

+112
-87
lines changed

1 file changed

+112
-87
lines changed

hub/apps/design/controls/listview-filtering.md

+112-87
Original file line numberDiff line numberDiff line change
@@ -3,142 +3,167 @@ description: Filter the items in your collection through user input.
33
title: Filtering collections
44
label: Filtering collections
55
template: detail.hbs
6-
ms.date: 3/20/2024
6+
ms.date: 3/29/2024
77
ms.topic: article
88
keywords: windows 10, uwp
99
pm-contact: anawish
1010
---
1111

1212
# Filtering collections and lists through user input
13-
If your collection displays many items or is heavily tied to user interaction, filtering is a useful feature to implement. Filtering using the method described in this article can be implemented to most collection controls, including [ListView](/windows/windows-app-sdk/api/winrt/microsoft.UI.Xaml.Controls.ListView), [GridView](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.gridview), and [ItemsRepeater](/uwp/api/microsoft.ui.xaml.controls.itemsrepeater?view=winui-2.2&preserve-view=true). 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.
13+
14+
If your collection displays many items or is heavily tied to user interaction, filtering is a useful feature to implement. Filtering using the method described in this article can be implemented with most collection controls, including [ListView](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.listview), [GridView](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.gridview), and [ItemsView](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.itemsview). Many types of user input can be used to filter a collection - such as checkboxes, radio buttons, and sliders - but this article demonstrates taking text-based user input and using it to update a ListView in real time, according to the user's search.
15+
16+
## Setting up the UI for filtering
17+
18+
To implement text filtering, your app will need a [ListView](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.listview) and a [TextBox](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.textbox) or other control that allows user input. The text that the user types into the TextBox is used as the filter; that is, only results that contain the user's text input will appear in the ListView. As the user types into the TextBox, the ListView constantly updates with filtered results.
1419

1520
> [!NOTE]
16-
> This article will focus on filtering with a ListView. Please be aware that the filtering method can also be applied to other collections controls such as GridView, ItemsRepeater, or TreeView.
21+
> This article demonstrates filtering with a ListView. However, the filtering that is demonstrated can also be applied to other collection controls such as GridView, ItemsView, or TreeView.
1722
18-
## Setting up the UI and XAML for filtering
19-
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. 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.
23+
The following XAML shows a UI with a simple ListView along with an accompanying TextBox. In this example, the ListView displays a collection of `Contact` objects. `Contact` is a class defined in the code-behind, and each `Contact` object has the following properties: `FirstName`, `LastName`, and `Company`.
2024

21-
The code below shows a UI with a simple ListView and its DataTemplate, along with an accompanying TextBox. In this example, the ListView displays a collection of Person objects. 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.
25+
The user can type a filtering term into the TextBox to filter the list of `Contact` objects by last name. The TextBox has it's `x:Name` attribute set (`FilterByLastName`) so you can access the TextBox's [Text](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.textbox.text) property in the code-behind. You also handle it's [TextChanged](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.textbox.textchanged) event (`OnFilterChanged`). The TextChanged event occurs whenever the user types in the TextBox, letting you perform a filtering operation upon receiving user input.
2226

23-
Using the TextBox, users can type a search/filtering term to filter the list of Person objects by last name. Note that the TextBox is bound to a specific name (`FilterByLName`) and has its own TextChanged event (`FilteredLV_LNameChanged`). 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.
27+
For filtering to work, the ListView must have a data source that can be manipulated in the code-behind, such as an [ObservableCollection\<T>](/dotnet/api/system.collections.objectmodel.observablecollection-1). In this case, the ListView's [ItemsSource](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.itemscontrol.itemssource) property is assigned to an `ObservableCollection<Contact>` in the code-behind.
2428

25-
For filtering to work, the ListView must have a data source that can be manipulated in the code-behind, such as an `ObservableCollection<>`. In this case, the ListView's ItemsSource property is assigned to an `ObservableCollection<Person>` in the code-behind.
29+
> [!TIP]
30+
> This is a simplified version of the example in the ListView page of the [WinUI Gallery app](#get-the-sample-code). Use the WinUI Gallery app to run and view the full code, including the ListView's [DataTemplate](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.datatemplate) and the `Contact` class.
2631
2732
```xaml
2833
<Grid>
29-
<Grid.ColumnDefinitions>
30-
<ColumnDefinition Width="1*"></ColumnDefinition>
31-
<ColumnDefinition Width="1*"></ColumnDefinition>
32-
</Grid.ColumnDefinitions>
33-
<Grid.RowDefinitions>
34-
<RowDefinition Height="400"></RowDefinition>
35-
<RowDefinition Height="400"></RowDefinition>
36-
</Grid.RowDefinitions>
37-
38-
<ListView x:Name="FilteredListView"
39-
Grid.Column="0"
40-
Margin="0,0,20,0">
41-
42-
<ListView.ItemTemplate>
43-
<DataTemplate x:DataType="local:Person">
44-
<StackPanel>
45-
<TextBlock Style="{ThemeResource BaseTextBlockStyle}" Margin="0,5,0,5">
46-
<Run Text="{x:Bind FirstName}"></Run>
47-
<Run Text="{x:Bind LastName}"></Run>
48-
</TextBlock>
49-
<TextBlock Style="{ThemeResource BodyTextBlockStyle}" Margin="0,5,0,5" Text="{x:Bind Company}"/>
50-
</StackPanel>
51-
</DataTemplate>
52-
</ListView.ItemTemplate>
53-
54-
</ListView>
55-
56-
<TextBox x:Name="FilterByLName" Grid.Column="1" Header="Last Name" Width="200"
57-
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,20"
58-
TextChanged="FilteredLV_LNameChanged"/>
34+
<StackPanel Width="300" Margin="24"
35+
HorizontalAlignment="Left">
36+
<TextBox x:Name="FilterByLastName"
37+
Header="Filter by Last Name"
38+
TextChanged="OnFilterChanged"/>
39+
<ListView x:Name="FilteredListView"
40+
ItemTemplate="{StaticResource ContactListViewTemplate}"/>
41+
</StackPanel>
5942
</Grid>
43+
6044
```
45+
6146
## Filtering the data
62-
[Linq](/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries) queries allow you to group, order, and select certain items in a collection. 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. The query result can be assigned to an [IEnumerable\<T>](/dotnet/api/system.collections.generic.ienumerable-1) collection object. 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).
47+
48+
[Linq](/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries) queries let you group, order, and select certain items in a collection. To filter a list, you construct a Linq query that selects only items that match the user-entered filtering term, entered in the `FilterByLastName` TextBox. The query result can be assigned to an [IEnumerable\<T>](/dotnet/api/system.collections.generic.ienumerable-1) collection object. Once you have this collection, you 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).
6349

6450
> [!NOTE]
65-
> 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.
51+
> In order for the ListView to animate in the most intuitive way when adding and removing items, it's important to add and remove items in the ListView's [ItemsSource](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.itemscontrol.itemssource) collection itself, rather than create a new collection of filtered objects and assign that to the ListView's ItemsSource property.
6652
67-
To start, we'll need to initialize our original data source in a separate collection, such as a `List<T>` or `ObservableCollection<T>`. 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. This will be an `ObservableCollection<Person>` called `PeopleFiltered`, and at initialization will have the same contents as `People`.
68-
69-
The code below performs the filtering operation through the following steps, shown in the code below:
70-
- Set the ListView's ItemsSource property to `PeopledFiltered`.
71-
- Define the TextChanged event, `FilteredLV_LNameChanged()`, for the `FilterByLName` TextBox. Inside this function, filter the data.
72-
- To filter the data, access the user-inputted search query/filtering term through `FilterByLName.Text`. 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`.
73-
- Compare the current `PeopleFiltered` collection with the newly filtered items in `TempFiltered`, removing and adding items from `PeopleFiltered` where necessary.
74-
- As items are removed and added from `PeopleFiltered`, the ListView will update and animate accordingly.
53+
To start, you'll need to initialize your original data source in a separate collection, such as a [List\<T>](/dotnet/api/system.collections.generic.list-1). In this example, you have a `List<Contact>` called `allContacts` that holds all of the `Contact` objects that can potentially be shown in the ListView.
54+
55+
You'll also need a collection to hold the filtered data, which will constantly change every time a filter is applied. For this, you'll use an [ObservableCollection\<T>](/dotnet/api/system.collections.objectmodel.observablecollection-1) so that the ListView is notified to update whenever the collection changes. In this example, it's an `ObservableCollection<Person>` called `contactsFiltered`, and is the [ItemsSource](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.itemscontrol.itemssource) for the ListView. At initialization, it will have the same contents as `allContacts`.
56+
57+
The filtering operation is performed through these steps, shown in the following code:
58+
59+
- Set the ListView's [ItemsSource](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.itemscontrol.itemssource) property to `contactsFiltered`.
60+
- Handle the [TextChanged](/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.textbox.textchanged) event (`OnFilterChanged`) for the `FilterByLastName` TextBox. Inside this event handler function, filter the data.
61+
- To filter the data, access the user-entered filtering term through the `FilterByLastName.Text` property. Use a [Linq](/dotnet/csharp/programming-guide/concepts/linq/introduction-to-linq-queries) query to select the items in `allContacts` where the last name contains the term in `FilterByLastName.Text`, and add those matching items into a collection called `filtered`.
62+
- Compare the current `contactsFiltered` collection with the newly filtered items in `filtered`, removing and adding items in `contactsFiltered` where necessary to make it match `filtered`.
63+
- As items are removed and added in `contactsFiltered`, the ListView updates and animates accordingly.
7564

7665
```csharp
7766
using System.Linq;
7867

79-
IList<Person> People;
80-
ObservableCollection<Person> PeopleFiltered;
68+
public sealed partial class MainPage : Page
69+
{
70+
// Define Contact collection to hold all Contact objects.
71+
IList<Contact> allContacts = new List<Contact>();
72+
// Define an ObservableCollection<Contact> object to serve as the ListView's
73+
// ItemsSource. This collection will get updated after the filters are used:
74+
ObservableCollection<Contact> contactsFiltered;
8175

8276
public MainPage()
8377
{
84-
// Define People collection to hold all Person objects.
85-
// Populate collection - i.e. add Person objects (not shown)
86-
People = new List<Person>();
78+
this.InitializeComponent();
79+
80+
// Populate allContacts collection.
81+
allContacts.Add(new Contact("Kendall", "Collins", "Adatum Corporation"));
82+
allContacts.Add(new Contact("Victoria", "Burke", "Bellows College"));
83+
allContacts.Add(new Contact("Preston", "Morales", "Margie's Travel"));
84+
allContacts.Add(new Contact("Miguel", "Reyes", "Tailspin Toys"));
85+
86+
// Populate contactsFiltered with all Contact objects (in this case,
87+
// allContacts holds all of our Contact objects so we copy them into
88+
// contactsFiltered). Set this newly populated collection as the
89+
// ItemsSource for the ListView.
90+
contactsFiltered = new ObservableCollection<Contact>(allContacts);
91+
Filtereditemscontrol.itemssource = contactsFiltered;
92+
}
8793

88-
// Create PeopleFiltered collection and copy data from original People collection
89-
PeopleFiltered = new ObservableCollection<Person>(People);
94+
// Whenever text changes in the filtering text box, this function is called:
95+
private void OnFilterChanged(object sender, TextChangedEventArgs args)
96+
{
97+
// This is a Linq query that selects only items that return true after
98+
// being passed through the Filter function, and adds all of those
99+
// selected items to filtered.
100+
var filtered = allContacts.Where(contact => Filter(contact));
101+
Remove_NonMatching(filtered);
102+
AddBack_Contacts(filtered);
103+
}
90104

91-
// Set the ListView's ItemsSource property to the PeopleFiltered collection
92-
FilteredListView.ItemsSource = PeopleFiltered;
105+
// The following functions are called inside OnFilterChanged:
93106
94-
// ...
107+
// When the text in any filter is changed, perform a check on each item in
108+
// the original contact list to see if the item should be displayed. If the
109+
// item passes the check, the function returns true and the item is added to
110+
// the filtered list. Make sure all text is case-insensitive when comparing.
111+
private bool Filter(Contact contact)
112+
{
113+
return contact.LastName.Contains
114+
(FilterByLastName.Text, StringComparison.InvariantCultureIgnoreCase);
95115
}
96116

97-
private void FilteredLV_LNameChanged(object sender, TextChangedEventArgs e)
117+
// These functions go through the current list being displayed
118+
// (contactsFiltered), and remove any items not in the filtered collection
119+
// (any items that don't belong), or add back any items from the original
120+
// allContacts list that are now supposed to be displayed. (Adding/removing
121+
// the items ensures the list view uses the desired add/remove animations.)
122+
123+
private void Remove_NonMatching(IEnumerable<Contact> filteredData)
98124
{
99-
/* Perform a Linq query to find all Person objects (from the original People collection)
100-
that fit the criteria of the filter, save them in a new List called TempFiltered. */
101-
List<Person> TempFiltered;
102-
103-
/* Make sure all text is case-insensitive when comparing, and make sure
104-
the filtered items are in a List object */
105-
TempFiltered = People.Where(contact => contact.LastName.Contains(FilterByLName.Text, StringComparison.InvariantCultureIgnoreCase)).ToList();
106-
107-
/* Go through TempFiltered and compare it with the current PeopleFiltered collection,
108-
adding and subtracting items as necessary: */
109-
110-
// First, remove any Person objects in PeopleFiltered that are not in TempFiltered
111-
for (int i = PeopleFiltered.Count - 1; i >= 0; i--)
125+
for (int i = contactsFiltered.Count - 1; i >= 0; i--)
112126
{
113-
var item = PeopleFiltered[i];
114-
if (!TempFiltered.Contains(item))
127+
var item = contactsFiltered[i];
128+
// If contact is not in the filtered argument list,
129+
// remove it from the ListView's source.
130+
if (!filteredData.Contains(item))
115131
{
116-
PeopleFiltered.Remove(item);
132+
contactsFiltered.Remove(item);
117133
}
118134
}
135+
}
119136

120-
/* Next, add back any Person objects that are included in TempFiltered and may
121-
not currently be in PeopleFiltered (in case of a backspace) */
122-
123-
foreach (var item in TempFiltered)
137+
private void AddBack_Contacts(IEnumerable<Contact> filteredData)
138+
{
139+
foreach (var item in filteredData)
124140
{
125-
if (!PeopleFiltered.Contains(item))
141+
// If the item in the filtered list is not currently in
142+
// the ListView's source collection, add it back in.
143+
if (!contactsFiltered.Contains(item))
126144
{
127-
PeopleFiltered.Add(item);
145+
contactsFiltered.Add(item);
128146
}
129147
}
148+
}
130149
}
131150
```
132151

133-
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.
152+
Now, as the user types in their filtering string in the `FilterByLastName` TextBox, the ListView immediately updates to show only the people whose last name contains the filtering string.
153+
154+
## Get the sample code
155+
156+
> [!div class="nextstepaction"]
157+
> [Open the WinUI 3 Gallery app and see the ListView in action](winui3gallery:/item/ItemsView).
158+
159+
[!INCLUDE [winui-3-gallery](../../../includes/winui-3-gallery.md)]
160+
161+
> For UWP: [!INCLUDE [winui-2-gallery](../../../includes/winui-2-gallery.md)]
134162
135-
## Next steps
136163

137-
### Get the sample code
138-
> - [Open the WinUI 2 Gallery app and see the ListView](winui2gallery:/item/ListView) in action. [!INCLUDE [winui-2-gallery](../../../includes/winui-2-gallery.md)]
139-
- Get the [WinUI 2 Gallery app (Microsoft Store)](https://www.microsoft.com/store/productId/9MSVH128X2ZT).
164+
## Related articles
140165

141-
### Related articles
142166
- [Lists](lists.md)
167+
- [Items view](itemsview.md)
143168
- [List view and grid view](listview-and-gridview.md)
144169
- [Collection commanding](collection-commanding.md)

0 commit comments

Comments
 (0)