-->

How do you apply multiple filter functions on one

2019-05-26 03:15发布

问题:

I have been working with a Collection View Source object that is bound to a listview and the CVS is a view on top of Observable Collection of objects.

I know how to apply a filter using the following technique:

cvs.Filter += new FilterEventHandler(SomeFilterFunction);

This works fine when you are only filtering in one function. The problem is when I want to filter on top of the already filtered CVS. If I have another function that filters the objects in the view based on different criteria, the objects are filtered ONLY on the criteria in the second filter and the results of the first filter go away.

Here is some example code to explain my problem:

cvs.Filter += new FilterEventHandler(SomeFilterFunction1);
cvs.Filter += new FilterEventHandler(SomeFilterFunction2);

public void SomeFilterFunction1(object sender, FilterEventArgs e)
{
      SomeObject example = e.Item as SomeObject;
      if(example.Name.Contains("A"))
      {
          e.Accepted = true;
      }
      else
      {
          e.Accepted = false;
      }

}

public void SomeFilterFunction2(object sender, FilterEventArgs e)
{
      SomeObject example = e.Item as SomeObject;
      if(example.Name.Contains("B"))
      {
          e.Accepted = true;
      }
      else
      {
          e.Accepted = false;
      }
}

So in this example, I want only "SomeObjects" with the letters A and B accepted by the filters. My problem is that When make the call cvs+=Filter with the filterfunction2, only Objects Name containing the letter B are accepted, disregarding the objects with letter A. So an Objects name containing the letter B but not A are accepted, when they shouldnt be.

My current solution to this problem has been creating a "Master" filter function that has every filter function inside of it and I run every Object through every filter and if the object makes it through ALL of the filters then it is accepted. This does work but my code is getting crazy now and logic is getting out of control. Does anyone know how to apply a new filter function on the result of the last filter for a CVS? Why doesn't the CVS just do this automatically instead of sending every Object through every filter or am I not thinking about CVS in the right way?

回答1:

The problem apply multiple filter event handlers, is that all the handlers are called and the CollectionViewSource does not take the individual results of e.Accepted into account. The result will always be the value of e.Accepted in the last event handler.

I created a manager class to allow handling of multiple FilterEventHandler functions and handles their results with AND or OR logic. So either all filters result as true, or at least one filter results as true.

public class MultipleFilterHandler
{
    private readonly CollectionViewSource collection;

    public MultipleFilterLogic Operation {get; set; }

    public MultipleFilterHandler(CollectionViewSource collection, MultipleFilterLogic operation)
    {
        this.collection = collection;
        this.Operation = operation;
    }

    public MultipleFilterHandler(CollectionViewSource collection) : 
        this( collection, MultipleFilterLogic.Or)
    {
    }

    private event FilterEventHandler _filter;
    public event FilterEventHandler Filter
    { 
        add
        {
            _filter += value;

            collection.Filter -= new FilterEventHandler(CollectionViewFilter);
            collection.Filter += new FilterEventHandler(CollectionViewFilter);
        }
        remove
        {
            _filter -= value;

            collection.Filter -= new FilterEventHandler(CollectionViewFilter);
            collection.Filter += new FilterEventHandler(CollectionViewFilter);
        }
    }

    private void CollectionViewFilter(object sender, FilterEventArgs e)
    {
        if (_filter == null)
            return;

        foreach (FilterEventHandler invocation in _filter.GetInvocationList())
        {
            invocation(sender, e);

            if ((Operation == MultipleFilterLogic.And && !e.Accepted) || (Operation == MultipleFilterLogic.Or && e.Accepted))
                return;
        }
    }
}

public enum MultipleFilterLogic
{
    And,
    Or
}

Just add and remove the event handlers on the Filter property of the MultipleFilterHandler as needed, and it will manage wiring up the CollectionViewSource.



回答2:

You can do just this:

cvs.Filter += new FilterEventHandler(SomeFilterFunction1);
cvs.Filter += new FilterEventHandler(SomeFilterFunction2);

public void SomeFilterFunction1(object sender, FilterEventArgs e)
{
      SomeObject example = e.Item as SomeObject;
      e.Accepted &= example.Name.Contains("A");
      //if you prefer OR logic use this one:          
      //e.Accepted |= example.Name.Contains("A");

}

public void SomeFilterFunction2(object sender, FilterEventArgs e)
{
      SomeObject example = e.Item as SomeObject;
      e.Accepted &= example.Name.Contains("B");
      //if you prefer OR logic use this one:
      //e.Accepted |= example.Name.Contains("B");
}

What this will do is the do and AND (or OR if you use the |= operator) with the current value in the Accepted property, making your filters work together.

EDIT: This was made in .NET 4.5