3
\$\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>
\$\endgroup\$
  • \$\begingroup\$ i am not really happy with the event, maybe you have a better solution in mind \$\endgroup\$ – Jens Apr 1 '16 at 14:40
  • 2
    \$\begingroup\$ You have a typo in the property name "FilterdItems". \$\endgroup\$ – Sebastian Redl Apr 1 '16 at 14:45
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.

\$\endgroup\$
  • \$\begingroup\$ i will take a look on this \$\endgroup\$ – Jens Apr 11 '16 at 6:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.