ListView is one of the better control in WPF which have lots of flexibilities and empowers to build Grid like control easily. In this article I will discuss how easily you could use ListView to create Grid with Filtering, Grouping, Sorting, Navigating or even with In Place Editors to edit control. This is all in one sample for you.
Introduction
If you are working with WPF for long, you might already have come
across with
ICollectionView. It is the primary Data object for any WPF
list controls (like
ComboBox,
ListBox,
ListView etc) that allows
flexibilities like Sorting, Filtering, Grouping, Current Record
Management etc. Thus it ensures that all the related information like
filtering, sorting etc is decoupled from the actual control. It is
been very popular to those working with data object because of inbuilt
support for all WPF List controls. So I thought I would consider to
describe it a bit so that people might easily plug in the same to their
own solution.
What is a CollectionView ?
It is a layer that runs over the Data Objects which allows you to
define rules for
Sorting,
Filtering,
Grouping etc and manipulate the
display of data rather than modifying the actual data objects.
Therefore in other words, a
CollectionView is a class which takes care
of the View totally and giving us the capability to handle certain
features incorporated within it.
How to Get a CollectionView ?
Practically speaking, getting a
CollectionView from an Enumerable the
most easiest thing I ever seen in WPF. Just you need to pass the
Enumerable to
CollectionViewSource.GetDefaultView. Thus rather than
defining
this.ListboxControl.ItemsSource = this.Source;
you need to write :
this.ListboxControl.ItemsSource = CollectionViewSource.GetDefaultView(this.Source);
The List will get the
CollectionView it requires.

So the CollectionView actually separates the View object List Control
with the actual DataSource and hence gives an interface to manipulate
the data before reflecting to the View objects. Now let us look how to
implement the basic features for the ICollectionView.
Sorting
Sorting can be applied to the
CollectionView in a very easy way. You
need to add a
SortDescription to the CollectionView. The
CollectionView actually maintains a stack of SortDescription objects, each of them
being a Structure can hold information of a Column and the Direction of
Sorting. You can add them in the collectionView to get desired output.
Say I store the
CollectionView as a property :
ICollectionView Source { get; set; }
Now if you want to sort the existing collection :
this.Source.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Descending));
Hence the CollectionView will be sorted based on Name and in Descending order.
Note : The default behavior of
CollectionView automatically Refresh
when a new
SortDescription is added to it. For performance issue, you
might use
DeferRefresh() if you want to refresh only once to add
SortDescription more than once.
Grouping
You can create custom group for
ICollectionView in the same way as you
do for sorting. To create Group of elements you need to use
GroupStyle to define the Template for the Group and to show the name of the Group
in Group Header.
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
Here we define the Group
HeaderTemplate for each groups so that the
TextBlock shows the name of the Group item by which the grouping is
made. You can specify more than one Grouping information for a single
collection. To group a collection you need to use :
this.Source.GroupDescriptions.Add(new PropertyGroupDescription("Department"));
Note : Grouping turns off virtualization. So if you are dealing with
large amount of data, Grouping may lead to performance issue.
You can apply custom grouping as well by defining
IValueConverter in
PropertyGroupDescription as well.
Filtering
Filtering requires a delegate (Predicate) based on which the filter
will occur. The Predicate takes in the item an based on the value true
or false it returns, it selects or unselect an element.
this.Source.Filter = item =>
{
ViewItem vitem = item as ViewItem;
if (vitem == null) return false;
return vitem.Name.Contains("A");
};
This will select only the elements which have A in their names.
Current Record Manipulation
ICollectionView also allows to synchronize items with the Current
position of the element in the
CollectionView. Each ItemsControl which
is the base class of any ListControl in WPF exposes a property called
IsSynchronizedWithCurrentItem when set to true will automatically keeps
the current position of the
CollectionView in synch.
There are methods like :
this.Source.MoveCurrentToFirst();
this.Source.MoveCurrentToPrevious();
this.Source.MoveCurrentToNext();
this.Source.MoveCurrentToLast();
These allows you to navigate around the
CurrentItem of the
CollectionView.You can also use CurrentChanged event to intercept your
selection logic around the object.
Using the Code
To demonstrate all the features, I have created on Demo application
which allows you to Sort, Filter, Group, in place editing and navigate between data
objects. Lets see how it works :

The application contains a
ListView with few data in it. The header is
created using
GridView which can be clicked and based on which the
items will sort.
<ListView ItemsSource="{Binding}" x:Name="lvItems" GridViewColumnHeader.Click="ListView_Click" IsSynchronizedWithCurrentItem="True" Grid.Row="1">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
<ListView.View>
<GridView AllowsColumnReorder="True">
<GridViewColumn Header="Id" DisplayMemberBinding="{Binding Id}" />
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Name}" Style="{StaticResource ListViewUnSelected}"/>
<TextBox Text="{Binding Path=Name}" Style="{StaticResource ListViewSelected}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Developer">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Developer}" Style="{StaticResource ListViewUnSelected}" />
<ComboBox SelectedItem="{Binding Path=Developer}" ItemsSource="{Binding ElementName=mainWin, Path=DeveloperList}" Style="{StaticResource ListViewSelected}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn Header="Salary">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Salary}" Style="{StaticResource ListViewUnSelected}" />
<TextBox Text="{Binding Path=Salary}" Style="{StaticResource ListViewSelected}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
SORT

