Click here to Skip to main content
15,879,326 members
Articles / Desktop Programming / WPF
Tip/Trick

How to merge datacells in WPF DataGridView

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
23 Jul 2013CPOL2 min read 46.7K   1.6K   5   3
How to merge datacells in WPF DataGridView.

Introduction  

The DataGridView Windows control is a very rich tool for displaying tabular data in WPF applications and also it is easy to achieve a great degree of customization with this control. In this article we will examine how to merge cells in the DataGridView. There is not an easy way to achieve this but by using the Paint event of DataGridView we can merge cells and we can achieve this.

Requirement

We have a requirement to create a WPF control which can display records and merge those cells having the same column value. As seen in the image group name (Test) is common for row1 to row 3 hence they are merged while Test2 is common for rows 4 and 5 and hence they are merged.

Image 1

Solution

The above requirement can be achieved by using the DataGridView in a WPF application. The onPaint event is used to paint the merged columns in the DataGridView which iterates indefinite and every time it paints the columns  having the same value.

Using the code 

First, let's create a simple application which uses the DataGridView control to display datatable records. DataGridView is not a WPF control so we can not access it directly in WPF. To add this control in WPF, we will have to add the following references:

  • PresentationFramework.dll
  • WindowsFormIntegration.dll

Add these namespace also in the code-behind file. Now we need to add the DataGridView in the XAML file:

XML
<Window x:Class="WpfApplication1.Window2"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wfi="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
        xmlns:swf="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"
        Title="Window2" Height="400" Width="500"
        SizeToContent="Height"
        WindowStartupLocation="CenterScreen"
        ResizeMode="NoResize">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="*"></RowDefinition>
        </Grid.RowDefinitions>

        <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="10,10,0,0">
            <Image Name="imgInformation"  ></Image>
            <Label Content="This is Test"/>
        </StackPanel>
    
        <wfi:WindowsFormsHost x:Name="wfh" Grid.Row="1" 
          Height="120"   Margin="31,10,31,0">
            
            <swf:DataGridView 
            x:Name="dataGrid"
            AutoGenerateColumns="False"
            AllowUserToAddRows="False"
            RowHeadersVisible="False"
            SelectionMode="ColumnHeaderSelect"
            AutoSize="True">
                <swf:DataGridView.Columns>
                    <swf:DataGridViewTextBoxColumn Width="143" 
                      HeaderText="Group Name" DataPropertyName="SubGroup" 
                      Name="SubGroup" SortMode="NotSortable" 
                      ReadOnly="True" Selected="False"
                    ></swf:DataGridViewTextBoxColumn>
                    <swf:DataGridViewTextBoxColumn Width="143" HeaderText="Part Name" 
                      DataPropertyName="PartName" SortMode="NotSortable" 
                      ReadOnly="True" Selected="False"></swf:DataGridViewTextBoxColumn>
                    <swf:DataGridViewTextBoxColumn Width="143" HeaderText="Libaray Name" 
                      DataPropertyName="PartLib" SortMode="NotSortable" 
                      ReadOnly="True" Selected="False"></swf:DataGridViewTextBoxColumn>
                </swf:DataGridView.Columns>
            </swf:DataGridView>
        </wfi:WindowsFormsHost>
          
        <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
            <Button Name="btnOk" Height="25" Width="80" 
              Margin="10,20,10,20" VerticalAlignment="Top" 
              Content="OK" Click="btnOk_Click" />
            <Button Name="btnCancel" Height="25" Width="80" 
              Margin="10,20,20,20" VerticalAlignment="Top" 
              Content="Cancel" Click="btnCancel_Click"/>
        </StackPanel>
    </Grid>
</Window>
//

To bind this DataGridView with the datatable the ItemSource is set in the code-behind file.

