Windows Develop Bookmark and Share   
 index > Windows Forms Data Controls and Databinding > DtatGridView.RowValidating Event and changing is BindingSource's List
 

DtatGridView.RowValidating Event and changing is BindingSource's List

Hi,

I have a DataGrideView that is bound to a BindingSource that is itself Bound to another BindingSource.

My goal is to allow the user to select from a preset group of category types and allow them to add\edit\delete values for the category types.

To that end here is the setup for a dummy form to mimic the layout.

1) Create a new Windows Form Application
2) Got to the code view for the form and past the following code at the bottom of this post over top of any existing code.

I apologize for the lack of commenting and crudeness of some of the code but I was trying to keep it as close to my actual project, witch has a strongly typed dataset and is broken into a DAL, BLL and GUI while allowing you to reproduce the error with minimal effort.

This should create a form with a ComboBox and DataGridView on it. The ComboBox will have 4 options and the DataGridView will show the related categories for the selected category. Now if you add 2 items to the first category, and then add one to the second category everything should work fine.

(Code must should be 5 or less characters, and description cannot be null or empty when .Trim()ed. My actual DataGridView enforces max code length of 5 and max description length of 32, but I don’t know how to do that outside the designer so it’s not in this sample.)

After adding these rows go back to the first category and select the second item. Now select the second category and you should get a RowIndexOutOfRange Exception. Even with he line checking for exactly that. It appears that it’s starting the RowValidation on the DataGridView after it has already changed the underlying BindingSource's collection, but before syncing the DataGridView's collection to the BindingSource. So the RowIndex is fine for the DataGridView, but no the BindingSource, and even if the collection its changing to has taht many rows, you wouldn't be validating against the right row, since its now checking the new BindingSource row.

So my question is, where should I be doing this validation since I can't just abort the validation in such scenario, if that’s even possible, since it might be a new/changed and invalid row.

