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>