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.

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:
<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.
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.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);
}
}
}

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:
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
{
public partial class Window2 : Window
{
public Window2()
{
InitializeComponent();
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.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;
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)
{
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