My best guess at to where to go next with this is trying to put something in an event either on the BindingSource or the ComboBox to catch this problem and proactively validate the row then set the selected row to -1 (witch should prevent the validation code from getting to the point where the exception is thrown.

dgvCategory_RowValidating is the place I currently try to do this validation.

D

evelopment environment is the Visual Studio 2008 Trial Version 9.0.21022.8 RTM (Making sure it can do what we need before we purchase the standard version), and I am developing for a .net 2.0 environment.

Not sure if this is something that might have been fixed in the retail version, or in .net 3.5.

I need to develop with .net 2.0 because we are still running windows 2000 on most of our machines.

Any advice is greatly appreciated.

Code Snippet

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private DataSet dsCategoryField;

public Form1()
{
this.cboCategoryField = new System.Windows.Forms.ComboBox();
this.dgvCategory = new System.Windows.Forms.DataGridView();
((System.ComponentModel.ISupportInitialize)(this.dgvCategory)).BeginInit();
this.SuspendLayout();
//
// cboCategoryField
//
this.cboCategoryField.FormattingEnabled = true;
this.cboCategoryField.Location = new System.Drawing.Point(12, 12);
this.cboCategoryField.Name = "cboCategoryField";
this.cboCategoryField.Size = new System.Drawing.Size(121, 21);
this.cboCategoryField.TabIndex = 0;
//
// dgvCategory
//
this.dgvCategory.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
this.dgvCategory.Location = new System.Drawing.Point(12, 39);
this.dgvCategory.Name = "dgvCategory";
this.dgvCategory.Size = new System.Drawing.Size(394, 264);
this.dgvCategory.TabIndex = 1;
this.dgvCategory.RowValidating += new System.Windows.Forms.DataGridViewCellCancelEventHandler(this.dgvCategory_RowValidating);
this.dgvCategory.CellParsing += new System.Windows.Forms.DataGridViewCellParsingEventHandler(this.dgvCategory_CellParsing);
this.dgvCategory.CellValidating += new System.Windows.Forms.DataGridViewCellValidatingEventHandler(this.dgvCategory_CellValidating);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(476, 393);
this.Controls.Add(this.dgvCategory);
this.Controls.Add(this.cboCategoryField);
this.Name = "Form1";
this.Text = "Form1";
this.Load += new System.EventHandler(this.Form1_Load);
((System.ComponentModel.ISupportInitialize)(this.dgvCategory)).EndInit();
this.ResumeLayout(false);
}

private void Form1_Load(object sender, EventArgs e)
{
dsCategoryField = new DataSet("CategoryDataSet");

DataTable dtCategoryField = new DataTable("CategoryField");
DataColumn dcCategoryFieldID = new DataColumn("ID", typeof(int));
DataColumn dcCategoryFieldName = new DataColumn("Name", typeof(string));
dtCategoryField.Columns.Add(dcCategoryFieldID);
dtCategoryField.Columns.Add(dcCategoryFieldName);

DataTable dtCategory = new DataTable("Category");
DataColumn dcCategoryID = new DataColumn("ID", typeof(int));
DataColumn dcCategoryFKID = new DataColumn("CategoryFieldID", typeof(int));
DataColumn dcCategoryCode = new DataColumn("Code", typeof(string));
DataColumn dcCategoryDescription = new DataColumn("Description", typeof(string));
dtCategory.Columns.Add(dcCategoryID);
dtCategory.Columns.Add(dcCategoryFKID);
dtCategory.Columns.Add(dcCategoryCode);
dtCategory.Columns.Add(dcCategoryDescription);

dsCategoryField.Tables.Add(dtCategoryField);
dsCategoryField.Tables.Add(dtCategory);

DataRelation myRelation = new DataRelation("Relation_00",
dsCategoryField.Tables["CategoryField"].Columns["ID"],
dsCategoryField.Tables["Category"].Columns["CategoryFieldID"]);

dsCategoryField.Relations.Add(myRelation);

AddCategoryField(1,"Category");
AddCategoryField(2,"SubCategory");
AddCategoryField(3,"ECategory");
AddCategoryField(4,"ESubCategory");

BindingSource bsCategoryField = new BindingSource(dsCategoryField,"CategoryField");
BindingSource bsCategory = new BindingSource(bsCategoryField,"Relation_00");

cboCategoryField.DataSource = bsCategoryField;
cboCategoryField.DisplayMember = "Name";
cboCategoryField.ValueMember = "ID";

dgvCategory.DataSource = bsCategory;
}

private void AddCategoryField(int id, string name){
DataRow row = dsCategoryField.Tables["CategoryField"].NewRow();
row["ID"] = id;
row["Name"] = name;
dsCategoryField.Tables["CategoryField"].Rows.Add(row);
}

private void dgvCategory_CellParsing(object sender, DataGridViewCellParsingEventArgs e)
{
if (e.ColumnIndex == 2) {//Code Column
if (e.Value != null) {
e.Value = e.Value.ToString().PadRight(5);
e.ParsingApplied = true;
}
}
if (e.ColumnIndex == 3) {//Description Column
if (e.Value != null) {
e.Value = e.Value.ToString().Trim();
e.ParsingApplied = true;
}
}
}
private void dgvCategory_CellValidating(object sender, DataGridViewCellValidatingEventArgs e)
{
if (dgvCategory.Rows[e.RowIndex].DataBoundItem != null) {
DataRow row = ((DataRowView)dgvCategory.Rows[e.RowIndex].DataBoundItem).Row;
string errorText = "";

if (e.ColumnIndex == 2) {
if (!ValidateCode(e.FormattedValue.ToString(),row)) {
errorText = "The Code '" + e.FormattedValue + "' has already been defined for the category '" +
cboCategoryField.Text + "'.";
}
}
if (e.ColumnIndex == 3) {
if (!ValidateDescription(e.FormattedValue.ToString())) {
errorText = "Description must not be empty";
}
}
dgvCategory.Rows[e.RowIndex].Cells[e.ColumnIndex].ErrorText = errorText;
}
}

//This is the section of code in question
private void dgvCategory_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
if (e.RowIndex < dgvCategory.Rows.Count &&
dgvCategory.Rows[e.RowIndex].DataBoundItem != null) {

DataGridViewRow dgvRow = dgvCategory.Rows[e.RowIndex];
DataRow dtRow =((DataRowView)dgvRow.DataBoundItem).Row;

StringBuilder errorText = new StringBuilder("", 40);
bool isPlural = false;
string codeValue = dgvRow.Cells[2].Value.ToString();
string descriptionValue = dgvRow.Cells[3].Value.ToString();

if (string.IsNullOrEmpty(codeValue) ||
!ValidateCode(codeValue,dtRow)) {
errorText.Append("Code");
}

if (!ValidateDescription(descriptionValue)) {
if (errorText.Length > 0) {
errorText.Append(" and ");
isPlural = true;
}
errorText.Append("Description");
}

if (errorText.Length > 0) {
if (isPlural) {
errorText.Append(" values are invalid");
}
else {
errorText.Append(" value is invalid");
}
e.Cancel = true;
}

dgvCategory.Rows[e.RowIndex].ErrorText = errorText.ToString();
}
}