C#
void Window2_Loaded(object sender, RoutedEventArgs e)
{
    DataTable tbl_main = new DataTable("tbl_main");
    tbl_main.Columns.Add("SubGroup");
    tbl_main.Columns.Add("PartName");
    tbl_main.Columns.Add("PartLib");

    DataRow dr = tbl_main.NewRow();
    dr[0] = "Test";
    dr[1] = "Part15";
    dr[2] = "Lib1";
    tbl_main.Rows.Add(dr);

    dr = tbl_main.NewRow();
    dr[0] = "Test";
    dr[1] = "Part2";
    dr[2] = "Lib2";
    tbl_main.Rows.Add(dr);

    dr = tbl_main.NewRow();
    dr[0] = "Test";
    dr[1] = "Part2";
    dr[2] = "Lib2";
    tbl_main.Rows.Add(dr);

    dr = tbl_main.NewRow();
    dr[0] = "Test2";
    dr[1] = "Part3";
    dr[2] = "Lib2";
    tbl_main.Rows.Add(dr);

    dr = tbl_main.NewRow();

    dr[0] = "Test2";
    dr[1] = "Part3";
    dr[2] = "Lib1";

    tbl_main.Rows.Add(dr);
      
    dataSet1.Tables.Add(tbl_main);
    dataSet1.Tables["tbl_main"].DefaultView.Sort = "SubGroup ASC";
    dataSet.Tables.Add(dataSet1.Tables["tbl_main"].DefaultView.ToTable());
    dataGrid.DataSource = dataSet;
    dataGrid.DataMember = "tbl_main";
   
    //dataGrid.Rows.RemoveAt(dataGrid.Rows.Count - 1);
    dataGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

    dataGrid.Columns[1].DefaultCellStyle.Alignment = 
       DataGridViewContentAlignment.MiddleCenter;
    foreach (DataGridViewRow row in this.dataGrid.Rows)
    {
        foreach (DataGridViewCell cell in row.Cells)
        {
            DataGridViewTextBoxCell dataCell = (DataGridViewTextBoxCell)cell;
            dataGrid.AutoResizeRow(dataCell.RowIndex, 
              DataGridViewAutoSizeRowMode.AllCellsExceptHeader);
        }
    }
}

Image 2

This is normal data table binding with DataGridView in WPF. In the Load event the datatable is created manually and the ItemsSource of the DataGridView is defined. But as per requirements we need to merge cells having the same column values. To achieve this we need to attach the OnPaint event with the DataGridView and a new rectangle is created above the cells in the DataGridView.

Here is the complete code to merge cells:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Data;
using System.Windows.Forms;
using System.Drawing;

namespace WpfApplication1
{
    /// <summary>
    /// Interaction logic for Window2.xaml
    /// </summary>
    public partial class Window2 : Window
    {
        public Window2()
        {
            InitializeComponent();
            
           // dataGrid.Paint += new PaintEventHandler(dataGrid_Paint);
         
            btnOk.IsDefault = true;
            btnCancel.IsCancel = true;

            Loaded += new RoutedEventHandler(Window2_Loaded);
        }

        void dataGrid_Paint(object sender, PaintEventArgs e)
        {
            
            dataGrid.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            Merge();
            
        }

        DataSet dataSet1 = new DataSet();
        DataSet dataSet = new DataSet();
        private List<string> MergedRowsInFirstColumn = new List<string>();

