Introduction
You can use this DataGridViewRichTextBoxColumn
to display and edit RTF content.
Background
A customer wanted to have superscript and subscript support in his reports, and he also wanted to edit the reports. I thought RTF files were a good choice. Then, a problem came up. I needed a setting dialog with a DataGridView
, where the user will input text (should support superscript and subscript). So, I decided to put a RichTextBox
in the DataGridView
.
I found an idea here. There was no source code. So, I decided to write about it and post my first article in CodeProject.
Three Classes to make a DataGridViewColumn
There is an example of a calendar DataGridViewColumn
here.
To make a custom DataGridViewColumn
, we should write three classes:
- An editor control class derived from
IDataGridViewEditingControl
- A cell class derived from
DataGridViewCell
or its descendant class - A column class derived from
DataGridViewColumn
or its descendant class
First, the Editor Control Class
Here is the code for supporting multiline input in the RichTextBox
:
public class DataGridViewRichTextBoxEditingControl :
RichTextBox, IDataGridViewEditingControl
{
protected override bool IsInputKey(Keys keyData)
{
Keys keys = keyData & Keys.KeyCode;
if (keys == Keys.Return)
{
return this.Multiline;
}
return base.IsInputKey(keyData);
}
public bool EditingControlWantsInputKey
(Keys keyData, bool dataGridViewWantsInputKey)
{
switch ((keyData & Keys.KeyCode))
{
case Keys.Return:
if ((((keyData & (Keys.Alt | Keys.Control | Keys.Shift))
== Keys.Shift) && this.Multiline))
{
return true;
}
break;
case Keys.Left:
case Keys.Right:
case Keys.Up:
case Keys.Down:
return true;
}
return !dataGridViewWantsInputKey;
}
}
Here is the code for shortcuts. RichTextBox
already supports Ctrl + '+' and Ctrl + Shift + '+' for subscript and superscript. What needs to be done is add support for Ctrl + 'B', Ctrl + 'I', and Ctrl + 'U'.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Control)
{
switch (e.KeyCode)
{
case Keys.B:
if (this.SelectionFont.Bold)
{
this.SelectionFont = new Font(this.Font.FontFamily,
this.Font.Size, ~FontStyle.Bold & this.Font.Style);
}
else
this.SelectionFont = new Font(this.Font.FontFamily,
this.Font.Size, FontStyle.Bold | this.Font.Style);
break;
case Keys.U:
if (this.SelectionFont.Underline)
{
this.SelectionFont = new Font(this.Font.FontFamily,
this.Font.Size, ~FontStyle.Underline & this.Font.Style);
}
else
this.SelectionFont = new Font(this.Font.FontFamily,
this.Font.Size, FontStyle.Underline | this.Font.Style);
break;
default:
break;
}
}
}
Second, the Cell Class
This is the most important class for RichTextBoxColumn
, because here we do the painting job for the cell.
I suggest you should first take a look here. Then, you will know how to print a RichTextBox
. I just changed it a little to print it to an Image
object.
The cell class was derived from DataGridViewTextBoxCell
due to speed problems. I changed it with DataGridViewImageCell
. I needed to do something to avoid errors in the DataGridViewImageCell
.
public class DataGridViewRichTextBoxCell : DataGridViewImageCell
{
public override Type ValueType
{
get
{
return typeof(string);
}
set
{
base.ValueType = value;
}
}
public override Type FormattedValueType
{
get
{
return typeof(string);
}
}
private static void SetRichTextBoxText(RichTextBox ctl, string text)
{
try
{
ctl.Rtf = text;
}
catch (ArgumentException)
{
ctl.Text = text;
}
}
public override void InitializeEditingControl
(int rowIndex, object initialFormattedValue,
DataGridViewCellStyle dataGridViewCellStyle)
{
base.InitializeEditingControl
(rowIndex, initialFormattedValue, dataGridViewCellStyle);
RichTextBox ctl = DataGridView.EditingControl as RichTextBox;
if (ctl != null)
{
SetRichTextBoxText(ctl, Convert.ToString(initialFormattedValue));
}
}
protected override object GetFormattedValue
(object value, int rowIndex, ref DataGridViewCellStyle cellStyle,
TypeConverter valueTypeConverter,
TypeConverter formattedValueTypeConverter,
DataGridViewDataErrorContexts context)
{
return value;
}
}
Now, we will paint the cell.
The RichTextBoxPrinter.Print
function is from the link I mentioned above.
private static readonly RichTextBox _editingControl = new RichTextBox();
private Image GetRtfImage(int rowIndex, object value, bool selected)
{
Size cellSize = GetSize(rowIndex);
if (cellSize.Width < 1 || cellSize.Height < 1)
return null;
RichTextBox ctl = null;
if (ctl == null)
{
ctl = _editingControl;
ctl.Size = GetSize(rowIndex);
SetRichTextBoxText(ctl, Convert.ToString(value));
}
if (ctl != null)
{
Size imgSize = new Size(cellSize.Width - 1, cellSize.Height - 1);
Image rtfImg = null;
if (selected)
{
ctl.BackColor = DataGridView.DefaultCellStyle.SelectionBackColor;
ctl.ForeColor = DataGridView.DefaultCellStyle.SelectionForeColor;
rtfImg = RichTextBoxPrinter.Print(ctl, imgSize.Width, imgSize.Height);
ctl.BackColor = DataGridView.DefaultCellStyle.BackColor;
ctl.ForeColor = DataGridView.DefaultCellStyle.ForeColor;
}
else
{
rtfImg = RichTextBoxPrinter.Print(ctl, imgSize.Width, imgSize.Height);
}
return rtfImg;
}
return null;
}
protected override void Paint(Graphics graphics,
Rectangle clipBounds, Rectangle cellBounds, int rowIndex,
DataGridViewElementStates cellState, object value,
object formattedValue, string errorText,
DataGridViewCellStyle cellStyle,
DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
base.Paint(graphics, clipBounds, cellBounds, rowIndex,
cellState, null, null, errorText, cellStyle, advancedBorderStyle, paintParts);
Image img = GetRtfImage(rowIndex, value, base.Selected);
if (img != null)
graphics.DrawImage(img, cellBounds.Left, cellBounds.Top);
}
Other things required for cell edit:
#region Handlers of edit events, copied from DataGridViewTextBoxCell
private byte flagsState;
protected override void OnEnter(int rowIndex, bool throughMouseClick)
{
base.OnEnter(rowIndex, throughMouseClick);
if ((base.DataGridView != null) && throughMouseClick)
{
this.flagsState = (byte)(this.flagsState | 1);
}
}
protected override void OnLeave(int rowIndex, bool throughMouseClick)
{
base.OnLeave(rowIndex, throughMouseClick);
if (base.DataGridView != null)
{
this.flagsState = (byte)(this.flagsState & -2);
}
}
protected override void OnMouseClick(DataGridViewCellMouseEventArgs e)
{
base.OnMouseClick(e);
if (base.DataGridView != null)
{
Point currentCellAddress = base.DataGridView.CurrentCellAddress;
if (((currentCellAddress.X == e.ColumnIndex) &&
(currentCellAddress.Y == e.RowIndex)) &&
(e.Button == MouseButtons.Left))
{
if ((this.flagsState & 1) != 0)
{
this.flagsState = (byte)(this.flagsState & -2);
}
else if (base.DataGridView.EditMode !=
DataGridViewEditMode.EditProgrammatically)
{
base.DataGridView.BeginEdit(false);
}
}
}
}
#endregion
Finally, the Column Class
It's easy because we already have the classes for the editor control and the cell:
public class DataGridViewRichTextBoxColumn : DataGridViewColumn
{
public DataGridViewRichTextBoxColumn()
: base(new DataGridViewRichTextBoxCell())
{
}
public override DataGridViewCell CellTemplate
{
get
{
return base.CellTemplate;
}
set
{
if (!(value is DataGridViewRichTextBoxCell))
throw new InvalidCastException("CellTemplate must" +
" be a DataGridViewRichTextBoxCell");
base.CellTemplate = value;
}
}
}
Using the Code
Use it just like other columns for DataGridView
. See the demo source code.
Points of Interest
At first, the cell class was derived from DataGridViewTextBoxCell
. It's OK if the text is short. But, when the RTF content was long or it contained a picture, it became slow when entering or leaving the cell. Then, I changed the parent class to DataGridViewImageCell
, and now the speed is no longer a problem.
History
- 7/19/2014
- Add some sample code for ashsanD to demostrate how to set the specific text bold. E.g. user can search some text in the datagridview and the matched text will be highlighted.
- 4/30/2009
- Updated class
DataGridViewRichTextBoxCell
(including gzobi666's correction) - Updated
RichTextBoxPrinter
according to the D_Kondrad's correction
- 12/18/2008
- Added the code with instructions