Dear DataGridView lovers,
I want a DataGridViewColumn that acts like a button but that shows an
image. Furthermore, when the user mouse-overs the image or clicks on it,
I want the image to change.
I've written some code the works, KINDA. Actually it works well, except
it generates error messages in the designer. I can't figure out why.
So, I'm going to post my code for 2 reasons:
1- To solicit help on perfecting the code. If you are a master at C#
custom controls, I need thee. Most importantly, there is an error that
happens not at run-time but in the designer, and I can't figure it out.
2- To share the code with others who are trying to write similar controls.
Problems with my code:
1- If you use the control in a DataGridView, close the form, then
re-open the form, you get an "Object reference not set to an instance of
an object" error message. You get 3 instances of that error, one for
each of the Images associated with the control. I'll paste a sample call
stack at the bottom of this message. (If you ignore the error and
continue then everything works properly. This error only appears in the
designer, not at run-time.)
2- I wanted a traditional Click event for the button. (The normal way of
testing for clicks within a DataGridView is a pain.) The way I got this
to work may is somewhat inelegant. It works though.
3- In order to get the mouseovers to work properly, I made
MouseEnterUnsharesRow always return true. This is probably bad design,
because it could lead to lots of memory being used by the DataGridView.
Without further ado, I give you the DataGridViewImageButtonColumn!
<cue applause>
******************************************************************
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
namespace CustomControls
{
public class DataGridViewImageButtonCell : DataGridViewTextBoxCell
{
public DataGridViewImageButtonCell()
: base()
{
}
enum MultiStateImageStates
{
StateNormal,
StateMouseOver,
StateClicked
}
Image imageToDraw = null;
void SetImageFromState(MultiStateImageStates state, int rowIndex)
{
DataGridViewImageButtonColumn parent =
(DataGridViewImageButtonColumn)this.OwningColumn;
if (parent != null) {
switch (state) {
case MultiStateImageStates.StateNormal:
imageToDraw = parent.ImageNormal;
break;
case MultiStateImageStates.StateMouseOver:
imageToDraw = parent.ImageMouseOver;
break;
case MultiStateImageStates.StateClicked:
imageToDraw = parent.ImageClicked;
break;
}
}
if (imageToDraw != null) {
parent.DataGridView.InvalidateCell(parent.Index, rowIndex);
}
}
protected override void Paint(System.Drawing.Graphics graphics,
System.Drawing.Rectangle clipBounds, System.Drawing.Rectangle
cellBounds, int rowIndex, DataGridViewElementStates elementState, object
value, object formattedValue, string errorText, DataGridViewCellStyle
cellStyle, DataGridViewAdvancedBorderStyle advancedBorderStyle,
DataGridViewPaintParts paintParts)
{
const int padding = 5;
base.Paint(graphics, clipBounds, cellBounds, rowIndex,
elementState, value, String.Empty, errorText, cellStyle,
advancedBorderStyle, paintParts);
if (imageToDraw == null) {
DataGridViewImageButtonColumn parent =
(DataGridViewImageButtonColumn)this.OwningColumn;
if (parent != null) {
imageToDraw = parent.ImageNormal;
}
}
if (imageToDraw != null) {
graphics.DrawImage(imageToDraw, cellBounds.Left +
padding, cellBounds.Top + padding);
}
}
protected override void OnMouseEnter(int rowIndex)
{
SetImageFromState(MultiStateImageStates.StateMouseOver,
rowIndex);
base.OnMouseEnter(rowIndex);
}
protected override void OnMouseLeave(int rowIndex)
{
SetImageFromState(MultiStateImageStates.StateNormal, rowIndex);
base.OnMouseLeave(rowIndex);
}
protected override void
OnMouseDown(DataGridViewCellMouseEventArgs e)
{
SetImageFromState(MultiStateImageStates.StateClicked,
e.RowIndex);
base.OnMouseDown(e);
}
protected override void OnClick(DataGridViewCellEventArgs e)
{
DataGridViewImageButtonColumn parent =
(DataGridViewImageButtonColumn)this.OwningColumn;
if (parent != null) {
parent.CellWasClicked(this);
}
base.OnClick(e);
}
protected override bool MouseEnterUnsharesRow(int rowIndex)
{
return true;
}
}
public class DataGridViewImageButtonColumn : DataGridViewTextBoxColumn
{
public DataGridViewImageButtonColumn()
: base()
{
this.ReadOnly = true;
DataGridViewImageButtonCell template = new
DataGridViewImageButtonCell();
base.CellTemplate = template;
}
void SetRowHeightFromImage(Image image)
{
const int verticalPadding = 10;
if (image != null) {
int minimumHeight = image.Height + verticalPadding;
if (this.DataGridView.RowTemplate.Height < minimumHeight) {
this.DataGridView.RowTemplate.Height = minimumHeight;
}
}
}
Image imageNormal = null;
public Image ImageNormal
{
get { return imageNormal; }
set
{
imageNormal = value;
SetRowHeightFromImage(imageNormal);
}
}
Image imageMouseOver = null;
public Image ImageMouseOver
{
get { return imageMouseOver; }
set
{
imageMouseOver = value;
SetRowHeightFromImage(imageMouseOver);
}
}
Image imageClicked = null;
public Image ImageClicked
{
get { return imageClicked; }
set
{
imageClicked = value;
SetRowHeightFromImage(imageClicked);
}
}
internal void CellWasClicked(DataGridViewImageButtonCell cell)
{
if (CellClick != null) {
EventArgs eventArgs = new EventArgs();
CellClick(cell, eventArgs);
}
}
public event EventHandler CellClick;
}
}
******************************************************************
Sample call stack for the error in the designer:
Object reference not set to an instance of an object
at System.ComponentModel.ReflectPropertyDescriptor.SetValue(Object
component, Object value)
at
System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializePropertyAssignStatement(IDesignerSerializationManager
manager, CodeAssignStatement statement, CodePropertyReferenceExpression
propertyReferenceEx, Boolean reportError)
at
System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeAssignStatement(IDesignerSerializationManager
manager, CodeAssignStatement statement)
at
System.ComponentModel.Design.Serialization.CodeDomSerializerBase.DeserializeStatement(IDesignerSerializationManager
manager, CodeStatement statement)
Patrick B. - 20 Jun 2008 22:25 GMT
If anyone else out there other than me actually needs a
"DataGridViewImageButtonColumn," here's the follow up to my last post.
I figured out the problem with my code that was causing the error
message. The function SetRowHeightFromImage() needs to be changed to the
following:
void SetRowHeightFromImage(Image image)
{
const int verticalPadding = 10;
if ((image != null) && (this.DataGridView != null)) {
int minimumHeight = image.Height + verticalPadding;
if (this.DataGridView.RowTemplate.Height < minimumHeight) {
this.DataGridView.RowTemplate.Height = minimumHeight;
}
}
}
Also, I just found an article on CodeProject from somebody trying to do
nearly the same thing. His approach is different. (For example, he
derives his class from DataGridViewButtonCell.) See the article here:
http://www.codeproject.com/KB/grid/DGV_ImageButtonCell.aspx
Thanks!
Patrick
(pls respond only in group)