Wojciech Szabowicz
Wojciech Szabowicz

Reputation: 4198

WPF MVVM How to combine many filters in ICollectionView

I have a question that seems fairly easy but it seems i have a problem with it:

I have a data grid with some data:

            <DataGrid ItemsSource="{Binding Candidates, IsAsync=True}" 
                      AutoGenerateColumns="False" 
                      EnableColumnVirtualization="True" 
                      EnableRowVirtualization="True"
                      VirtualizingStackPanel.VirtualizationMode="Standard"
                      VirtualizingStackPanel.IsVirtualizing="True"
                      CanUserAddRows="false">

                <DataGrid.Columns>
                    <DataGridTextColumn Binding="{Binding Firstname}" Header="Imię" />
                    <DataGridTextColumn Binding="{Binding Lastname}" Header="Nazwisko" />
                    <DataGridTextColumn Binding="{Binding commendation.Name}" Header="Polecenie" />
  </DataGrid.Columns>
</DataGrid>

Now using mvvm and command I am applying some filters on those and it looks like:

    public CatalogViewModel()
    {
        this._catalog = new CatalogContexct();
        this._candidates = this._catalog.Candidates.Include("commendation").ToList();

        var candidates = new ListCollectionView(this._candidates);

        this.Candidates = CollectionViewSource.GetDefaultView(candidates);

        this.FirstameCommand = new RelyCommand(FilterFirstname, param => this._canExecute);
        this.LastnameCommand = new RelyCommand(FilterLastname, param => this._canExecute);
        this.CommendationCommand = new RelyCommand(FilterCommendation, param => this._canExecute);
    }

And filters look like:

    public void FilterFirstname(object obj)
    {
        this.Candidates.Filter += item =>
        {
            Candidate candidate = item as Candidate;
            return candidate.Firstname.Contains(obj.ToString());
        };

        this.Candidates.Refresh();
    }

    public void FilterLastname(object obj)
    {
        this.Candidates.Filter += item =>
        {
            Candidate candidate = item as Candidate;

            if(string.IsNullOrWhiteSpace(candidate.Lastname))
            {
                return false;
            }

            return candidate.Lastname.Contains(obj.ToString());
        };

        this.Candidates.Refresh();
    }

    public void FilterCommendation(object obj)
    {
        this.Candidates.Filter += item =>
        {
            Candidate candidate = item as Candidate;

            if (string.IsNullOrWhiteSpace(candidate.commendation.Name))
            {
                return false;
            }

            return candidate.commendation.Name.Contains(obj.ToString());
        };

        this.Candidates.Refresh();
    }

Now this is basically working as intended BUT it work on each column separately so if I type name it will filter me a name and if I type last name it will filter me last name but it will override name so I wont get exact match for name and last name and I want to aggregate that filters.

Is there any way to aggregate that filters?

Upvotes: 2

Views: 2052

Answers (1)

Kedrzu
Kedrzu

Reputation: 653

Why it does not work?

On each FilterCommendation, FilterLastname,FilterFirstname you are adding another delegate to this.Candidates.Filter. All of them will execute on Refresh but only result from last delegate will be returned. Consider following example:

Predicate<int> tmp = i => true;
tmp += i => i != 0;

Assert.AreEqual(true, tmp(1));
Assert.AreEqual(false, tmp(0));

It will allways return i != 0 because it it the last predicate, but all of them will be executed. There is no 'and' between them.

How you can accomplish your goals?

You can create collection of filters and add or remove from it and refresh view. Full example below:

    Dictionary<string, Predicate<Candidate>> filters
        = new Dictionary<string, Predicate<Candidate>>();

    public CatalogViewModel()
    {
        ...

        Candidates.Filter = FilterCandidates;
    }

    private bool FilterCandidates(object obj)
    {
        Candidate c = (Candidate)obj;
        return filters.Values
            .Aggregate(true,
                (prevValue, predicate) => prevValue && predicate(c));
    }
    public void FilterFirstname(object obj)
    {
        string val = obj.ToString();
        AddFilterAndRefresh(
            "FirstName",
            candidate => candidate.Firstname.Contains(val));
    }

    public void FilterLastname(object obj)
    {
        string val = obj.ToString();
        AddFilterAndRefresh(
            "FirstName",
            candidate => !string.IsNullOrWhiteSpace(candidate.Lastname) && candidate.Lastname.Contains(val));
    }

    public void ClearFilters()
    {
        filters.Clear();
        Candidates.Refresh();
    }

    public void RemoveFilter(string filterName)
    {
        if (filters.Remove(filterName))
        {
            Candidates.Refresh();
        }
    }

    private void AddFilterAndRefresh(string name, Predicate<Candidate> predicate)
    {
        filters.Add(name, predicate);
        Candidates.Refresh();
    }

Upvotes: 5

Related Questions