Merging cells in WPF DataGrid vertically

13,675

Solution 1

Trick to such scenarios is to use the Groups formed in CollectionViewSource as the ItemsSource of a DataGrid. And, use a DataGrid itself as the CellTemplate of Column.

Xaml

    <Window.Resources>
        <CollectionViewSource x:Key="CvsKey">
            <CollectionViewSource.GroupDescriptions>
                <PropertyGroupDescription PropertyName="Country"/>
            </CollectionViewSource.GroupDescriptions>
        </CollectionViewSource>
    </Window.Resources>

    <Grid>
        <DataGrid x:Name="dg" Loaded="dg_Loaded" HorizontalScrollBarVisibility="Disabled" HeadersVisibility="All" Grid.Column="0" RowHeaderWidth="0" CanUserAddRows="False" AutoGenerateColumns="False"  VerticalContentAlignment="Center" HorizontalContentAlignment="Center">            
            <DataGrid.Columns>
                <DataGridTemplateColumn Header="Country"  Width="75">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>                           
                            <Grid>
                                <TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
                            </Grid>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
                <DataGridTemplateColumn Header="Name"  Width="75">
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <DataGrid ItemsSource="{Binding Items}" IsReadOnly="True" AutoGenerateColumns="False" HeadersVisibility="None">
                                <DataGrid.Columns>
                                    <DataGridTemplateColumn Width="*">
                                        <DataGridTemplateColumn.CellTemplate>
                                            <DataTemplate>
                                                <TextBlock Text="{Binding Name}"/>
                                            </DataTemplate>
                                        </DataGridTemplateColumn.CellTemplate>
                                    </DataGridTemplateColumn>
                                </DataGrid.Columns>
                            </DataGrid>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
          </DataGrid.Columns>            
        </DataGrid>
    </Grid>
</Window>

DataGrid.Loaded event

 private void dg_Loaded(object sender, RoutedEventArgs e)
    {
        var groups = (this.Resources["CvsKey"] as CollectionViewSource).View.Groups;
        dg.ItemsSource = groups;
    }

This should get you started.

Output :

Vertical Grid Output

Solution 2

I have created a demo project on github.com
Output:

enter image description here

XAML Code: Grid with two columns and each containing a DataGrid

 <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="Auto" />
                <ColumnDefinition Width="Auto"/>
            </Grid.ColumnDefinitions>
            <DataGrid Name="basketNameDataGrid" AutoGenerateColumns="False" CanUserResizeRows="False"
                      CanUserAddRows="False">
                <DataGrid.RowStyle>
                    <Style TargetType="DataGridRow">
                        <Setter Property="Height" Value="{Binding RowHeight}"></Setter>
                    </Style>
                </DataGrid.RowStyle>
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="Basket">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name}" VerticalAlignment="Center"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                </DataGrid.Columns>
            </DataGrid>
            <DataGrid Name="itemDataGrid" Grid.Column="1" AutoGenerateColumns="False" HeadersVisibility="Column" 
                      CanUserResizeRows="False" CanUserAddRows="False">
                <DataGrid.RowStyle>
                    <Style TargetType="DataGridRow">
                        <Setter Property="Height" Value="20"></Setter>
                    </Style>
                </DataGrid.RowStyle>
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="Item Name">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding Name, Mode = OneWay}"/>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                        </DataGridTemplateColumn>
                    <DataGridTextColumn Header="Price" Binding="{Binding Price, Mode = OneWay}" CanUserSort="False"></DataGridTextColumn>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>

C# code - There are three classes to structure data. (Removed constructors to reduce code lines)

  class Item
    { 
        public Name {get;}
        public Price {set;}
     }

    class Basket : List<Item>
     {  
        public Name {get;}
     }   

    class BasketCollection : List<Basket>
     {
     }          

Code behind in MainWindow.cs - Populate data and assign to DataGrids.

public MainWindow()
{
   InitializeComponent();
   //// Get some data to show in View
   var baskets = GetData();
   int rowHeight = 20; //// itemDataGrid row height is 20 in xaml

   //// Create a list of annonymous type with properties Name an RowHeight.
   //// RowHeight = Height of one row * number of items in current basket.
   var basketNameData = baskets.Select(x => new { Name = x.Name, RowHeight = rowHeight * x.Count });

   //// Assign data to first DataGrid 
   basketNameDataGrid.ItemsSource = basketNameData.ToList();

   //// Get list of  all Items in all baskets and assign as ItemsSource to second datagrid
   itemDataGrid.ItemsSource = baskets.SelectMany(basket => basket).ToList();
}

/// <summary>
/// Gets some data to bind to view
/// </summary>
/// <returns>Basket Collection</returns>
private BasketCollection GetData()
{
    var baskets = new BasketCollection();

    var fruitBasket = new Basket("Fruit");
    fruitBasket.Add(new Item("Alphonso Mango", 80));
    fruitBasket.Add(new Item("Nagpur Orange", 10));
    fruitBasket.Add(new Item("Dragon Fruit", 50));

    var vegetableBasket = new Basket("Vegetable");
    vegetableBasket.Add(new Item("Brinjal", 5));
    vegetableBasket.Add(new Item("Broccoli", 5));
    vegetableBasket.Add(new Item("Onion", 3)); 

    baskets.Add(fruitBasket);
    baskets.Add(vegetableBasket);

    return baskets;
}

Note: This solution do not actually merge cells, but create such visual effect.You may try this. Demo uses two DataGrid controls. The row height of first DataGrid is increased to create merged cell effect.

Alternative : ReoGrid is MS Excel compatible control which supports merge/unmerge cells feature like Excel. ReoGrid claims to be free and open source. It do not support Data Binding but it has support for DataTable.

Share:
13,675
nickosv
Author by

nickosv

Updated on June 13, 2022

Comments

  • nickosv
    nickosv almost 2 years

    I want to make a DataGrid in WPF, where some of the cells will "merge together", if they are alike.

    Example:

    +---------+------+-----+
    | Country | Name | Age |
    +---------+------+-----+
    |         | Lisa | 24  |
    +         +------+-----+
    | Danmark |  Per | 32  |
    +         +------+-----+
    |         | Hans | 33  |
    +---------+------+-----+
    | Germany | Mick | 22  |
    +---------+------+-----+
    

    Is there any way to achieve this using DataGrid using binding?

    Thanks a lot in advance.