Search code examples
c#wpfxamlmvvmdatagrid

WPF: Filtering a dataGrid on the fly


In my WPF Window I have a DataGrid control, with its ItemsSource bound to an ObservableCollection of items (let's say a simple object with a couple properties):

XAML: (Removed some xmlns stuff for brevity)

<Window>
    <Window.Resources>
        <CollectionViewSource x:Key="MyViewSource"
                              Source="{Binding MyItemList}"
                              Filter="MyItemList_Filter"/>
    </Window.Resources>

    <Window.DataContext>
        <!-- Some Ioc stuff -->
    </Window.DataContext>

    <StackPanel>
        <TextBox Text="{Binding TextFilter}" />
        <DataGrid Grid.Row="1" x:Name="dataGrid"
            ItemsSource="{Binding Source={StaticResource MyViewSource}}"
            SelectionUnit="FullRow"
            SelectionMode="Extended"
            CanUserAddRows="False"
            CanUserDeleteRows="False"
            HeadersVisibility="Column" />
    </StackPanel>
</Window>

ViewModel (cs):

public class ViewModel : ViewModelBase // From Galasoft MVVM Light toolkit
{

    #region TextFilter Property
    public const string TextFilterPropertyName = "TextFilter";

    private string _TextFilter;

    public string TextFilter
    {
        get
        {
            return _TextFilter;
        }

        set
        {
            if (_TextFilter == value)
            {
                return;
            }

            _TextFilter = value;

            RaisePropertyChanged(TextFilterPropertyName);
        }
    }
    #endregion // TextFilter Property

    #region MyItemList Property
    public const string MyItemListPropertyName = "MyItemList";

    private ObservableCollection<Item> _MyItemList;

    public ObservableCollection<Item> MyItemList
    {
        get
        {
            return _MyItemList;
        }

        set
        {
            if (_MyItemList == value)
            {
                return;
            }

            _MyItemList = value;

            RaisePropertyChanged(MyItemListPropertyName);
        }
    }
    #endregion // MyItemList Property


}

Filter method, from Window's code behind:

private void MyItemList_Filter(object sender, FilterEventArgs e)
{
    var vm = (ViewModel)this.DataContext;
    var item = (Item)e.Item;
    // ...Simplified...
    e.Accepted = item.PropertyToCheck.Contains(vm.TextFilter);
}

Filtering is applied only when filling MyItemList: how can I make the MyItemList_Filter be called (and DataGrid items be shown/hidden accordingly) on "live" TextFilter change?

Any help would be appreciated


Solution

  • You could (should) move the filtering logic to the view model, e.g.:

    public class ViewModel : ViewModelBase
    {
        public const string TextFilterPropertyName = "TextFilter";
    
        private string _TextFilter;
        public string TextFilter
        {
            get
            {
                return _TextFilter;
            }
            set
            {
                if (_TextFilter == value)
                    return;
                _TextFilter = value;
                RaisePropertyChanged(TextFilterPropertyName);
                Filter();
            }
        }
    
        public const string MyItemListPropertyName = "MyItemList";
    
        private ObservableCollection<Item> _MyItemList;
        public ObservableCollection<Item> MyItemList
        {
            get
            {
                return _MyItemList;
            }
            set
            {
                if (_MyItemList == value)
                    return;
    
                _MyItemList = value;
                RaisePropertyChanged(MyItemListPropertyName);
            }
        }
    
        private ObservableCollection<Item> _filtered;
        public ObservableCollection<Item> FilteredList
        {
            get
            {
                return _filtered;
            }
            set
            {
                if (_filtered == value)
                    return;
    
                _filtered = value;
                RaisePropertyChanged("FilteredList");
            }
        }
    
        private void Filter()
        {
            _filtered.Clear();
            foreach(var item in _MyItemList)
            {
                if (item.PropertyToCheck.Contains(TextFilter))
                    _filtered.Add(item);
            }
        }
    } 
    

    That's where it belongs. Then you don't need to the CollectionViewSource:

    <DataGrid Grid.Row="1" x:Name="dataGrid" ItemsSource="{Binding FilteredList}" ... />