4
\$\begingroup\$

I have implemented a filter for filtering a IEnumerable of a specific type.

To accomblish this goal i have created three classes: FilterOption, Filter and the FilterManager.

FilterOption.cs

public class FilterOption<TFilterOptionType>
{
 private bool _isSelected;
 public bool IsSelected
 {
 get { return _isSelected; }
 set
 {
 _isSelected = value;
 this.OnIsSelectedChanged(new EventArgs());
 }
 }
 public TFilterOptionType Option { get; private set; }
 public FilterOption(TFilterOptionType option)
 {
 this.Option = option;
 }
 public event EventHandler IsSelectedChanged;
 protected virtual void OnIsSelectedChanged(EventArgs e)
 {
 IsSelectedChanged?.Invoke(this, e);
 }
}

Filter.cs

public class Filter<TFilterType, TFilterOptionType>
{
 public bool IsActive => this.Options.Any(option => option.IsSelected);
 public string Name { get; private set; }
 public ObservableCollection<TFilterType> FilteredItems{ get; }
 public List<FilterOption<TFilterOptionType>> Options { get; set; }
 private Func<TFilterType, TFilterOptionType> PropertyGetter { get; }
 public Filter(
 string name,
 IEnumerable<TFilterType> items,
 ObservableCollection<TFilterType> filteredItems,
 Func<TFilterType, TFilterOptionType> propertyGetter)
 {
 this.Name = name;
 this.FilteredItems = filteredItems;
 this.PropertyGetter = propertyGetter;
 this.Options =
 items.Select(x => this.PropertyGetter(x))
 .Distinct()
 .OrderBy(option => option)
 .Select(option => new FilterOption<TFilterOptionType>(option))
 .ToList();
 }
 public void Refresh()
 {
 if (this.IsActive)
 {
 foreach (var option in this.Options.Where(option => !option.IsSelected))
 {
 this.FilteredItems.RemoveAll(item => this.PropertyGetter(item).Equals(option.Option));
 }
 }
 }
}

FilterManager.cs

public class FilterManager<TFilterType>
{
 public ObservableCollection<Filter<TFilterType, IComparable>> Filters { get; set; } =
 new ObservableCollection<Filter<TFilterType, IComparable>>();
 public ObservableCollection<TFilterType> FilteredItems { get; } = new ObservableCollection<TFilterType>();
 private IEnumerable<TFilterType> Items { get; }
 public FilterManager(IEnumerable<TFilterType> items)
 {
 this.Items = items;
 this.FilteredItems.Add(this.Items);
 }
 public void AddFilter(string filterName, Func<TFilterType, IComparable> propertyGetter)
 {
 var filter = new Filter<TFilterType, IComparable>(filterName, this.Items, this.FilteredItems, propertyGetter);
 this.Filters.Add(filter);
 foreach (var filterOption in filter.Options)
 {
 filterOption.IsSelectedChanged += this.OptionDisabled;
 }
 }
 private void OptionDisabled(object sender, EventArgs e)
 {
 this.FilteredItems.Clear();
 this.FilteredItems.Add(this.Items);
 foreach (var filter in this.Filters)
 {
 filter.Refresh();
 }
 }
}

The implemented FilterManager provides the option to add any filter. You can do this with calling

YourFilterManagerInstance.AddFilter("Name of the Filter", item => item.ItemProperty);

One FilterManager can be shown in WPF with this XAML (and the right Setting to a proper ViewModel)

<ItemsControl Grid.Row="1" ItemsSource="{Binding BindingToFilterManager.Filters}">
 <ItemsControl.ItemsPanel>
 <ItemsPanelTemplate>
 <StackPanel />
 </ItemsPanelTemplate>
 </ItemsControl.ItemsPanel>
 <ItemsControl.ItemTemplate>
 <DataTemplate>
 <StackPanel>
 <Separator />
 <Expander Width="300">
 <Expander.Header>
 <TextBlock Text="{Binding Name, Converter={StaticResource ToUpperCaseConverter}}"
 FontSize="16" FontWeight="SemiBold" />
 </Expander.Header>
 <ItemsControl ItemsSource="{Binding Options}">
 <ItemsControl.ItemsPanel>
 <ItemsPanelTemplate>
 <StackPanel />
 </ItemsPanelTemplate>
 </ItemsControl.ItemsPanel>
 <ItemsControl.ItemTemplate>
 <DataTemplate>
 <CheckBox Content="{Binding Option}" IsChecked="{Binding IsSelected}"
 Margin="0, 3, 3, 3" />
 </DataTemplate>
 </ItemsControl.ItemTemplate>
 </ItemsControl>
 </Expander>
 </StackPanel>
 </DataTemplate>
 </ItemsControl.ItemTemplate>
</ItemsControl>
asked Apr 1, 2016 at 14:38
\$\endgroup\$
2
  • \$\begingroup\$ i am not really happy with the event, maybe you have a better solution in mind \$\endgroup\$ Commented Apr 1, 2016 at 14:40
  • 2
    \$\begingroup\$ You have a typo in the property name "FilterdItems". \$\endgroup\$ Commented Apr 1, 2016 at 14:45

1 Answer 1

1
\$\begingroup\$

Is there any reason why you can't use the standard WPF filtering? At first glance your code looks extremely complex and bug-prone. I think you should keep it simple and use the existing abstractions instead. In your case I would go for something like:

interface IFilter<T>
{
 bool IsEnabled { get; set; }
 event Action IsEnabledChanged; 
 bool Filter(T item);
}

Then I would use standard CollectionViewSource filtering:

//Bind this collection of filters to the filter list in your UI
Filters = new IFilter<T>[] { .... };
//load or create the collection of actual items you want to filter
var items = new ObserbaleCollection<T>();
//Bind this collection view to the item list in your UI
Items = (ListCollectionView)CollectionViewSource.GetDefaultView(items);
//Set up the filter
//This will only show an item if it passes every enabled filter. 
//You might want to tweak this logic if you need different behaviour
Items.Filter = item => Filters.Where(f => f.IsEnabled).All(f => f.Filter((T)item));

What's left is to wire IsEnabledChanged event to Items.Refresh() method and you should be good to go.

answered Apr 8, 2016 at 10:33
\$\endgroup\$
1
  • \$\begingroup\$ i will take a look on this \$\endgroup\$ Commented Apr 11, 2016 at 6:53

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.