ユーザー入力によるコレクションとリストのフィルター処理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. この記事で説明する方法を使用したフィルター処理は、ListViewGridViewItemsRepeater など、ほとんどのコレクション コントロールに実装できます。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.

フィルター処理のための UI と XAML の設定Setting up the UI and XAML for filtering

フィルター処理を実装するには、アプリに ListView を用意し、TextBox や、ユーザー入力を受け付けるその他のコントロールの近くに表示する必要があります。To implement filtering, your app should have a ListView should appear alongside a TextBox or other control that allows for user input. ユーザーが TextBox に入力するテキストがフィルターとして使用され、テキスト入力/検索クエリを含む結果のみが表示されます。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. ユーザーが TextBox に入力すると、ListView は常に、フィルター処理された結果で更新されます。具体的には、TextBox 内のテキストが 1 文字でも変更されるたびに、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、付随する TextBox を含む UI を示しています。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.

ユーザーは TextBox を使用して検索/フィルター条件を入力し、Person オブジェクトのリストを姓でフィルター処理できます。Using the TextBox, users can type a search/filtering term to filter the list of Person objects by last name. TextBox は特定の名前 (FilterByLName) にバインドされ、独自の TextChanged イベント (FilteredLV_LNameChanged) を備えていることに注意してください。Note that the TextBox is bound to a specific name (FilterByLName) and has its own TextChanged event (FilteredLV_LNameChanged). バインドされた名前によって、分離コードで TextBox の内容/テキストにアクセスできます。また、ユーザーが TextBox に入力するたびに 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.

フィルター処理が機能するためには、分離コードで操作できる ObservableCollection<> などのデータ ソースが ListView に必要です。For filtering to work, the ListView must have a data source that can be manipulated in the code-behind, such as an ObservableCollection<>. この場合、ListView の ItemsSource プロパティが分離コードの ObservableCollection<Person> に割り当てられます。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. リストをフィルター処理するために、FilterByLName TextBox にユーザーが入力した検索クエリ/フィルター条件に一致する項目のみを選択する Linq クエリを作成します。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. このコレクションを取得したら、それを使用して元のリストと比較し、一致しない項目を削除し、一致する項目を追加し直します (バックスペースの場合)。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 をアニメーション表示するためには、フィルター処理されたオブジェクトの新しいコレクションを作成して ListView の ItemsSource プロパティに割り当てるのではなく、ListView の ItemsSource コレクション自体の項目を削除または追加することが重要です。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>. この例では、People という名前の List<Person> があり、ListView に示されるすべての Person オブジェクトがここに保持されます (このリストの事前設定/初期化は次のコード スニペットに示されていません)。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. これは PeopleFiltered という名前の ObservableCollection<Person> であり、初期化時に 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:

  • ListView の ItemsSource プロパティを PeopledFiltered に設定します。Set the ListView's ItemsSource property to PeopledFiltered.
  • FilterByLName TextBox の TextChanged イベント FilteredLV_LNameChanged() を定義します。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 クエリを使用して、条件 FilterByLName.Text が姓に含まれる People の項目を選択し、一致した項目を 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.
  • 項目が PeopleFiltered から削除および追加されると、ListView が更新され、更新内容に応じてアニメーション表示されます。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 TextBox にフィルター条件を入力すると、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