Search code examples
wpfxamlfilterlistboxtogglebutton

Activating filters in a ListBox with some toggle buttons


Ok I have a ListBox binding to a property from the ViewModel. It has been populated with a Caliburn.Micro.BindableCollection like this

public BindableCollection<QueueTask> QueueTasks
        {
            get
            {
                return this._queueProcessor.Queue;
            }
        }

The queue task has some properties like this:

public class QueueTask : PropertyChangedBase
    {
        private StatusCode _status;
        private int _completedPercent;

        public StatusCode Status
        {
            get => _status;
            set
            {
                _status = value;
                this.NotifyOfPropertyChange(() => Status);
            }
        }

        public int CompletedPercent
        {
            get { return _completedPercent; }
            set
            {
                _completedPercent = value;
                this.NotifyOfPropertyChange(() => CompletedPercent);
            }
        }
    }

Now as you can see one of his properties is a StatusCode enum

public enum StatusCode
    {
        Waiting = 1,    
        Processing,    
        Finished,    
        Error
    } 

Now the real question is... How I can (in the View) filter the ListBox to show only a portion of the list based in the StatusCode using 4 ToggleButtons for each of the StatusCode. So if I have pressed (or activate) the button for finished and error only show in the ListBox those who meet that condition. If they are all active, show everyone. And if there is none marked then do not show anything.

I know that it can be acomplished using an ICollectionView. If it can be in xaml better, although it can be in the code behind the View. The idea I had was to have several ICollectionView one binding to the other. As a chain and each toggle button will activate or deactivate the filtering for each one (although I still have to see how to do it even for a single toggle button). I don´t know if this is the best approach so any help is welcome.

Sorry for my english I only speak spanish...


Solution

  • First thanks to Sean Sexton for this post https://wpf.2000things.com/2014/01/14/986-filtering-a-listbox-using-a-collectionviewsource/

    now the solution using CollectionViewSource is simple (more or less). It is implemented in the view with some code.

    First created (the CollectionViewSource is binding to the full queue):

    <Window.Resources>        
            <CollectionViewSource Source="{Binding QueueTasks, Mode=OneWay}" x:Key="FilteredQueueTasks" IsLiveFilteringRequested="True">
                <CollectionViewSource.LiveFilteringProperties>
                    <clr:String>Status</clr:String>
                </CollectionViewSource.LiveFilteringProperties>
            </CollectionViewSource>
        </Window.Resources>
    

    Then bind the ListBox to this CollectionViewSource (it is simplified). Then it only show the tasks already filtered by the CollectionViewSource

    <ListBox Grid.Row="2" ItemsSource="{Binding Source={StaticResource FilteredQueueTasks}}" />
    

    The code behind the view looks like this (is large)

    public partial class QueueView : Window, INotifyPropertyChanged
        {
            public QueueView()
            {
                InitializeComponent();
            }
    
            private void QueueTasks_Filter(object sender, FilterEventArgs e)
            {
                StatusCode status = ((QueueTask) e.Item).Status;
    
                switch (status)
                {
                    case StatusCode.Waiting:
                        e.Accepted = showWaiting;
                        break;
                    case StatusCode.Processing:
                        e.Accepted = showProcessing;
                        break;
                    case StatusCode.Finished:
                        e.Accepted = showFinished;
                        break;
                    case StatusCode.Error:
                        e.Accepted = showWithErrors;
                        break;
                    case StatusCode.Warning:
                        e.Accepted = showWithWarnings;
                        break;
                    default:
                        e.Accepted = false;
                        break;
                }
            }
    
            private bool showWaiting = true;
    
            public bool ShowWaiting
            {
                get => showWaiting;
    
                set
                {
                    showWaiting = value;
                    OnPropertyChanged(nameof(ShowWaiting));
                    ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
                }
            }
    
            private bool showProcessing = true;
    
            public bool ShowProcessing
            {
                get => showProcessing;
    
                set
                {
                    showProcessing = value;
                    OnPropertyChanged(nameof(ShowProcessing));
                    ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
                }
            }
    
            private bool showFinished;
    
            public bool ShowFinished
            {
                get => showFinished;
    
                set
                {
                    showFinished = value;
                    OnPropertyChanged(nameof(ShowFinished));
                    ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
                }
            }
    
            private bool showWithErrors = true;
    
            public bool ShowWithErrors
            {
                get => showWithErrors;
    
                set
                {
                    showWithErrors = value;
                    OnPropertyChanged(nameof(ShowWithErrors));
                    ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
                }
            }
    
            private bool showWithWarnings = true;
    
            public bool ShowWithWarnigs
            {
                get => showWithWarnings;
    
                set
                {
                    showWithWarnings = value;
                    OnPropertyChanged(nameof(ShowWithWarnigs));
                    ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).View.Refresh();
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
    
            private void Window_Initialized(object sender, EventArgs e)
            {
                ((CollectionViewSource)this.Resources["FilteredQueueTasks"]).Filter += QueueTasks_Filter;
            }
        }
    

    Then every ToggleButton attach to it like this:

    <StackPanel Grid.Row="1" HorizontalAlignment="Right" Height="22" VerticalAlignment="Bottom" Orientation="Horizontal" Margin="-1,0,5,5">
                <ToggleButton IsChecked="{Binding ShowWithErrors, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                    <Image Source="Images/Error.png" Margin="1" />
                </ToggleButton>
                <ToggleButton IsChecked="{Binding ShowWithWarnigs, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                    <Image Source="Images/Warning.png" Margin="1" />
                </ToggleButton>
                <ToggleButton IsChecked="{Binding ShowFinished, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                    <Image Source="Images/Complete.png" Margin="1" />
                </ToggleButton>
                <ToggleButton IsChecked="{Binding ShowProcessing, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                    <Image Source="Images/Working.png" Margin="1"/>
                </ToggleButton>
                <ToggleButton IsChecked="{Binding ShowWaiting, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:QueueView}}}">
                    <Image Source="Images/Movies.png" Margin="1" />
                </ToggleButton>
            </StackPanel>
    

    It updates the filter when any of the ToggleButtons changes her state and when any item in the queue update her Status. For the LiveFilter to work the property must implement INotifyPropertyChanged so it can notify the view for changes.