Drawing an Image before the Text in a Custom DataGridViewTextBoxCell

Hi,

I have a custom DataGridViewTextBoxCell. I need to paint an icon "prefixing" the Text.

I tried overriding the Paint. And pushed the padding of the text. I have drawn my Icon in that space. But in this scenario my AutoSizeColumnsMode does not work. And the cell padding given for this cell makes the Header to be drawn with different width and the content cells to be drawn with different width.

So, I would like to have a solution

1. Which allows me to put an Icon before the text.

2. Does not affect the AutoSizeColumns Mode functionality.

3. Keeps the cell resizing in sync with the Header Cell.

Thanks in advance for any help offered.




Answer this question

Drawing an Image before the Text in a Custom DataGridViewTextBoxCell

  • keiop

    Hello Ravi,

    You may want to overwrite the GetPreferredSize function of your custom cell. Looking at my implementations of these 2 custom cells under http://forums.microsoft.com/MSDN/ShowPost.aspx PostID=231989&SiteID=1 could be helpful.

    Thanks, -regis

    Microsoft Windows Forms team
    This post is provided "as-is"


  • SeaBreeze

    Regis

    Many thanks, this is a very useful piece of sample code. I have been trying to implement Excel-like "auto filter" functionality for my grid (i.e. dropdowns displayed above each column containing a list of all distinct values in the column). Having struggled to get these combo boxes to appear in the first row in the grid I have now decided to implement the ComboBox objects in the header cells. I intend to modify your sample to do this.

    However, my grid is data bound so I am adding header cells in the OnColumnAdded event as follows (using your image header):

    protected override void OnColumnAdded(DataGridViewColumnEventArgs e)
    {
     DataGridViewImageColumnHeaderCell headerCell = new DataGridViewImageColumnHeaderCell();
     e.Column.DefaultHeaderCellType = headerCell.GetType();
     e.Column.HeaderCell = headerCell;
     headerCell.ImagePadding = new Padding(3, 2, 4, 4);
     headerCell.Image = Properties.Resources.MyImage;
     base.OnColumnAdded(e);
    }

    I have noticed that changes to visual properties in your code call DataGridView.UpdateCellValue with a Rowindex of -1, but Reflector tells me that this will always cause the method to throw:

    public void UpdateCellValue(int columnIndex, int rowIndex)
    {
          if ((columnIndex < 0) || (columnIndex >= this.Columns.Count))
          {
                throw new ArgumentOutOfRangeException("columnIndex");
          }
          if ((rowIndex < 0) || (rowIndex >= this.Rows.Count))
          {
                throw new ArgumentOutOfRangeException("rowIndex");
          }
          if (base.IsHandleCreated)
          {
                this.OnCellCommonChange(columnIndex, rowIndex);
          }
    }

    Did I miss something

    Also in my project I have noticed that the overridden GetPreferredSize method is never being called.

    kh


  • Jayender

    Hi there,

    You encountered a bug in my sample code. A header cell shouldn't be calling the DataGridView.UpdateCellValue(...) - my mistake. Sorry 'bout that.

    Two things may need to happen when a cell property changes:

    (a) if the property affects the rendering of the cell, then it need to be repainted. Calling this.DataGridView.InvalidateCell(this); takes care of that.

    (b) if the property change affects the preferred size of the cell, then the related autosized elements need to be re-autosized via calls to AutoResizeColumn, AutoResizeColumnHeadersHeight, etc... This is a much trickier problem because those functions are protected. The two articles I mention earlier in this thread contain some help about how to do that. If nothing uses autosizing features in your grid, this is not an issue.

    FYI, our internal function OnCellCommonChange takes care of those two jobs (a) & (b).

    In retrospect, I think we should have made it easier to take care of (b) in custom cells.

    Regarding GetPreferredSize not being called: this function only gets called if the cell is autosized somehow (i.e. it belongs to an autosized row or an autosized column). If this is not the case, there is no need to know the preferred size of the cell. If your cell is supposed to be autosized and its GetPreferredSize isn't called then we have a problem...

    Thanks, -regis

    Microsoft Windows Forms team
    This post is provided "as-is"


  • Corin

    Ravi,

    Here's an implementation of a custom column header cell I wrote a while back. This could be helpful.

    using System;
    using System.Drawing;
    using System.Globalization;
    using System.Windows.Forms;
    using System.ComponentModel;

    namespace DataGridViewImageHeaderCellElements
    {
    /// <summary>
    /// Custom column header cell that can display an Image or Icon in addition to the typical Value.
    /// </summary>
    public class DataGridViewImageColumnHeaderCell : DataGridViewColumnHeaderCell
    {
    private Padding imagePadding;
    private bool imageBeforeValue;
    private Image image;
    private Icon icon;

    /// <summary>
    /// Constructor that sets the default values
    /// </summary>
    public DataGridViewImageColumnHeaderCell()
    {
    this.imageBeforeValue = true;
    }

    /// <summary>
    /// Represents the icon displayed by the cell
    /// </summary>
    [
    DefaultValue(null)
    ]
    public Icon Icon
    {
    get
    {
    return this.icon;
    }
    set
    {
    this.icon = value;
    if (this.DataGridView != null)
    {
    this.DataGridView.UpdateCellValue(this.ColumnIndex, -1);
    }
    }
    }

    /// <summary>
    /// Represents the image displayed by the cell
    /// </summary>
    [
    DefaultValue(null)
    ]
    public Image Image
    {
    get
    {
    return this.image;
    }
    set
    {
    this.image = value;
    if (this.DataGridView != null)
    {
    this.DataGridView.UpdateCellValue(this.ColumnIndex, -1);
    }
    }
    }

    /// <summary>
    /// Determines if the Image or Icon is displayed on the left of right of the Value.
    /// </summary>
    [
    DefaultValue(true)
    ]
    public bool ImageBeforeValue
    {
    get
    {
    return this.imageBeforeValue;
    }
    set
    {
    if (this.ImageBeforeValue != value)
    {
    this.imageBeforeValue = value;
    if (this.DataGridView != null)
    {
    this.DataGridView.InvalidateCell(this);
    }
    }
    }
    }

    /// <summary>
    /// Defines a padding around the image or icon.
    /// </summary>
    public Padding ImagePadding
    {
    get
    {
    return this.imagePadding;
    }
    set
    {
    if (this.ImagePadding != value)
    {
    if (value.Left < 0 || value.Right < 0 || value.Top < 0 || value.Bottom < 0)
    {
    if (value.All != -1)
    {
    value.All = 0;
    }
    else
    {
    value.Left = Math.Max(0, value.Left);
    value.Right = Math.Max(0, value.Right);
    value.Top = Math.Max(0, value.Top);
    value.Bottom = Math.Max(0, value.Bottom);
    }
    }
    this.imagePadding = value;
    if (this.DataGridView != null)
    {
    this.DataGridView.UpdateCellValue(this.ColumnIndex, -1);
    }
    }
    }
    }

    /// <summary>
    /// Custom Clone implementation that copies the cell specific properties.
    /// </summary>
    public override object Clone()
    {
    DataGridViewImageColumnHeaderCell dataGridViewCell = base.Clone() as DataGridViewImageColumnHeaderCell;
    if (dataGridViewCell != null)
    {
    dataGridViewCell.ImageBeforeValue = this.ImageBeforeValue;
    dataGridViewCell.ImagePadding = this.ImagePadding;
    dataGridViewCell.Image = this.Image;
    dataGridViewCell.Icon = this.Icon;
    }
    return dataGridViewCell;
    }

    /// <summary>
    /// Utility function that adjusts the vertical padding of the cell to account
    /// for the additional image or icon.
    /// </summary>
    private Padding GetAdjustedCellPadding(DataGridViewCellStyle cellStyle)
    {
    if (this.image != null)
    {
    if (this.imageBeforeValue)
    {
    return new Padding(cellStyle.Padding.Left + this.imagePadding.Horizontal + this.image.Width, cellStyle.Padding.Top, cellStyle.Padding.Right, cellStyle.Padding.Bottom);
    }
    else
    {
    return new Padding(cellStyle.Padding.Left, cellStyle.Padding.Top, cellStyle.Padding.Right + this.imagePadding.Horizontal + this.image.Width, cellStyle.Padding.Bottom);
    }
    }
    else if (this.icon != null)
    {
    if (this.imageBeforeValue)
    {
    return new Padding(cellStyle.Padding.Left + this.imagePadding.Horizontal + this.icon.Width, cellStyle.Padding.Top, cellStyle.Padding.Right, cellStyle.Padding.Bottom);
    }
    else
    {
    return new Padding(cellStyle.Padding.Left, cellStyle.Padding.Top, cellStyle.Padding.Right + this.imagePadding.Horizontal + this.icon.Width, cellStyle.Padding.Bottom);
    }
    }
    return cellStyle.Padding;
    }

    /// <summary>
    /// Custom implementation of GetContentBounds to ensure that the potential image or icon is not part
    /// of the content bounds.
    /// </summary>
    protected override Rectangle GetContentBounds(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex)
    {
    if (this.image != null || this.icon != null)
    {
    if (cellStyle == null)
    {
    throw new ArgumentNullException("cellStyle");
    }
    cellStyle.Padding = GetAdjustedCellPadding(cellStyle);
    }

    return base.GetContentBounds(graphics, cellStyle, rowIndex);
    }

    /// <summary>
    /// Custom implementation of GetPreferredSize that take into account the potential
    /// image or icon and its padding.
    /// </summary>
    protected override Size GetPreferredSize(Graphics graphics, DataGridViewCellStyle cellStyle, int rowIndex, Size constraintSize)
    {
    if (rowIndex != -1)
    {
    throw new ArgumentOutOfRangeException("rowIndex");
    }

    if (this.DataGridView == null)
    {
    return new Size(-1, -1);
    }

    Size basePreferredSize = base.GetPreferredSize(graphics, cellStyle, rowIndex, constraintSize);

    if (constraintSize.Width == 0)
    {
    if (this.image != null)
    {
    basePreferredSize.Width += image.Width + this.ImagePadding.Horizontal;
    }
    else if (this.icon != null)
    {
    basePreferredSize.Width += icon.Width + this.ImagePadding.Horizontal;
    }
    }

    if (constraintSize.Height == 0)
    {
    DataGridViewAdvancedBorderStyle dgvabsPlaceholder = new DataGridViewAdvancedBorderStyle(), dgvabsEffective;
    dgvabsEffective = this.DataGridView.AdjustColumnHeaderBorderStyle(this.DataGridView.AdvancedColumnHeadersBorderStyle,
    dgvabsPlaceholder,
    false /*isFirstDisplayedColumn*/,
    false /*isLastVisibleColumn*/);
    Rectangle borderWidthsRect = BorderWidths(dgvabsEffective);
    int borderAndPaddingHeights = borderWidthsRect.Top + borderWidthsRect.Height + cellStyle.Padding.Vertical;
    if (this.image != null)
    {
    basePreferredSize.Height = Math.Max(basePreferredSize.Height, image.Height + borderAndPaddingHeights + this.ImagePadding.Vertical);
    }
    else if (this.icon != null)
    {
    basePreferredSize.Height = Math.Max(basePreferredSize.Height, icon.Height + borderAndPaddingHeights + this.ImagePadding.Vertical);
    }
    }

    return basePreferredSize;
    }

    /// <summary>
    /// Custom painting implementation that paints the image or icon on top of the base implementation.
    /// </summary>
    protected override void Paint(Graphics graphics,
    Rectangle clipBounds,
    Rectangle cellBounds,
    int rowIndex,
    DataGridViewElementStates dataGridViewElementState,
    object value,
    object formattedValue,
    string errorText,
    DataGridViewCellStyle cellStyle,
    DataGridViewAdvancedBorderStyle advancedBorderStyle,
    DataGridViewPaintParts paintParts)
    {
    if (cellStyle == null)
    {
    throw new ArgumentNullException("cellStyle");
    }

    Padding cellStylePadding = cellStyle.Padding;
    int imageHeight = 0, imageWidth = 0;

    if (this.image != null || this.icon != null)
    {
    if (this.image != null)
    {
    imageHeight = this.image.Height;
    imageWidth = this.image.Width;
    }
    else if (this.icon != null)
    {
    imageHeight = this.icon.Height;
    imageWidth = this.icon.Width;
    }
    cellStyle.Padding = GetAdjustedCellPadding(cellStyle);
    }

    base.Paint(graphics,
    clipBounds,
    cellBounds,
    rowIndex,
    dataGridViewElementState,
    value,
    formattedValue,
    errorText,
    cellStyle,
    advancedBorderStyle,
    paintParts);

    if (this.image != null || this.icon != null)
    {
    Rectangle valBounds = cellBounds;
    Rectangle borderWidths = BorderWidths(advancedBorderStyle);
    valBounds.Offset(borderWidths.X, borderWidths.Y);
    valBounds.Width -= borderWidths.Right;
    valBounds.Height -= borderWidths.Bottom;

    if (valBounds.Width > 0 && valBounds.Height > 0)
    {
    if (this.imageBeforeValue)
    {
    if (this.DataGridView.RightToLeft == RightToLeft.Yes)
    {
    valBounds.Offset(Math.Max(cellStylePadding.Right + this.ImagePadding.Right, valBounds.Width - cellStylePadding.Left - this.ImagePadding.Left - imageWidth), cellStylePadding.Top + this.ImagePadding.Top);
    }
    else
    {
    valBounds.Offset(cellStylePadding.Left + this.ImagePadding.Left, cellStylePadding.Top + this.ImagePadding.Top);
    }
    }
    else
    {
    if (this.DataGridView.RightToLeft == RightToLeft.Yes)
    {
    valBounds.Offset(cellStylePadding.Right + this.ImagePadding.Right, cellStylePadding.Top + this.ImagePadding.Top);
    }
    else
    {
    valBounds.Offset(Math.Max(cellStylePadding.Left + this.ImagePadding.Left, valBounds.Width - cellStylePadding.Right - this.ImagePadding.Right - imageWidth), cellStylePadding.Top + this.ImagePadding.Top);
    }
    }
    valBounds.Width -= cellStylePadding.Horizontal + this.ImagePadding.Horizontal;
    valBounds.Height -= cellStylePadding.Vertical + this.ImagePadding.Vertical;
    if (valBounds.Width > 0 && valBounds.Height > 0)
    {
    switch (cellStyle.Alignment)
    {
    case DataGridViewContentAlignment.MiddleCenter:
    case DataGridViewContentAlignment.MiddleLeft:
    case DataGridViewContentAlignment.MiddleRight:
    valBounds.Y += Math.Max(0, (valBounds.Height - imageHeight) / 2);
    break;
    case DataGridViewContentAlignment.BottomCenter:
    case DataGridViewContentAlignment.BottomLeft:
    case DataGridViewContentAlignment.BottomRight:
    valBounds.Y += Math.Max(0, valBounds.Height - imageHeight);
    break;
    }
    Region reg = graphics.Clip;
    graphics.SetClip(Rectangle.Intersect(valBounds, Rectangle.Truncate(graphics.VisibleClipBounds)));
    try
    {
    if (this.image != null)
    {
    Rectangle imageBounds = new Rectangle(valBounds.Location, this.image.Size);
    graphics.DrawImage(this.image, imageBounds);
    }
    else if (this.icon != null)
    {
    Rectangle iconBounds = new Rectangle(valBounds.Location, this.icon.Size);
    graphics.DrawIconUnstretched(this.icon, iconBounds);
    }
    }
    finally
    {
    graphics.Clip = reg;
    }
    }
    }
    cellStyle.Padding = cellStylePadding;
    }
    }

    /// <summary>
    /// Custom string representation of this custom cell type.
    /// </summary>
    public override string ToString()
    {
    return "DataGridViewImageColumnHeaderCell { ColumnIndex=" + this.ColumnIndex.ToString(CultureInfo.CurrentCulture) + " }";
    }
    }
    }

    Usage sample:

    DataGridViewTextBoxColumn dataGridViewTextBoxColumn = new DataGridViewTextBoxColumn();
    dataGridViewTextBoxColumn.DefaultHeaderCellType = typeof(DataGridViewImageColumnHeaderCell);
    dataGridViewTextBoxColumn.HeaderText = "Directory";
    DataGridViewImageColumnHeaderCell dataGridViewImageColumnHeaderCell = dataGridViewTextBoxColumn.HeaderCell as DataGridViewImageColumnHeaderCell;
    dataGridViewImageColumnHeaderCell.ImagePadding = new Padding(3, 2, 4, 4);
    Bitmap directoryImage = global::DataGridViewImageHeaderCellElementsSample.Properties.Resources.Directory;
    directoryImage.MakeTransparent();
    dataGridViewImageColumnHeaderCell.Image = directoryImage;
    this.dataGridView1.Columns.Add(dataGridViewTextBoxColumn);

    Thanks, -regis

    Microsoft Windows Forms team
    This post is provided "as-is"


  • Drawing an Image before the Text in a Custom DataGridViewTextBoxCell