3
votes

In my C# Windows Forms application I have a DataGridView that is bound to a BindingList<Item> list which in turn is initialised with a List<Item>.

// bind view to controller
myDataGridView.DataBindings.Add("DataSource", myController, "Items");

// bind controller to model
Items = new BindingList<Item>(model.Items);

Hence, the columns of the datagrid are generated according to the properties of class Item. I provided a handler method for the DataGridViews CellFormatting event to display certain cell values depending on certain property values of theItem type:

myDataGridView.CellFormatting += new DataGridViewCellFormattingEventHandler(myontroller.HandleCellFormatting);

I now also want to add one of two possible icons to every row in the grid, also depending on the value of certain properties of Item. Note that there is now direct correspondance to any of Items' properties, so I cannot have an extra column in my grid to hold the icon. So guess I have to either add an icon to an already existing cell or maybe generate an appropriate column on the fly. Any ideas ?

1
If you can't directly edit the Item class, could you create an extension method for it that decides which icon to display? - Robin Bennett
It's probably worth mentioning that in MVVM development, the ViewModel exists to do this sort of conversion between your model (Item) and the View's bindings. - Robin Bennett
@RobinBennett: I could both add an extension method and let the ViewModel (Controller) do the conversion, but there is still the question on where to put the icon in the dataGridView, since it has been generated to have exactly as many columns as the Model has properties. - Angle.Bracket
Sorry, I wasn't very clear (and an extension method wouldn't help if you need a property). In this case a ViewModel would be an object that looks like an Item except with an extra property. It would probably inherit from Item, and have a constructor that took an Item and stored it in a private property. - Robin Bennett
Sounds good. I'll give it a try ... - Angle.Bracket

1 Answers

4
votes

You need to handle CellPainting event of DataGridView and paint the cell yourself.

Example

This example shows how you can draw an image in a bound-column of DataGridView so the column shows bound data, as well as image. For example, here I decided to draw a red icon for negative numbers, a silver icon for zero numbers and a green icon for positive numbers:

enter image description here

To do so, define some variables to keep reference to the images. We will use this variables to render images and also to dispose the image when we no more need it:

Image zero, negative, positive;

Handle Load event of the form and images from file, resource or wherever you have stored images and assign to those variables. Set up data binding. Set up a suitable left padding for the cell in which you are going to paint the icon:

private void Form1_Load(object sender, EventArgs e)
{
    var list = new[] {
        new { C1 = "A", C2 = -2 },
        new { C1 = "B", C2 = -1 },
        new { C1 = "C", C2 = 0 },
        new { C1 = "D", C2 = 1 },
        new { C1 = "E", C2 = 2 },
    }.ToList();
    dataGridView1.DataSource = list;

    zero = new Bitmap(16, 16);
    using (var g = Graphics.FromImage(zero))
        g.Clear(Color.Silver);
    negative = new Bitmap(16, 16);
    using (var g = Graphics.FromImage(negative))
        g.Clear(Color.Red);
    positive = new Bitmap(16, 16);
    using (var g = Graphics.FromImage(positive))
        g.Clear(Color.Green);

    //Set padding to have enough room to draw image
    dataGridView1.Columns[1].DefaultCellStyle.Padding = new Padding(18, 0, 0, 0);
}

Handle CellPainting event of the DataGridView and render the cell contents and the image for the columns which you want:

private void DataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    //We don't need custom paint for row header or column header
    if (e.RowIndex < 0 || e.ColumnIndex != 1) return;

    //We don't need custom paint for null value
    if (e.Value == null || e.Value == DBNull.Value) return;

    //Choose image based on value
    Image img = zero;
    if ((int)e.Value < 0) img = negative;
    else if ((int)e.Value > 0) img = positive;

    //Paint cell
    e.Paint(e.ClipBounds, DataGridViewPaintParts.All);
    e.Graphics.DrawImage(img, e.CellBounds.Left + 1, e.CellBounds.Top + 1,
        16, e.CellBounds.Height - 3);

    //Prevent default paint
    e.Handled = true;
}

Handle FormClosing event to dispose the images:

private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
    //Dispose images
    if (zero != null) zero.Dispose();
    if (negative != null) negative.Dispose();
    if (positive != null) positive.Dispose();
}