備忘録的なSomething

素敵なAnything

【C#,WPF,MVVM】DataGridとかの項目をフィルタリングして表示する

DataGrid や ListView などの ItemsControl に対して、検索結果等で絞り込んで表示したいときは、CollectionViewSource のフィルタリング機能が使用できます。

やり方:

  • VMに CollectionViewSource のプロパティを定義します。
public CollectionViewSource ItemsView { get; } = new CollectionViewSource();
  • xaml では、DataGrid 等の ItemsSrouce に CollectionViewSource の View プロパティをバインドします。
<DataGrid ItemsSource="{Binding ItemsView.View}" />
  • VMのコンストラクタ等で、CollectionViewSource の Source と View.Filter を設定します。
    View.Filter には、object(Source に入れたアイテムの型にキャスト可)を受け取って、表示するかどうかをboolで返す処理を指定します。
    (以下では関数を作成して指定しています)
        // コンストラクタ
        public MainWindowViewModel()
        {
            ItemsView.Source = new[] { "アイテム1", "アイテム2", "アイテム3", };
            ItemsView.View.Filter = ItemsViewFilter;
        }

        // フィルタリング関数
        private bool ItemsViewFilter(object obj)
        {
            var item = obj as string;
            if (表示する条件)
            {
                return true;
            }
            else
            {
                return false;
            }
        }
  • 検索ボタンが押されたタイミングなど、フィルタリングを実行するときはビューの Refresh() 関数を呼びます。
ItemsView.View.Refresh();

以下の記事で紹介した方法と組み合わせると、Enterキーを押したときにフィルタリングをかけることもできます。
【C#,WPF,MVVM】TextBox上でEnterキーが押されたときにコマンドを実行する - 備忘録的なSomething

サンプルコード

このサンプルでは、string のリストから、入力した部分文字列を含む項目を絞り込んで表示します。
MVVMフレームワークとして ReactiveProperty を使用しています。

xaml(Margin等の指定は省略)

<Window x:Class="WpfTestApp.Windows.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" SizeToContent="WidthAndHeight" ResizeMode="NoResize">
    <StackPanel>
        <TextBox Text="{Binding SearchText.Value}" />
        <Button Content="検索" Command="{Binding SearchCommand}" />
        <DataGrid ItemsSource="{Binding ItemsView.View}"
                  AutoGenerateColumns="False"
                  IsSynchronizedWithCurrentItem="False">
            <DataGrid.Columns>
                <DataGridTextColumn Header="世界" Binding="{Binding}" />
            </DataGrid.Columns>
        </DataGrid>
    </StackPanel>
</Window>

IsSynchronizedWithCurrentItemは、デフォルトの True だとなぜか最初の項目が初期選択されてしまうので、嫌な場合は False を指定します。

VM

    public class MainWindowViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        public ReactivePropertySlim<string> SearchText { get; } = new ReactivePropertySlim<string>();
        public ReactiveCommand SearchCommand { get; } = new ReactiveCommand();
        public CollectionViewSource ItemsView { get; } = new CollectionViewSource();

        // コンストラクタ
        public MainWindowViewModel()
        {
            ItemsView.Source = new[] { "Asgard", "Alfheim", "Jotunheim", "Midgard", "Muspelheim", "Nidavellir", "Niflheim", "Svartalfheim", "Vanaheim", };
            ItemsView.View.Filter = ItemsViewFilter;

            SearchCommand.Subscribe(() => ItemsView.View.Refresh());
        }

        // フィルタリング関数
        private bool ItemsViewFilter(object obj)
        {
            if (string.IsNullOrEmpty(SearchText.Value))
            {
                return true;
            }

            var item = obj as string;
            return item.Contains(SearchText.Value);
        }
    }

実行結果

f:id:kmua:20211127164941p:plain
↓(「gard」が含まれる世界を検索)
f:id:kmua:20211127172734p:plain

更新履歴

  • 2021/11/30 IsSynchronizedWithCurrentItem について追記