Introduction
The DataGridView
has a button column that can display text, and it also has an image column. But, it does not have a button column that can display an image. This was a problem because I wanted my newer projects to be visually consistent with some earlier projects written using C++ Builder and TMS Software's Advanced StringGrid. In these earlier C++ projects, an image was used to display information, and buttons were used to execute actions. You never clicked on an image to perform an action. Buttons with text are not an acceptable option because they take up too much space compared to a smaller button with an icon on it. In addition to having a button display an image, I also wanted to be able to enable or disable the button based on programming or security logic.
The major technical obstacle I ran into was that when the DataGridView.Columns.Add()
method is invoked, behind the scenes in the .NET Framework, only the empty constructor for the corresponding DataGridViewButtonCell
class is called. So, creating an extended button cell class where the images are passed as a parameter of the constructor was not a design option. Writing hard-coded classes for each type of image button cell created a lot of redundant code, so that was a problem too.
The solution I came up with was to write an abstract DataGridViewImageButtonCell
class that derived from the DataGridViewButtonCell
class. This class has several concrete methods and a single abstract method called LoadImages()
. When you derive a new cell from this class, you will be forced by the compiler to write a new LoadImages()
routine.
So, in order to create a particular button (for example, a Delete button), there are three steps:
Step 1: Derive a specific DataGridViewImageButtonDeleteCell
class from the abstract DataGridViewImageButtonCell
class. Override the abstract LoadImages()
method with a new method that loads the images that represent what this new cell's action does. In this example, I would load Delete images. Depending on the method you use to load the images, this routine can be as short as three lines of code.
public class DataGridViewImageButtonDeleteCell : DataGridViewImageButtonCell
{
public override void LoadImages()
{
_buttonImageHot = Image.FromFile("C:\\delete_16_h.bmp");
_buttonImageNormal = Image.FromFile("C:\\delete_16.bmp");
_buttonImageDisabled = Image.FromFile("C:\\delete_d.bmp");
}
}
Step 2: Create a DataGridViewImageButtonDeleteColumn
that derives from DataGridViewButtonColumn
to display the Delete button cells.
public class DataGridViewImageButtonDeleteColumn : DataGridViewButtonColumn
{
public DataGridViewImageButtonDeleteColumn()
{
this.CellTemplate = new DataGridViewImageButtonDeleteCell();
this.Width = 22;
this.Resizable = DataGridViewTriState.False;
}
}
Step 3: Add the column to the grid in order to display the Delete image button.
DataGridViewImageButtonDeleteColumn columnDelete =
new DataGridViewImageButtonDeleteColumn();
dataGridView1.Columns.Add(columnDelete);
While this solution did the things I wanted it to do, I did not want to have to use a local image file or add an image to the resource file of every project I wanted to use the button column in. I wanted to be able to create a Delete button column and drop it into any project I had without any additional steps. So, I decided to embed the images in each class as a byte array. In order to do this, I wrote a small utility to read a bitmap and convert it into a hex string.
using System.IO;
using Microsoft.VisualBasic;
StringBuilder sb = new StringBuilder();
Image image = Image.FromFile("Enter filename here");
MemoryStream ms = new MemoryStream();
image.Save(ms, ImageFormat.Bmp);
byte[] byteArray = ms.ToArray();
for (int idx = 0; idx < byteArray.Length; idx++)
{
if (idx % 15 == 0)
{
sb.Append("\n");
}
sb.Append("0x");
if (byteArray[idx] < 16)
{
sb.Append("0");
}
sb.Append(Conversion.Hex(byteArray[idx]));
sb.Append(", ");
}
TextBox1.Text = sb.ToString();
The hex string created as output from this utility is hard-coded into the LoadImages()
method of the derived DataGridViewImageButtonDeleteCell
class. This way, the Delete images becomes part of the class. By creating a collection of concrete classes with embedded images derived from the abstract DataGridViewImageButtonCell
class on my hard-drive, I can link them into any project I write and have them all look identical without any extra effort. It also makes it easier to share them with other programmers since the column class, cell class, and the embedded images are now in a single text file.
Miscellaneous Notes
Each of the cells have a Normal, Hot, and Disabled image. If you have your desktop set to the Windows Classic theme, or you have the VisualStyles disabled in your program, then only the Normal and Disabled image will be seen. If you have Windows XP or Vista themes activated and VisualStyles enabled in your program, then moving the mouse over a button will activate the Hot image.
Also, this code is written for 16x16 icons displayed in 22x22 cells. Using a different size icon or cell will require you to tweak the variables and constants in the Paint()
method of the abstract class.
Background
In my earlier programs, the grids I had would display various resources, and the buttons on each line would change their status or open forms to modify data. These resources could be dispatched, taken out-of-service, marked available, etc. at the click of a button. Having a series of buttons next to an item made this easy.
In this article and the included example, I used Save, Print, and Delete to show how the grid button would work. But, this was for illustrative purposes only. In a real program, I would just select a grid line and hit the Delete button, or put the Save and Print on the File menu where the user expects to see it. I'm not suggesting that putting these particular buttons in a grid is good GUI design.
Things to Do
I was not able to get transparency working on my buttons, but I only worked on this for a day so, maybe more later if I need it. I would not want to put both an icon and text on a grid button, but it could be an option for people who would. The utility to create the hex string is very basic. It would be nice to make it a full-featured program with a dialog box to select the image and allow a variety of formats instead of only the hardcoded BMP.
History
- 06/20/08: Version 1.0 released
- 06/22/08: Article updated to make the process for creating a button column more clear.
I labelled each part Step 1, Step 2 and Step 3 and gave an explicit example of loading the image.