Home > database >  Filtering a CollectionView by multiple columns?
Filtering a CollectionView by multiple columns?

Time:01-17

I have a listview that pulls in a bunch of information from a database. There are known values assigned to these that would be useful to sort by, but I only seem to be able to sort by one item at a time. Here is that working code:

private bool prodAreaFilter(object item)
{
    return (item as PCdata).ProductionArea.IndexOf(((ComboBoxItem)prodAreaComboBox.SelectedItem).Content.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
}

private void prodArea_Combo_SelectionChanged(object sender, RoutedEventArgs e)
{
    CollectionView viewPA = (CollectionView)CollectionViewSource.GetDefaultView(lstView.ItemsSource);
    viewPA.Filter = prodAreaFilter;

    CollectionViewSource.GetDefaultView(lstView.ItemsSource).Refresh();

    //MessageBox.Show(((ComboBoxItem)prodAreaComboBox.SelectedItem).Content.ToString());

}

private bool typeFilter(object item)
{
    return (item as PCdata).Type.IndexOf(((ComboBoxItem)typeComboBox.SelectedItem).Content.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
}

private void type_Combo_SelectionChanged(object sender, RoutedEventArgs e)
{
    CollectionView viewType = (CollectionView)CollectionViewSource.GetDefaultView(lstView.ItemsSource);
    viewType.Filter = typeFilter;

    CollectionViewSource.GetDefaultView(lstView.ItemsSource).Refresh();

    //MessageBox.Show(((ComboBoxItem)typeComboBox.SelectedItem).Content.ToString());

}

Instead of filtering together, they overwrite the other one. I think this is caused by them each having their own filtering logic, so I tried to combine it all into one filter:

private bool Filter(object item)
{
    return (item as PCdata).Type.IndexOf(((ComboBoxItem)typeComboBox.SelectedItem).Content.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
    (item as PCdata).ProductionArea.IndexOf(((ComboBoxItem)prodAreaComboBox.SelectedItem).Content.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
}


private void prodArea_Combo_SelectionChanged(object sender, RoutedEventArgs e)
{
    CollectionView viewPA = (CollectionView)CollectionViewSource.GetDefaultView(lstView.ItemsSource);
    viewPA.Filter = Filter;

    CollectionViewSource.GetDefaultView(lstView.ItemsSource).Refresh();

    //MessageBox.Show(((ComboBoxItem)prodAreaComboBox.SelectedItem).Content.ToString());

}

//private bool typeFilter(object item)
//{
//    return (item as PCdata).Type.IndexOf(((ComboBoxItem)typeComboBox.SelectedItem).Content.ToString(), StringComparison.OrdinalIgnoreCase) >= 0;
//}

private void type_Combo_SelectionChanged(object sender, RoutedEventArgs e)
{
    CollectionView viewType = (CollectionView)CollectionViewSource.GetDefaultView(lstView.ItemsSource);
    viewType.Filter = Filter;

    CollectionViewSource.GetDefaultView(lstView.ItemsSource).Refresh();

    //MessageBox.Show(((ComboBoxItem)typeComboBox.SelectedItem).Content.ToString());

}

But I'm struggling on making it work because the object items are different and I can't return multiple items. Am I on the right path here or is there a better way to do it?

CodePudding user response:

One example of filtering. Maybe this will help you. If you have a button for filtering by "Name" and "Age"

private void btnFilterName_Click(object sender, RoutedEventArgs e)
{
    CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(myCollectionView);
    view.Filter = obj => ((MyDataModel)obj).Name.Contains(txtFilterName.Text);
}

private void btnFilterAge_Click(object sender, RoutedEventArgs e)
{
    CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(myCollectionView);
    view.Filter = obj => ((MyDataModel)obj).Age == Convert.ToInt32(txtFilterAge.Text);
}

To filter by multiple columns, you can use the "&&" operator to combine multiple filter conditions in the same event.

private void btnFilterNameAge_Click(object sender, RoutedEventArgs e)
{
    CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(myCollectionView);
    view.Filter = obj => ((MyDataModel)obj).Name.Contains(txtFilterName.Text) && ((MyDataModel)obj).Age == Convert.ToInt32(txtFilterAge.Text);
}

To clear the filter, set the filter property to null in the button click event.

private void btnClearFilter_Click(object sender, RoutedEventArgs e)
{
    CollectionView view = (CollectionView)CollectionViewSource.GetDefaultView(myCollectionView);
    view.Filter = null;
}

CodePudding user response:

A filter is a predicate which is applied for every item in your collection.

If it returns true, you get to see that item. If it returns false then you do not.

That filtering logic is run every time you add or remove an item or force refresh() on the collection. Unless you make it use live filtering, in which case when anything changes in the collection the whole thing is checked again.

All of which means that collectionview filtering is quite an expensive thing to use. You should carefully consider just how many items you have in your base collection. If there are a million rows and you have a user insert or delete rows out that then each time the whole collection is traversed and checked.

I wrote this article on filtering some years back:

https://social.technet.microsoft.com/wiki/contents/articles/26673.wpf-collectionview-tips.aspx

Microsoft have ditched the technet gallery the source was on.

If you wanted flexible filtering based on more than one property then you could use the technique that demonstrates where the predicate itself checks a list of predicates. Want two columns to match, you add 2 predicates to the list. Want three, you add three. Below, criteria is a List. Trueforall returns true if they're all true.

private bool dynamic_Filter(object item)
{
    Person p = item as Person;
    bool isIn = true;
    if (criteria.Count() == 0)
       return isIn;
    isIn = criteria.TrueForAll(x => x(p));

    return isIn;
}

In that sample you can pick from any or all of several criteria. The code that adds the predicates to that list looks like:

        criteria.Clear();

        if (yearsChosen > 0)
        {
            criteria.Add(new Predicate<Person>(x => yearsChosen < (
                (_zeroDay   (_now - x.BirthDate)).Year - 1)
                                                                ));
        }
        if (letterChosen != "Any")
        {
            criteria.Add(new Predicate<Person>(x => x.LastName.StartsWith(letterChosen)));
        }
        if (genderChosen != "Any")
        {
            criteria.Add(new Predicate<Person>(x => x.Gender.Equals(genderChosen.Substring(0, 1))));
        }

        PeopleView.Filter = dynamic_Filter;
        RaisePropertyChanged("PeopleView");
  • Related