Dot Net For All

Search, Group ListBox Items Using CollectionViewSource

Have you ever tried of searching the items in ListBox without any button event? In this article we will see how to search Filter the Items in the WPF listbox using CollectionViewSource. In the first part I will discuss about the CollectionViewSource. It’s functionalities and features followed by its working. Then I will use CollectionViewSource to search as well as group items in ListBox as an example. This example can be duplicated for all the items control of WPF  like combobox, datagrid etc.

If you want to know MVVM pattern you can read here.

Explaining CollectionViewSource

CollectionViewSource creates a wrapper around the source collection and separates it from the View. CollectionViewSource has a View property which is used to manipulate the source collection. This separation of the source and view prevents from changing the actual collection.

Since we should be able to remove the coupling of the View and Source using CollectionViewSource, it helps us to bind the same source collection to multiple views.

Search and Group Examples

In this part of the article I will show how we can bind the same source collection.This collection is used to have a search filter and grouping of items on the single view.

Please check the below figure. This will give an idea of what we will achieve.

CollectionViewSource Search and Grouping

 

The left hand side of the figure is ListBox which is used for searching. The right hand side for Grouping. Please note that source collection is same in both of the cases.

Steps To Achieve Searing and Grouping

  <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel  Grid.Column="0" Grid.Row="0" Orientation="Horizontal">
            <TextBlock Text="Search Service:"></TextBlock>
            <TextBox Text="{Binding FirstSearchText,UpdateSourceTrigger=PropertyChanged}" Width="100"></TextBox>
        </StackPanel>
        

        <ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Column="0" Grid.Row="1">
            <ListBox ItemsSource="{Binding FirstCollectionViewSource}" >
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text="{Binding ProcessName}"></TextBlock>
                    </DataTemplate>
                </ListBox.ItemTemplate>
            </ListBox>
        </ScrollViewer>
        <ScrollViewer VerticalScrollBarVisibility="Auto" Grid.Column="1" Grid.Row="1">
            <ListView ItemsSource="{Binding SecondCollectionViewSource}" HorizontalContentAlignment="Stretch" Grid.Column="1" Grid.Row="1">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <UniformGrid Rows="1" Columns="2">
                            <TextBlock Text="{Binding ProcessName}" />
                            <TextBlock Text="{Binding Id,StringFormat=ID: \{0\}}" />
                        </UniformGrid>
                    </DataTemplate>
                </ListView.ItemTemplate>
                <ListView.GroupStyle>
                    <GroupStyle>
                        <GroupStyle.HeaderTemplate>
                            <DataTemplate>
                                <Border BorderBrush="Red" BorderThickness="2" Background="White" Margin="2">
                                    <TextBlock Text="{Binding Name, StringFormat=Priority: {0}}" FontSize="16" FontWeight="Bold" Margin="4" />
                                </Border>
                            </DataTemplate>
                        </GroupStyle.HeaderTemplate>
                    </GroupStyle>
                </ListView.GroupStyle>
            </ListView>
        </ScrollViewer>
    </Grid>

I have created a ListBox and a ListView in the above xaml code. The choice of controls is yours. It can be any control derived from ItemsControl. The first one used to filter the items and the second for grouping of the items.

The bound control in second case (in this example a ListView) rearranges the items to be consistent with the grouping criteria (as well as sorting and filtering if used). However, there is no default visual cue indicating grouping is actually taking place. I have defined a GroupStyle which is used to provide a header to the group. We are binding Name property which is bit misleading. This is actually the property(PriorityClass) which is grouping the data.

Now coming to the ViewModel class. Please check the code below.

   public class TaskViewModel
    {
        private string firstSearchText;
        private string secondSearchText;
        public TaskViewModel()
        {
            AllProcess = new ObservableCollection<Process>(Process.GetProcesses());

            FirstCollectionViewSource = new CollectionViewSource { Source = AllProcess }.View;
            SecondCollectionViewSource = new CollectionViewSource { Source = AllProcess }.View;      

            SecondCollectionViewSource.GroupDescriptions.Add(new PropertyGroupDescription("PriorityClass"));
        }

        private bool DoesCollectionContainName(object processName)
        {
            Process prcName = processName as Process;
            return prcName.ProcessName.ToLower().Contains(FirstSearchText.ToLower());
        }

        public ObservableCollection<Process> AllProcess { get; set; }

        public ICollectionView FirstCollectionViewSource
        {
            get;
            set;
        }

        public ICollectionView SecondCollectionViewSource
        {
            get;
            set;
        }

        public string FirstSearchText
        {
            get
            {
                return firstSearchText;
            }
            set
            {
                firstSearchText = value;
                FirstCollectionViewSource.Filter = DoesCollectionContainName;
            }
        }        
    }

In the above code I am getting all the process of the system. And binding it to a ObservableCollection property named AllProcess. FirstCollectionViewSource  and SecondCollectionViewSource  are the new instances of the CollectionViewSource. These instance I have created in the constructor of the class. These are the properties of ICollectionView type which will prevent from working on the actual source collection.

We can see in the above .xaml that these are the two properties which I have bound to the respective Listboxes. For SecondCollectionViewSource I have created a GroupDescription for the property PriorityLevel. This is a property for Process class.

For FirstCollectionViewSource I have attached a Filter in the FirstSearchText property. This property is bound to the TextBox control. This TextBox control is used to find all the processes which contain the entered text as name. Filter accepts a predicate delegate. That is why we have created a function named DoesCollectionContainName. This function will be used to iterate each element of the collection and return the items which meet the condition.

Run the application and you can see the CollectionViewSource in work.

Conclusion:

In this article we have seen how we can use CollectionViewSource to Filter and group the same source collection.

 

Top career enhancing courses you can't miss

My Learning Resource

Excel your system design interview