private bool ValidateCode(string value, DataRow row)
{
DataRow[] rows = dsCategoryField.Tables["Category"].Select("CategoryFieldID = '" +
cboCategoryField.SelectedValue + "' AND Code = '" + value +"'");

if (rows.Length == 0 ||
(rows.Length == 1 &&
rows[0] == row)) {
return true;
}
else {
return false;
}
}
private bool ValidateDescription(string value)
{
return (string.Empty != value);
}

}
}


Blazer.Saga  Tuesday, August 26, 2008 9:59 PM
I believe I found a post that solves this problem.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=119755&SiteID=1

I put the change to the
RowValidating event in red below. The check for IsCurrentRowDirty will cause the row to validate when leaving the row but not when leaving the row after making no changes to it, all subsequent calls to validate the row will fail because it is no longer dirty. So it wont try to validate it again when leaving the DataGridView and when changing the underlying BindingSource list.

This apears to work fine for all the cases where I need it to validate.

If anyone knows of a reson why this method may be flawed please let me know.

private void dgvCategory_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
if (dgvCategory.IsCurrentRowDirty &&
dgvCategory.Rows[e.RowIndex].DataBoundItem != null) {

DataGridViewRow dgvRow = dgvCategory.Rows[e.RowIndex];
DataRow dtRow =((DataRowView)dgvRow.DataBoundItem).Row;

StringBuilder errorText = new StringBuilder("", 40);
bool isPlural = false;
string codeValue = dgvRow.Cells[2].Value.ToString();
string descriptionValue = dgvRow.Cells[3].Value.ToString();

if (string.IsNullOrEmpty(codeValue) ||
!ValidateCode(codeValue,dtRow)) {
errorText.Append("Code");
}

if (!ValidateDescription(descriptionValue)) {
if (errorText.Length > 0) {
errorText.Append(" and ");
isPlural = true;
}
errorText.Append("Description");
}

if (errorText.Length > 0) {
if (isPlural) {
errorText.Append(" values are invalid");
}
else {
errorText.Append(" value is invalid");
}
e.Cancel = true;
}

dgvCategory.Rows[e.RowIndex].ErrorText = errorText.ToString();
}
}

Blazer.Saga  Wednesday, August 27, 2008 4:19 PM
I believe I found a post that solves this problem.

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=119755&SiteID=1

I put the change to the
RowValidating event in red below. The check for IsCurrentRowDirty will cause the row to validate when leaving the row but not when leaving the row after making no changes to it, all subsequent calls to validate the row will fail because it is no longer dirty. So it wont try to validate it again when leaving the DataGridView and when changing the underlying BindingSource list.

This apears to work fine for all the cases where I need it to validate.

If anyone knows of a reson why this method may be flawed please let me know.

private void dgvCategory_RowValidating(object sender, DataGridViewCellCancelEventArgs e)
{
if (dgvCategory.IsCurrentRowDirty &&
dgvCategory.Rows[e.RowIndex].DataBoundItem != null) {

DataGridViewRow dgvRow = dgvCategory.Rows[e.RowIndex];
DataRow dtRow =((DataRowView)dgvRow.DataBoundItem).Row;

StringBuilder errorText = new StringBuilder("", 40);
bool isPlural = false;
string codeValue = dgvRow.Cells[2].Value.ToString();
string descriptionValue = dgvRow.Cells[3].Value.ToString();

if (string.IsNullOrEmpty(codeValue) ||
!ValidateCode(codeValue,dtRow)) {
errorText.Append("Code");
}

if (!ValidateDescription(descriptionValue)) {
if (errorText.Length > 0) {
errorText.Append(" and ");
isPlural = true;
}
errorText.Append("Description");
}

if (errorText.Length > 0) {
if (isPlural) {
errorText.Append(" values are invalid");
}
else {
errorText.Append(" value is invalid");
}
e.Cancel = true;
}

dgvCategory.Rows[e.RowIndex].ErrorText = errorText.ToString();
}
}

Blazer.Saga  Wednesday, August 27, 2008 4:19 PM

You can use google to search for other answers

Custom Search

More Threads

• KeyDown Search in DataGridView
• custom formatting for (too) long text
• the connection of the port
• DataGridView, BindingList<T>, EndEdit() problem
• DataGridView column resize repaint problem
• Table Adapter Issue
• DataGridview Binding: Conditionally adding an unbound column
• get data to textfield from dataset
• why is Tables[0].TableName.ToString() = "Table"??
• DataGridView getting index of the entering row