Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Winforms: Problems validating a cell in a datagridview

I want to validate a Winforms datagridview cell with CellValidating. If a value was not set correctly by the user I set ErrorText and use e.Cancel, so that the cursor remains in the cell. The problem is now, that the error-symbol (and the error text) is not displayed (in the cell). When I delete e.Cancel the cell looses the focus and error-symbol is displayed. How can I achieve that the cell remains in edit mode and the error-symbol is displayed too?

if (...)
{
   this.datagridviewX.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "Errortext";
   e.Cancel = true;
}
else
{
   this.datagridviewX.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "";
}
like image 715
Kottan Avatar asked Oct 10 '11 14:10

Kottan


1 Answers

The behaviour you are seeing is actually due to a painting issue and not due to the error icon not being shown. What is happening is that when you set the cell's error text the icon is displayed but the text box of the cell in edit mode is painted over the icon, hence no icon shown to the user!

You have two options for fixing this - one is to simply use the row's error text so instead of:

this.datagridviewX.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = "Errortext";  
e.Cancel = true;  

You have:

this.datagridviewX.Rows[e.RowIndex].ErrorText = "Errortext";
e.Cancel = true;

The other option is to change the cell padding of the cell (moving the editing control) and painting the icon in.

I actually found this technique for solving the problem here and reproduced their code below (in C# and not VB.Net).

First you have your cell validating event where you add some code to change the cell padding:

void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
    if (string.IsNullOrEmpty(e.FormattedValue.ToString()))
    {
        DataGridViewCell cell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];

        cell.ErrorText =
            "Company Name must not be empty";

        if (cell.Tag == null)
        {
            cell.Tag = cell.Style.Padding;
            cell.Style.Padding = new Padding(0, 0, 18, 0);
        }
        e.Cancel = true;

    }
    else
    {
        dataGridView1.Rows[e.RowIndex].ErrorText = string.Empty;
    }
}

That allows the icon to be seen not the editing control has moved, except the icon has moved too! So we also need to paint a new icon.

void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (dataGridView1.IsCurrentCellDirty)
    {
        if (!string.IsNullOrEmpty(e.ErrorText))
        {
            GraphicsContainer container = e.Graphics.BeginContainer();
            e.Graphics.TranslateTransform(18,0);
            e.Paint(this.ClientRectangle, DataGridViewPaintParts.ErrorIcon);
            e.Graphics.EndContainer(container);
            e.Handled = true;
        }
    }
}

Then when you end editing on the cell you need to reset the padding:

void dataGridView1_CellEndEdit(object sender, DataGridViewCellEventArgs e)
{
    if (!string.IsNullOrEmpty(dataGridView1[e.ColumnIndex, e.RowIndex].ErrorText))
    {
        DataGridViewCell cell = dataGridView1.Rows[e.RowIndex].Cells[e.ColumnIndex];
        cell.ErrorText = string.Empty;
        cell.Style.Padding = (Padding)cell.Tag;
        cell.Tag = null;
    }
}

The post where I found this neglects to set the mouse over for the new painted icon - Here is some rough code that addresses that, I don't have time to get it really working so there are some slight fudges that thought would fix - I'll tidy that up if I get a minute later.

I set DataGridView.ShowCellToolTips = true and introduce a boolean inError to track if we currently have an editing error. I then handle the MouseHover event:

void dataGridView1_MouseHover(object sender, EventArgs e)
{
    if (inError)
    {                
        Point pos = this.PointToClient(Cursor.Position);               

        if (r.Contains(pos.X - 20, pos.Y - 5))
        {                   
            t.Show("There was an error", dataGridView1.EditingControl, 3000); 
        }
    }
}

The t in that code is a form level ToolTip control, and r is a rectangle.

I populate r as below in the cell painting handler:

void dataGridView1_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
    if (dataGridView1.IsCurrentCellDirty)
    {
        if (!string.IsNullOrEmpty(e.ErrorText))
        {            
            GraphicsContainer container = e.Graphics.BeginContainer();

            r = dataGridView1.GetCellDisplayRectangle(e.ColumnIndex, e.RowIndex, true);
            e.Graphics.TranslateTransform(18, 0);
            e.Paint(this.ClientRectangle, DataGridViewPaintParts.ErrorIcon);
            e.Graphics.EndContainer(container);            

            e.Handled = true;
        }
    }
}

I'm not happy about the minus 20 and minus 5 on the position point - that is what I'd fix up if I had a bit more time.

like image 173
David Hall Avatar answered Nov 12 '22 16:11

David Hall