Introduction
A couple of days ago, I was working on a program that allows users to upload multiple files on a server. I realized it would be nice to show progress of upload for every file individually. I tried to find a way how to solve this. After hours of “Googling” I found some solutions, how to show progress bar in DataGridView
object so I decided to create my own custom progress bar column. Articles I found served as a guide for me and I have picked up same ideas, how to customize DataGridView
columns. I have implemented some functionality that I will describe later.
Background
DataGridView
control provides several column types, enabling users to enter and edit values in a variety of ways. Sometimes these column types do not meet needs, but that’s not a problem because you can create your own column types that can host controls you need. When you want to create your own column type, you must define some classes derived from DataGridViewColumn
and DataGridViewCell
.
In this short example, I will show you how to create a progress bar column. This example does not directly insert progress bar control into a cell, it only draws a rectangle which looks like a progress bar.
To do this, you must define classes that derive from DataGridViewColumn
and DataGridViewImageCell
. (You can also derive from DataGridViewCell
which is the base type of all cells directly, but in this case is much more suitable to derive from DataGridViewImageCell
, because we will use some graphical operations.)
Main Steps
As I mentioned earlier, two classes must be created. The first class I have created is DataGridViewProgressColumn
which derives from DataGridViewImageColumn
. This class creates a custom column for hosting progress bar cells and overrides property CellTemplate
.
public class DataGridViewProgressColumn : DataGridViewImageColumn
{
. . .
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
if (value != null &&
!value.GetType().IsAssignableFrom(typeof(DataGridViewProgressCell)))
{
throw new InvalidCastException("Must be a DataGridViewProgressCell");
}
base.CellTemplate = value;
}
}
. . .
}
The second class I have created is DataGridViewProgressBarCell
. This class creates a custom cell for hosting a progress bar. This class derives from DataGridViewImageCell
.
When developing a custom cell type, it is also critical to check if some virtual methods need to be overridden. In this example, two methods need to be overridden: Clone()
method and Paint()
method.
Paint() Method
In order to draw the content of the cell in the way we wanted, we must overload the paint
method. This method is a critical one for any cell type. It is responsible for painting a cell. It sets various properties and is responsible for calling Graphics.FillRectangle(…)
and Graphics.DrawString(…)
methods which draw progress bar and write a text.
protected override void Paint(System.Drawing.Graphics g,
System.Drawing.Rectangle clipBounds,
System.Drawing.Rectangle cellBounds,
int rowIndex,
DataGridViewElementStates cellState,
object value, object formattedValue,
string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
if (Convert.ToInt16(value) == 0 || value == null)
{
value = 0;
}
int progressVal = Convert.ToInt32(value);
float percentage = ((float)progressVal / 100.0f);
Brush backColorBrush = new SolidBrush(cellStyle.BackColor);
Brush foreColorBrush = new SolidBrush(cellStyle.ForeColor);
base.Paint(g, clipBounds, cellBounds, rowIndex,
cellState, value, formattedValue, errorText,
cellStyle, advancedBorderStyle,
(paintParts & ~DataGridViewPaintParts.ContentForeground));
float posX = cellBounds.X;
float posY = cellBounds.Y;
float textWidth = TextRenderer.MeasureText
(progressVal.ToString() + "%", cellStyle.Font).Width;
float textHeight = TextRenderer.MeasureText
(progressVal.ToString() + "%", cellStyle.Font).Height;
switch (cellStyle.Alignment)
{
case DataGridViewContentAlignment.BottomCenter:
posX = cellBounds.X + (cellBounds.Width / 2) - textWidth / 2;
posY = cellBounds.Y + cellBounds.Height - textHeight;
break;
case DataGridViewContentAlignment.BottomLeft:
posX = cellBounds.X;
posY = cellBounds.Y + cellBounds.Height - textHeight;
break;
case DataGridViewContentAlignment.BottomRight:
posX = cellBounds.X + cellBounds.Width - textWidth;
posY = cellBounds.Y + cellBounds.Height - textHeight;
break;
case DataGridViewContentAlignment.MiddleCenter:
posX = cellBounds.X + (cellBounds.Width / 2) - textWidth / 2;
posY = cellBounds.Y + (cellBounds.Height / 2) - textHeight / 2;
break;
case DataGridViewContentAlignment.MiddleLeft:
posX = cellBounds.X;
posY = cellBounds.Y + (cellBounds.Height / 2) - textHeight / 2;
break;
case DataGridViewContentAlignment.MiddleRight:
posX = cellBounds.X + cellBounds.Width - textWidth;
posY = cellBounds.Y + (cellBounds.Height / 2) - textHeight / 2;
break;
case DataGridViewContentAlignment.TopCenter:
posX = cellBounds.X + (cellBounds.Width / 2) - textWidth / 2;
posY = cellBounds.Y;
break;
case DataGridViewContentAlignment.TopLeft:
posX = cellBounds.X;
posY = cellBounds.Y;
break;
case DataGridViewContentAlignment.TopRight:
posX = cellBounds.X + cellBounds.Width - textWidth;
posY = cellBounds.Y;
break;
}
if (percentage >= 0.0)
{
g.FillRectangle(new SolidBrush(_ProgressBarColor), cellBounds.X + 2,
cellBounds.Y + 2, Convert.ToInt32((percentage * cellBounds.Width * 0.8)),
cellBounds.Height / 1 - 5);
g.DrawString(progressVal.ToString() + "%",
cellStyle.Font, foreColorBrush, posX, posY);
}
else
{
if (this.DataGridView.CurrentRow.Index == rowIndex)
{
g.DrawString(progressVal.ToString() + "%", cellStyle.Font,
new SolidBrush(cellStyle.SelectionForeColor), posX, posX);
}
else
{
g.DrawString(progressVal.ToString() + "%", cellStyle.Font,
foreColorBrush,posX, posY);
}
}
}
This method is also responsible for correct rendering of text according to alignment selected by user. This alignment can be selected in CellStyle Builder Window.
The Clone() Method
The DataGridViewImageCell
derives from DataGridViewCell
class that implements the ICloneable
interface. Each custom cell type typically needs to override the Clone()
method to copy its custom properties. Cells are cloneable because a particular cell instance can be used for multiple rows in the grid. This is the case when the cell belongs to a shared row. When a row gets unshared, its cells need to be cloned. Following is the implementation for the Clone()
method:
public override object Clone()
{
DataGridViewProgressCell dataGridViewCell = base.Clone() as DataGridViewProgressCell;
if (dataGridViewCell != null)
{
dataGridViewCell.ProgressBarColor = this.ProgressBarColor;
}
return dataGridViewCell;
}
ProgressBarColor Property
I have created a new property called ProgressBarColor
, which sets color of rendered progress bar. This property must be implemented in both classes (DataGridViewProgressBarCell
and DataGridViewProgressBarColumn
). In DataGridViewProgressBarColumn
class, this method sets ProgressBarColor
property of CellTemplate
(which must be casted as DataGridViewProgressCell
). ProgressBarColor
value of CellTemplate
is next passed into _progressBarColor
variable of DataGridViewProgressBarCell
class. _progressBarColor
variable is used in Paint(…)
method. Here is an implementation of ProgressBarColor
property:
[Browsable(true)]
public Color ProgressBarColor
{
get {
if (this.ProgressBarCellTemplate == null)
{
throw new InvalidOperationException
("Operation cannot be completed because this DataGridViewColumn
does not have a CellTemplate.");
}
return this.ProgressBarCellTemplate.ProgressBarColor;
}
set {
if (this.ProgressBarCellTemplate == null)
{
throw new InvalidOperationException
("Operation cannot be completed because this DataGridViewColumn
does not have a CellTemplate.");
}
this.ProgressBarCellTemplate.ProgressBarColor = value;
if (this.DataGridView != null)
{
DataGridViewRowCollection dataGridViewRows = this.DataGridView.Rows;
int rowCount = dataGridViewRows.Count;
for (int rowIndex = 0; rowIndex < rowCount; rowIndex++)
{
DataGridViewRow dataGridViewRow = dataGridViewRows.SharedRow(rowIndex);
DataGridViewProgressCell dataGridViewCell =
dataGridViewRow.Cells[this.Index] as DataGridViewProgressCell;
if (dataGridViewCell != null)
{
dataGridViewCell.SetProgressBarColor(rowIndex, value);
}
}
this.DataGridView.InvalidateColumn(this.Index);
}
}
}
As I mentioned earlier, when we want to pass ProgressBarColor
value into CellTemplate
, we must first get this CellTemplate
. We can get CellTemplate
from ProgressBarCellTemplate
property of DataGridProgressColumn
class. When we get this, we can easily set ProgressBarColor
property of CellTemplate
.
Using the Code
I have created a simple project called ProgressBarColumn
. In this project, I have included one form named frmMain
containing DataGridView
with 2 columns. The first column is of DataGridViewTextBoxColumn
Type and the second is DataGridViewProgressColumn
type. In this project, I show you how to load data into DataGridView
and how this grid renders DataGridViewProgressColumn
.
History
- 10 Oct 2010 - Original version posted