        void Window2_Loaded(object sender, RoutedEventArgs e)
        {
            DataTable tbl_main = new DataTable("tbl_main");
            tbl_main.Columns.Add("SubGroup");
            tbl_main.Columns.Add("PartName");
            tbl_main.Columns.Add("PartLib");

            DataRow dr = tbl_main.NewRow();
            dr[0] = "Test";
            dr[1] = "Part15";
            dr[2] = "Lib1";
            tbl_main.Rows.Add(dr);

            dr = tbl_main.NewRow();
            dr[0] = "Test";
            dr[1] = "Part2";
            dr[2] = "Lib2";
            tbl_main.Rows.Add(dr);

            dr = tbl_main.NewRow();
            dr[0] = "Test";
            dr[1] = "Part2";
            dr[2] = "Lib2";
            tbl_main.Rows.Add(dr);

            dr = tbl_main.NewRow();
            dr[0] = "Test2";
            dr[1] = "Part3";
            dr[2] = "Lib2";
            tbl_main.Rows.Add(dr);

            dr = tbl_main.NewRow();

            dr[0] = "Test2";
            dr[1] = "Part3";
            dr[2] = "Lib1";

            tbl_main.Rows.Add(dr);
        

      
            dataSet1.Tables.Add(tbl_main);
            dataSet1.Tables["tbl_main"].DefaultView.Sort = "SubGroup ASC";
            dataSet.Tables.Add(dataSet1.Tables["tbl_main"].DefaultView.ToTable());
            dataGrid.DataSource = dataSet;
            dataGrid.DataMember = "tbl_main";
           
            //dataGrid.Rows.RemoveAt(dataGrid.Rows.Count - 1);
            dataGrid.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;

            dataGrid.Columns[1].DefaultCellStyle.Alignment = 
               DataGridViewContentAlignment.MiddleCenter;
            foreach (DataGridViewRow row in this.dataGrid.Rows)
            {
                foreach (DataGridViewCell cell in row.Cells)
                {
                    DataGridViewTextBoxCell dataCell = (DataGridViewTextBoxCell)cell;
                    dataGrid.AutoResizeRow(dataCell.RowIndex, 
                      DataGridViewAutoSizeRowMode.AllCellsExceptHeader);
                }
            }            
        }

        private void Merge()
        {
            int[] RowsToMerge = new int[3];
            RowsToMerge[0] = -1;
            int rowcount = dataGrid.HorizontalScrollingOffset / 11;
            //Merge first column at first
            for (int i = 0; i < dataSet.Tables["tbl_main"].Rows.Count - 1; i++)
            {
                if (dataSet.Tables["tbl_main"].Rows[i]["SubGroup"] == 
                    dataSet.Tables["tbl_main"].Rows[i + 1]["SubGroup"])
                {
                    if (RowsToMerge[0] == -1)
                    {
                        RowsToMerge[0] = i;
                        RowsToMerge[1] = i + 1;
                    }
                    else
                    {
                        RowsToMerge[1] = i + 1;
                    }
                }
                else
                {
                    if (RowsToMerge[0] != -1)
                    {
                        MergeCells(RowsToMerge[0], RowsToMerge[1], dataGrid.Columns["SubGroup"].Index, 
                          isSelectedCell(RowsToMerge, dataGrid.Columns["SubGroup"].Index) ? true : false);
                        
                        RowsToMerge[0] = -1;
                    }
                }
             
            }
            if (RowsToMerge[0] != -1)
            {
                MergeCells(RowsToMerge[0], RowsToMerge[1], dataGrid.Columns["SubGroup"].Index, 
                  isSelectedCell(RowsToMerge, dataGrid.Columns["SubGroup"].Index) ? true : false);
                RowsToMerge[0] = -1;
            }           
        }

        private bool isRowsHaveOneCellInFirstColumn(int RowId1, int RowId2)
        {
            foreach (string rowsCollection in MergedRowsInFirstColumn)
            {
                string[] RowsNumber = rowsCollection.Split(';');

                if ((isStringInArray(RowsNumber, RowId1.ToString())) &&
                    (isStringInArray(RowsNumber, RowId2.ToString())))
                {
                    return true;
                }
            }
            return false;
        }

        private bool isStringInArray(string[] Array, string value)
        {
            foreach (string item in Array)
            {
                if (item == value)
                {
                    return true;
                }

            }
            return false;
        }

        private void CollectMergedRowsInFirstColumn(int RowId1, int RowId2)
        {
            string MergedRows = String.Empty;

            for (int i = RowId1; i <= RowId2; i++)
            {
                MergedRows += i.ToString() + ";";
            }
            MergedRowsInFirstColumn.Add(MergedRows.Remove(MergedRows.Length - 1, 1));
        }

