13

I would like to draw a red border around a DataGridView cell while it's being edited.

I've managed to draw a red border around the selected cell while it's not being edited using this code:

private void Form1_Load(object sender, EventArgs e)
{
    this.Width = 650;
    this.Height = 250;
    dataGridView1.Left = 5;
    dataGridView1.Top = 5;
    dataGridView1.Width = 600;
    dataGridView1.Height = 175;

    DataTable dt = new DataTable("Test Table");
    dt.Columns.Add("Column 1");
    dt.Columns.Add("Column 2");
    dt.Columns.Add("Column 3");
    dt.Columns.Add("Column 4");
    dt.Columns.Add("Column 5");
    dt.Rows.Add(dt.NewRow());
    dt.Rows.Add(dt.NewRow());
    dt.Rows.Add(dt.NewRow());
    dt.Rows.Add(dt.NewRow());
    dt.Rows.Add(dt.NewRow());
    dataGridView1.DataSource = dt;

    dataGridView1.AllowUserToAddRows = false;
    dataGridView1.AllowUserToDeleteRows = false;
    dataGridView1.MultiSelect = false;
    dataGridView1.SelectionMode = DataGridViewSelectionMode.CellSelect;
    dataGridView1.DefaultCellStyle.SelectionBackColor = Color.White;
    dataGridView1.CellPainting += new System.Windows.Forms.DataGridViewCellPaintingEventHandler(this.dataGridView1_CellPainting);
    dataGridView1.EditingControlShowing += new System.Windows.Forms.DataGridViewEditingControlShowingEventHandler(this.dataGridView1_EditingControlShowing);
}

private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (e.ColumnIndex != -1 && e.RowIndex != -1 && dataGridView1[e.ColumnIndex, e.RowIndex].Selected)
    {
        using (Brush borderBrush = new SolidBrush(Color.Red))
        {
            using (Pen borderPen = new Pen(borderBrush, 2))
            {
                Rectangle rectDimensions = e.CellBounds;
                rectDimensions.Width -= 2;
                rectDimensions.Height -= 2;
                rectDimensions.X = rectDimensions.Left + 1;
                rectDimensions.Y = rectDimensions.Top + 1;

                e.Graphics.DrawRectangle(borderPen, rectDimensions);

                e.Handled = true;
            }
        }
    }
}

Which produces this result:

Image1

However, when you edit a cell this happens:

Image2

It seems the EditingControl is drawing itself over the top of most of my red border. Unfortunately, I can't find a way to fix this so my red border will stay fully displayed at all times.

How can I do this???



Here's what I've tried so far:

1. Handling the EditingControlShowing() event to manually re-draw the border like this:

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    Graphics gfx = e.Control.CreateGraphics();

    using (Brush borderBrush = new SolidBrush(Color.Red))
    {
        using (Pen borderPen = new Pen(borderBrush, 2))
        {
            Rectangle rectDimensions = e.Control.ClientRectangle;
            rectDimensions.Width -= 2;
            rectDimensions.Height -= 2;
            rectDimensions.X = rectDimensions.Left + 1;
            rectDimensions.Y = rectDimensions.Top + 1;

            gfx.DrawRectangle(borderPen, rectDimensions);
        }
    }
}

But this didn't draw anything. I tried a few variations of this but all of them still drew nothing here.


2. I then tried to handle the Paint() event of the EditingControl like this:

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    e.Control.Paint -= new PaintEventHandler(dataGridView1_EditingControl_Paint);
    e.Control.Paint += new PaintEventHandler(dataGridView1_EditingControl_Paint);
}

void dataGridView1_EditingControl_Paint(object sender, PaintEventArgs e)
{
    MessageBox.Show("Starting EditingControl Paint() Event...");
}

But this event doesn't even fire. I later read somewhere that the EditingControl uses a normal TextBox, which doesn't fire the Paint() event because it's handled by Windows instead.


3. Finally, rather than trying to re-paint another border, I decided to try and hack around it by resizing the EditingControl to be smaller than my border in hopes the border would then show around it, like this:

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e)
{
    e.Control.Resize -= new EventHandler(dataGridView1_EditingControl_Resize);
    e.Control.Resize += new EventHandler(dataGridView1_EditingControl_Resize);
}

