Mimickin' Excel Comments in the DataGridView

I had a need to make the WinForm DataGridView bend to my will by having some Excel-like behavior. Since I had do do some digging to make this happen, thought I'd share.

First off, my goal was to include those little comment indicator triangles in specific cells. In order to do this, you need to handle the the CellPainting event. And no - before you ask - I haven't yet created my own grid control to inherit and extend the grid behavior...but it is on my list of refactoring tasks to consider.

image

Here is the CellPainting event handler (for simplicity, I'm always showing the little glyph. In reality, I selectively show it based on the data for the row):

 void DataGridView_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
        {
            if (e.RowIndex >= 0 && e.ColumnIndex == 1)
            {
                Color backColor = ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected) ? e.CellStyle.SelectionBackColor : e.CellStyle.BackColor;
                Color foreColor = ((e.State & DataGridViewElementStates.Selected) == DataGridViewElementStates.Selected) ? e.CellStyle.SelectionForeColor : e.CellStyle.ForeColor;
                Brush backBrush = new SolidBrush(backColor);
                Brush foreBrush = new SolidBrush(foreColor);
                Brush gridBrush = new SolidBrush(DataGridView.GridColor);
                Pen gridLinePen = new Pen(gridBrush);

                Rectangle rect = new Rectangle(e.CellBounds.X + 1, e.CellBounds.Y + 1, e.CellBounds.Width - 4, e.CellBounds.Height - 4);
                try
                {
                    e.Graphics.FillRectangle(backBrush, e.CellBounds);
                    e.Graphics.DrawLine(gridLinePen, e.CellBounds.Left, e.CellBounds.Bottom - 1, e.CellBounds.Right - 1, e.CellBounds.Bottom - 1);
                    //e.Graphics.DrawLine(gridLinePen, e.CellBounds.Right - 1, e.CellBounds.Top, e.CellBounds.Right - 1, e.CellBounds.Bottom);   
                    Point[] triPoints = new Point[] { new Point(e.CellBounds.Right - 10, e.CellBounds.Y), new Point(e.CellBounds.Right, e.CellBounds.Y), new Point(e.CellBounds.Right + 10, e.CellBounds.Y + 20) };
                    e.Graphics.DrawPolygon(Pens.Red, triPoints);
                    e.Graphics.FillPolygon(new SolidBrush(Color.Red), triPoints);                  
                    
                    SizeF textSize = e.Graphics.MeasureString(e.Value.ToString(), e.CellStyle.Font);                    
                    int ypad = Convert.ToInt16((e.CellBounds.Height - textSize.Height) / 2); // align middle                    
                    
                    e.Graphics.DrawString(TruncateString(e.Value.ToString(), e.CellBounds.Width, e.Graphics, e.CellStyle.Font), e.CellStyle.Font, foreBrush, e.CellBounds.X + e.CellStyle.Padding.Left, e.CellBounds.Y + ypad);

                    e.Handled = true;
                }
                finally
                {
                    backBrush.Dispose();
                    foreBrush.Dispose();
                    gridBrush.Dispose();
                    gridLinePen.Dispose();
                }
            }
        }

And a quick and dirty function to do the ellipses for text that is wider than the cell has room for (btw...I'm only breaking on words):

 private string TruncateString(string text, int length, Graphics g, Font font)
        {                        
            string[] words = text.Split(new string[] { " " },StringSplitOptions.RemoveEmptyEntries);            
            List<string> output = new List<string>();
            string word;
            SizeF wordSize;
            int outputLength = 0;
            bool addEllipse = false;

            for(int i=0; i< words.Length; i++) 
            {
                word = words[i];
                wordSize = g.MeasureString(word, font);
                if (outputLength + wordSize.Width <= length)
                {
                    output.Add(word);
                    outputLength += Convert.ToInt16(wordSize.Width) + 1;
                }
                else
                {
                    addEllipse = true;
                    break;
                }
            }

            return String.Join(" ", output.ToArray()) + ((addEllipse) ? "..." : string.Empty);
        }

Which gives me the look I'm after:

image

Still messing with it a little, e.g. the size of the triangle, mouseover tooltips on the glyph, all the wacky conditions of the grid styling, etc.; but you get the idea.