        private void MergeCells(int RowId1, int RowId2, int Column, bool isSelected)
        {
            System.Drawing.Graphics g = dataGrid.CreateGraphics();
            g.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
            g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
            System.Drawing.Pen gridPen = new System.Drawing.Pen(dataGrid.GridColor);

            System.Drawing.Rectangle CellRectangle1 = 
              dataGrid.GetCellDisplayRectangle(Column, RowId1, true);
          
            System.Drawing.Rectangle CellRectangle2 = 
              dataGrid.GetCellDisplayRectangle(Column, RowId2, true);

            if (CellRectangle1.IsEmpty && CellRectangle2.IsEmpty) return;

            if (CellRectangle1.IsEmpty)
            {
                //Calculate RowID1 as per scroll offset
                int height1 = dataGrid.Rows[0].Height;
                if (dataGrid.VerticalScrollingOffset != 0)
                {
                    if ((dataGrid.VerticalScrollingOffset / height1) == 0)
                        RowId1 = dataGrid.VerticalScrollingOffset / height1;
                    else if (((dataGrid.VerticalScrollingOffset / height1) != 0 && 
                        (dataGrid.VerticalScrollingOffset % height1 == 0))
                        || ((dataGrid.VerticalScrollingOffset / height1) == 0 && 
                        (dataGrid.VerticalScrollingOffset % height1 != 0)))
                        RowId1 = dataGrid.VerticalScrollingOffset / height1;
                    else
                        RowId1 = (dataGrid.VerticalScrollingOffset / height1) + 1;
                }
                CellRectangle1 = dataGrid.GetCellDisplayRectangle(Column, RowId1, true);
            }

            int rectHeight = 0;
            string MergedRows = String.Empty;

            for (int i = RowId1; i <= RowId2; i++)
            {
                rectHeight += dataGrid.GetCellDisplayRectangle(Column, i, false).Height;
            }

            System.Drawing.Rectangle newCell = new System.Drawing.Rectangle(CellRectangle1.X, 
              CellRectangle1.Y, (CellRectangle1.Width - 2), (rectHeight - 1));

            g.FillRectangle(new System.Drawing.SolidBrush(isSelected ? 
              dataGrid.DefaultCellStyle.SelectionBackColor : 
              dataGrid.DefaultCellStyle.BackColor), newCell);

            g.DrawRectangle(gridPen, newCell);

            g.DrawString(dataGrid.Rows[RowId1].Cells[Column].Value.ToString(), 
              dataGrid.DefaultCellStyle.Font, new System.Drawing.SolidBrush(isSelected ? 
              dataGrid.DefaultCellStyle.SelectionForeColor : dataGrid.DefaultCellStyle.ForeColor), 
              newCell.X + newCell.Width / 3, newCell.Y + newCell.Height / 3);
            g.Dispose();
           
        }

        private bool isSelectedCell(int[] Rows, int ColumnIndex)
        {
            if (dataGrid.SelectedCells.Count > 0)
            {
                for (int iCell = Rows[0]; iCell <= Rows[1]; iCell++)
                {
                    for (int iSelCell = 0; iSelCell < dataGrid.SelectedCells.Count; iSelCell++)
                    {
                        if (dataGrid.Rows[iCell].Cells[ColumnIndex] == dataGrid.SelectedCells[iSelCell])
                        {
                            return true;
                        }
                    }
                }
                return false;
            }
            else
            {
                return false;
            }
        }

        private void btnOk_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void btnCancel_Click(object sender, EventArgs e)
        {

        }
    }
}

Points of Interest

Download the above WPF application and Build and Run the application. If you have any questions you can contact me through mail or through the discussion board.

History

  • 1.1 version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer RSystems Internaional Ltd
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionMerge more Columns Pin
HPSta1-Jan-21 20:56
HPSta1-Jan-21 20:56 
QuestionSort Pin
Member 111659809-Nov-14 22:28
Member 111659809-Nov-14 22:28 
GeneralMy vote of 5 Pin
Raman Midha23-Jul-13 22:40
professionalRaman Midha23-Jul-13 22:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.