void dataGridView1_EditingControl_Resize(object sender, EventArgs e)
{
    dataGridView1.EditingControl.Left = 20;
}

However, that just gave me this result:

Image3

So the TextBox did move over to the Left, but it seems there is another control underneath it that is still blocking my red border. However, I can't find anyway to get access to that control to resize it so this didn't work for me either.


4. I also tried using the code from #1 above to re-draw the border in the Resize() event, but that still did nothing. Although, using dataGridView1.EditingControl.BackColor = Color.Red; did work so I can format some parts of the control here, but it seems trying to draw a border isn't one of them.

All I want to do is keep a red border showing around the cell while it's being edited. Do you know how I can do this?

Calcolat
  • 878
  • 2
  • 11
  • 22
  • Did you try to tackle the horrors of borderstyles? – TaW Aug 22 '15 at 10:36
  • I did have a look at them but all I could find was options to set the BorderStyle to none, inset, raised, etc. But I wasn't sure if there was a way to also set the border colors using them? – Calcolat Aug 22 '15 at 10:40
  • You are right, colors are not part of the BorderStyles. – TaW Aug 22 '15 at 10:52
  • 2
    The very basic problem is that you just don't have enough space to make that red border visible. You'll have to shrink the editing control, covered already in [this existing Q+A](http://stackoverflow.com/questions/1389451/how-do-i-handle-painting-for-a-datagridviews-editing-control). That's typically where it ends. – Hans Passant Aug 22 '15 at 12:16

3 Answers3

11

It can be done using some settings and painting specific parts of cell. To so so:

First, set CellBorderStyle to Raised or Sunken in designer or simply in form load code:

this.dataGridView1.CellBorderStyle = DataGridViewCellBorderStyle.Raised;

Then draw cells using these specific ordered rules:

  1. Draw only grid content cells not ColumnHeader cells nor RowHeader
  2. When drawing selected cells, first paint all parts except borders using e.Paint(...); then draw borders yourself
  3. Set e.Handled=true to prevent default painting
  4. When drawing non-selected cells, first paint all parts except borders using e.Paint(...)
  5. Draw top border of cells of first row and left border of cells of first column using grid background color
  6. Draw bottom border of cells of last row and right border of cells of last column using grid line color
  7. Draw bottom border of cells of non-last row and right border of cells of non-last column using grid background color
  8. Draw top border cells of non-first row and left border of cells of non-last column using grid line color 9.Set e.Handled=true to prevent default painting

This is screenshot of result, after select

enter image description here

and here is screenshot of result, when editing cell

enter image description here

and here is the code of cell paint event:

private void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    //Draw only grid content cells not ColumnHeader cells nor RowHeader cells
    if (e.ColumnIndex > -1 & e.RowIndex > -1)
    {
        //Pen for left and top borders
        using (var backGroundPen = new Pen(e.CellStyle.BackColor, 1))
        //Pen for bottom and right borders
        using (var gridlinePen = new Pen(dataGridView1.GridColor, 1))
        //Pen for selected cell borders
        using (var selectedPen = new Pen(Color.Red, 1))
        {
            var topLeftPoint = new Point(e.CellBounds.Left, e.CellBounds.Top);
            var topRightPoint = new Point(e.CellBounds.Right - 1, e.CellBounds.Top);
            var bottomRightPoint = new Point(e.CellBounds.Right - 1, e.CellBounds.Bottom - 1);
            var bottomleftPoint = new Point(e.CellBounds.Left, e.CellBounds.Bottom - 1);

            //Draw selected cells here
            if (this.dataGridView1[e.ColumnIndex, e.RowIndex].Selected)
            {
                //Paint all parts except borders.
                e.Paint(e.ClipBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

                //Draw selected cells border here
                e.Graphics.DrawRectangle(selectedPen, new Rectangle(e.CellBounds.Left, e.CellBounds.Top, e.CellBounds.Width - 1, e.CellBounds.Height - 1));

                //Handled painting for this cell, Stop default rendering.
                e.Handled = true;
            }
            //Draw non-selected cells here
            else
            {
                //Paint all parts except borders.
                e.Paint(e.ClipBounds, DataGridViewPaintParts.All & ~DataGridViewPaintParts.Border);

                //Top border of first row cells should be in background color
                if (e.RowIndex == 0)
                    e.Graphics.DrawLine(backGroundPen, topLeftPoint, topRightPoint);

                //Left border of first column cells should be in background color
                if (e.ColumnIndex == 0)
                    e.Graphics.DrawLine(backGroundPen, topLeftPoint, bottomleftPoint);

                //Bottom border of last row cells should be in gridLine color
                if (e.RowIndex == dataGridView1.RowCount - 1)
                    e.Graphics.DrawLine(gridlinePen, bottomRightPoint, bottomleftPoint);
                else  //Bottom border of non-last row cells should be in background color
                    e.Graphics.DrawLine(backGroundPen, bottomRightPoint, bottomleftPoint);

                //Right border of last column cells should be in gridLine color
                if (e.ColumnIndex == dataGridView1.ColumnCount - 1)
                    e.Graphics.DrawLine(gridlinePen, bottomRightPoint, topRightPoint);
                else //Right border of non-last column cells should be in background color
                    e.Graphics.DrawLine(backGroundPen, bottomRightPoint, topRightPoint);

                //Top border of non-first row cells should be in gridLine color, and they should be drawn here after right border
                if (e.RowIndex > 0)
                    e.Graphics.DrawLine(gridlinePen, topLeftPoint, topRightPoint);

                //Left border of non-first column cells should be in gridLine color, and they should be drawn here after bottom border
                if (e.ColumnIndex > 0)
                    e.Graphics.DrawLine(gridlinePen, topLeftPoint, bottomleftPoint);

                //We handled painting for this cell, Stop default rendering.
                e.Handled = true;
            }
        }
    }
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Setting `e.Handled` to true is dangerous. Setting the value of a *DataGridViewTextBoxCell* will overlay it, resizing does not over-paint everything and leaves a big mess. Better solution is here: https://stackoverflow.com/a/35553181/419581 – rémy May 02 '18 at 22:11
3

The most simple approach using your existing code is setting of the CellBorderStyle to Sunken as shown below:

dataGridView1.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.Sunken;

enter image description here

If you don't like Sunken, then you can achieve this by AdjustCellBorderStyle and DataGridViewAdvancedBorderStyle, in cell focus event change/customize the cell border style. Also take a look at: How to: Customize Cells and Columns in the Windows Forms DataGridView Control by Extending Their Behavior and Appearance.

I hope it w'd help you.

NASSER
  • 5,900
  • 7
  • 38
  • 57
  • That's an Interesting approach and it sort of works. My main issue is that I prefer to keep the style as flat as possible and that turns every cell into a sunken look. Also, if I use a thicker Pen width for the border while cells are not in Edit Mode, the border still becomes noticeably cut-off while in Edit mode. Is there maybe a way to apply the sunken style only to the currently selected cell and not the rest? If there is, I might be able to use that as a work-around for now. – Calcolat Aug 22 '15 at 10:58
0

It seems like the EditingControl is hosted in a parent Panel, and if you set the Opaque style of that panel to true, then the border will be painted.

E.g.

void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
    var c = e.Control;
    var p = c.Parent;
    SetStyles(p, ControlStyles.Opaque, true);
}

private static void SetStyles(Control c, ControlStyles styles, bool value) {
    MethodInfo mi = typeof(Control).GetMethod("SetStyle", BindingFlags.NonPublic | BindingFlags.Instance);
    mi.Invoke(c, new Object[] { styles, value });
}
Loathing
  • 5,109
  • 3
  • 24
  • 35
  • Thank you for your comment, I tried that and because when you set opaque style for a control, its background doesn't paint so there was some problems in rendering. for example when you minimize and the restore window, you will see the problem. The solution that I still using is which I posted in this [asnwer](http://stackoverflow.com/a/32170212/3110834), I'll be happy if you take a look at and find it helpful:) – Reza Aghaei Sep 07 '15 at 10:30