My .NET focused coding blog.

Merging cells in a WPF ListView

This post provides an example of how you can merge cells that contain the same value in a ListView in WPF and make it look like the cell spans several rows as shown in the rightmost picture below.

listview11arrowlistview2


The windows in the pictures above contains a ListView with a GridView view mode and two columns for displaying the Name and Continent string properties of Country objects in a collection that is exposed from the below view model class:

public class ViewModel
{
    public ViewModel()
    {
        this.Countries = new List<Country>();
        this.Countries.Add(new Country { Name = "Germany", Continent = "Europe" });
        this.Countries.Add(new Country { Name = "United Kingdom", Continent = "Europe" });
        this.Countries.Add(new Country { Name = "France", Continent = "Europe" });
        this.Countries.Add(new Country { Name = "USA", Continent = "North America" });
        this.Countries.Add(new Country { Name = "Canada", Continent = "North America" });
    }

    public IList<Country> Countries { get; private set; }
}

public class Country
{
    public string Name { get; set; }
    public string Continent { get; set; }
}

The key to be able to merge the cells that contain the same value is to bind the ItemsSource property of the ListView to a grouped collection instead of binding it directly to the Countries collection property of the view model.

You can use a CollectionViewSource to group (and also sort and filter for that matter) a collection directly in XAML without having to modify the view model at all. Just add the CollectionViewSource to the Resources ResourceDictionary of the view, bind its Source property to the source collection of the view model and then add a PropertyGroupDescription, whose PropertyName property determines which group an item – a country in this particular example – belongs to, to the GroupDescriptions collection of the CollectionViewSource:

<Window.DataContext>
    <local:ViewModel/>
</Window.DataContext>
<Window.Resources>
    <CollectionViewSource x:Key="cvs" Source="{Binding Countries}">
        <CollectionViewSource.GroupDescriptions>
            <!-- Group countries by the Continent property -->
            <PropertyGroupDescription PropertyName="Continent"/>
        </CollectionViewSource.GroupDescriptions>
    </CollectionViewSource>
</Window.Resources>

You should then bind the ItemsSource property of the ListView to the Groups property of the view of the CollectionViewSource. Whenever you bind to some collection property in WPF, you are always binding to an automatically generated view and not to the actual collection itself. The type of view that is created for you by the runtime depends on the type of the source collection but all of these types derive from the System.Windows.Data.CollectionView base class.

The Groups property of the CollectionView class will return a collection of MS.Internal.Data.CollectionViewGroupInternal objects, one for each group (or continent). These objects are created by the CollectionView class according to the GroupDescriptions that were added to the CollectionViewSource.

The CollectionViewGroupInternal class is derived from the public System.Windows.Data.CollectionViewGroup class and this one has a Name property that returns the value (“Europe” and “North America” in this case) of the property whose name was specified as the value of the PropertyName property of the PropertyGroupDescription and an Items property that returns the collection of objects, i.e. the Country objects, that belongs to a specific group, i.e. a continent in this particular example.

This means that you can now use an ItemsControl in the CellTemplate of the GridViewColumn to display a list of several countries in the same cell:

<ListView ItemsSource="{Binding Path=Groups, Source={StaticResource cvs}}">
    <ListView.View>
        <GridView>
            <GridView.Columns>
                <!-- Bind to the Name property of the CollectionViewGroup -->
                <GridViewColumn Header="Continent" DisplayMemberBinding="{Binding Name}"/>
                <GridViewColumn Header="Country">
                    <GridViewColumn.CellTemplate>
                        <DataTemplate>
                            <!--The Items property of the CollectionViewGroup contains all 
                                        Country objects that belong to a specific continent -->
                            <ItemsControl ItemsSource="{Binding Items}">
                                <ItemsControl.ItemTemplate>
                                    <DataTemplate>
                                        <!-- Display the value of the Name property of a Country object -->
                                        <TextBlock Text="{Binding Name}"/>
                                    </DataTemplate>
                                </ItemsControl.ItemTemplate>
                            </ItemsControl>
                        </DataTemplate>
                    </GridViewColumn.CellTemplate>
                </GridViewColumn>
            </GridView.Columns>
        </GridView>
    </ListView.View>
</ListView>

See also

How to: Display ListView Contents by Using a GridView: http://msdn.microsoft.com/en-us/library/ms747048(v=vs.110).aspx
CollectionViewSource Class: http://msdn.microsoft.com/en-us/library/system.windows.data.collectionviewsource.aspx
CollectionView Class: http://msdn.microsoft.com/en-us/library/system.windows.data.collectionview(v=vs.110).aspx


7 Comments on “Merging cells in a WPF ListView”

  1. Andy says:

    Thanks!

  2. Jane says:

    Thanks, nice article. Just a question, if you define countries collection as ICollectionView instead of IList. Does it work? Thanks.

  3. Eldhin says:

    This could be a very noob question but i have a problems with this part:

    Looks like it isn’t recognizing the local:ViewModel

    Do i need to declare something before that?

  4. Adrian says:

    How can i use this to list data from sql server query?

  5. Adrian says:

    how can i reload the list if something changed?

  6. Eldhin,

    You need to declare the namespace in your XAML markup:

    XAML:
    <Window xmlns:local=”clr-namespace:WpfApplication1″ …>
    C#:
    namespace WpfApplication1
    {
    public class ViewModel { … }
    }

  7. zeb says:

    Hello, thanks for this post..
    juste a question : how to extend this model to all grid columns to do something like this :

    +—————————————————————-|
    | | | Berlin |
    | | Germany | Munich |
    | | | Hambourg |
    | |————————–|—————–|
    | Europe | | Newport |
    | | United Kingdom | London |
    | | | Bristol |
    | |————————-|—————–|
    | | France | Paris |
    |———————|————————-|—————–|
    | North America | USA | City 1 |
    | | Canada | City 2 |
    |———————|————————-|—————–|


Leave a comment