In the above image it is shown how the items automatically gets sorted
when the header is clicked. To handle this I have used
GridViewColumnHeader.Click which allows you to get control over the
column in which it is clicked.
private void ListView_Click(object sender, RoutedEventArgs e)
{
GridViewColumnHeader currentHeader = e.OriginalSource as GridViewColumnHeader;
if(currentHeader != null && currentHeader.Role != GridViewColumnHeaderRole.Padding)
{
using (this.Source.DeferRefresh())
{
Func<SortDescription, bool> lamda = item => item.PropertyName.Equals(currentHeader.Column.Header.ToString());
if (this.Source.SortDescriptions.Count(lamda) > 0)
{
SortDescription currentSortDescription = this.Source.SortDescriptions.First(lamda);
ListSortDirection sortDescription = currentSortDescription.Direction == ListSortDirection.Ascending ? ListSortDirection.Descending : ListSortDirection.Ascending;
currentHeader.Column.HeaderTemplate = currentSortDescription.Direction == ListSortDirection.Ascending ?
this.Resources["HeaderTemplateArrowDown"] as DataTemplate : this.Resources["HeaderTemplateArrowUp"] as DataTemplate;
this.Source.SortDescriptions.Remove(currentSortDescription);
this.Source.SortDescriptions.Insert(0, new SortDescription(currentHeader.Column.Header.ToString(), sortDescription));
}
else
this.Source.SortDescriptions.Add(new SortDescription(currentHeader.Column.Header.ToString(), ListSortDirection.Ascending));
}
}
}
In the above code I need to handle the Sorting gracefully so that we
always remove the current Sorting before we insert a new sort.
FILTER

The FilterBy section allows you to handle Filtering. You can select the
ColumnName from the
ComboBox and then apply the selection criteria for
the column. To do this I have used FilterButton Click command.
this.Source.Filter = item =>
{
ViewItem vitem = item as ViewItem;
if (vitem == null) return false;
PropertyInfo info = item.GetType().GetProperty(cmbProperty.Text);
if (info == null) return false;
return info.GetValue(vitem,null).ToString().Contains(txtFilter.Text);
};
Hence the predicate will be applied to the filter criteria.
GROUP

You can use Grouping Section to group based on Column Name. Here you
can see I have grouped items on Developer names. The applied group
header will be shown in the Group
Header section.
this.Source.GroupDescriptions.Clear();
PropertyInfo pinfo = typeof(ViewItem).GetProperty(cmbGroups.Text);
if (pinfo != null)
this.Source.GroupDescriptions.Add(new PropertyGroupDescription(pinfo.Name));
NAVIGATE
Navigation is handled using a sets of buttons which eventually calls
the respective methods to automatically keep
CollectionView in sync
with ListView items.
IN PLACE EDITING
In order to show you in-place editing, I have placed a separate control for editing purpose of each of the Selected Row. The selected Item is detected automatically using
RelativeSource in the ListView DataColumn selector.
Say for instance :
<GridViewColumn Header="Name">
<GridViewColumn.CellTemplate>
<DataTemplate>
<Grid>
<TextBlock Text="{Binding Path=Name}" Style="{StaticResource ListViewUnSelected}"/>
<TextBox Text="{Binding Path=Name}" Style="{StaticResource ListViewSelected}" />
</Grid>
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
Here the Style
ListViewSelected will show the
TextBox while the
ListViewUnselected will show the
TextBlock. If you look into the Style it will look like :
<Style x:Key="ListViewUnSelected" TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource convVis}, ConverterParameter=False}" />
</Style>
<Style x:Key="ListViewSelected" TargetType="{x:Type FrameworkElement}">
<Setter Property="Visibility" Value="{Binding Path=IsSelected, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ListViewItem}}, Converter={StaticResource convVis}, ConverterParameter=True}" />
</Style>
The RelativeSource FindAncestor searches the corresponding ListViewItem everytime and Binds it with the IsSelected property of it. Hence when the item gets selected, the CellTemplate automatically gets updated with the Editor controls.
The
Evaluate Selected Object button actually shows a short message based on the information in the current selection.
private void btnEvaluate_Click(object sender, RoutedEventArgs e)
{
ViewItem item = this.lvItems.SelectedItem as ViewItem;
string msg = string.Format("Hello {0}, Developer in {1} with Salary {2}", item.Name, item.Developer, item.Salary);
MessageBox.Show(msg);
}
So from the image above you can see Peter is a developer in WPF.

If you change the Developer section from the ComboBox and choose SilverLight, the MessageBox will update the value and hence the internal object is also gets updated.
Notice : I have used reflection to get PropertyInfo for many cases. If
you don't want to use it, you might also do this statically.
Conclusion
In summery, WPF ius the most flexible tool for UI Development. It has lots of flexibilities and the use of few features can really make our life easier. In this article, you got the idea on how to handle ICollectionView for Data Manipulation. If you are really new to this type of programming, why not you start reading my Beginners Tutorial on WPF.
I hope this article comes to you handy. Thank you for reading the post and your valuable feedback.