1

I have a DataGridView that consist of some rows and columns. I want to check all cells of my DataGridView for empty cells, if any cell is empty in any row then give me a message. I have a code for doing that but it checks only the cells of the first row This is my code:

    foreach (DataGridViewRow rw in dgv_absence.Rows)
    {
        if (rw.Cells[0].Value == null || rw.Cells[0].Value == DBNull.Value || string.IsNullOrWhiteSpace(rw.Cells[0].FormattedValue.ToString()) || rw.Cells[1].Value == null || rw.Cells[1].Value == DBNull.Value || string.IsNullOrWhiteSpace(rw.Cells[1].FormattedValue.ToString()))
        {
            MessageBox.Show("message","title");
            return;
        }
        else
        {
            for (int i = 0; i < dgv_absence.Rows.Count - 1; i++)
            {
                // do some thing
            }
        }
    }
Slai
  • 22,144
  • 5
  • 45
  • 53
Hunar
  • 399
  • 3
  • 7
  • 27
  • is this for a windows form app? – Juan M. Elosegui Dec 15 '16 at 16:08
  • 2
    There are several validation events. Looping all rows is an inefficient way to check one – Ňɏssa Pøngjǣrdenlarp Dec 15 '16 at 16:11
  • yes, windows forms – Hunar Dec 15 '16 at 16:13
  • Possible duplicate of [Looping through DataGridView Cells](http://stackoverflow.com/questions/13788156/looping-through-datagridview-cells) – 41686d6564 stands w. Palestine Dec 15 '16 at 16:19
  • You can use Fluent Validation if you don't want to be tight to the validation events of the framework. Also you can separate the validation process in separate project. In that way you can reuse it in another kind of project, and is not hard to set up the implementation. Check my answer here http://stackoverflow.com/a/40997368/819153, I use it there for a Asp.net application. Hope this helps – Zinov Dec 15 '16 at 16:57

4 Answers4

0

Instead of Cells[0], which only targets the first cell of each row, you need to also loop through the cells in an inner for loop. Like this:

        foreach (DataGridViewRow row in dataGridView.Rows)
        {
            foreach (DataGridViewCell cell in row.Cells)
            {

            }
        }
Forklift
  • 949
  • 8
  • 20
  • Make sure your inner loop references to Cells collection of the row and you are not trying to reference a Cells collection of the DataGridView. – Forklift Dec 15 '16 at 16:24
0

you can call this method it return if one cell is empty or not:

 public Boolean IsDataGridViewEmpty( DataGridView dataGridView)
    {
        bool isEmpty = false;
        foreach (DataGridViewRow row in dataGridView.Rows)
        {
            if (!row.IsNewRow)
            {
                foreach (DataGridViewCell cell in row.Cells)
                {
                    //if (!string.IsNullOrEmpty(cell.Value.ToString ()))
                    //{
                    if (string.IsNullOrEmpty(cell.Value.ToString().Trim()))
                    {
                        isEmpty = true;
                        break; // TODO: might not be correct. Was : Exit For
                        //  }
                    }
                }
            }
        }
        return isEmpty;
    }

you can call it by:

  if (!IsDataGridViewEmpty( dgv_absence))
        {
           MessageBox.Show("message","title");
             return;
        }
Beldi Anouar
  • 2,170
  • 1
  • 12
  • 17
  • I think this is only useful if the OP is trying to validate the data while it's being entered. – 41686d6564 stands w. Palestine Dec 15 '16 at 16:24
  • this way is not useful for, cuz i want to validate the cells when i press the button that i have on the form that also contains a datagridview. – Hunar Dec 15 '16 at 16:31
  • Ok honor i have edit my answer with a method who test if a cell is empty – Beldi Anouar Dec 15 '16 at 16:38
  • ok thank you Beldi for your answer, your code i have an error in the line "if (!string.IsNullOrEmpty(cell.Value))", also in line "if (!string.IsNullOrEmpty(Strings.Trim(cell.Value.ToString())))" – Hunar Dec 15 '16 at 16:48
  • Honor i have change the method , try it i will work. – Beldi Anouar Dec 15 '16 at 17:05
  • sorry Beldi, also i have an error An unhandled exception of type 'System.NullReferenceException' occurred. – Hunar Dec 15 '16 at 17:23
  • you must disable the last blank line in DatagridView by adding this code :"dgv_absence.AllowUserToAddRows = false;" – Beldi Anouar Dec 15 '16 at 17:28
  • ok but i need "AllowUserToAddRows" to be true, cuz i enter new rows from datagridview and insert it into database. – Hunar Dec 15 '16 at 17:47
  • don't use `.ToString()` on objects that can be null. Use `string.IsNullOrWhiteSpace(Convert.ToString(cell.Value))` instead – Slai Dec 15 '16 at 18:11
  • Ok you can test if (!row.IsNewRow),i have change my answer try it it will work ! – Beldi Anouar Dec 16 '16 at 08:18
0

This is what I would do.

The DataGrid control on Windows Form can read errors from objects implementing IDataErrorInfo

As MSDN says.

IDataErrorInfo Provides the functionality to offer custom error information that a user interface can bind to.

Your POCO object inside the data source collection for the grid, should implement IDataErrorInfo let's say something like this:

public class MyEntity : IDataErrorInfo
{
    public string this[string columnName]
    {
        get
        {
            // here you can validate each property of your class (POCO object)
            var result = string.Join(Environment.NewLine, Validator.Validate(this, columnName).Select(x => x.ErrorMessage));
            return result;
        }
    }

    public string Error
    {
        get
        {
            // here you can errors related to the whole object (ex: Password, and PasswordConfirmation do not match)
            return string.Join(Environment.NewLine, Validator.Validate(this)
                                                                .Select(x => x.ErrorMessage));
        }
    }

    public Boolean IsValid
    {
        get { return string.IsNullOrEmpty(Error); }
    }
}

Then you could use some validation technique to set up your validation rules.

I like to use DataAnnotation to implement my validation logic.

So, let's say your class has a property (name) that cannot be null you class could be:

public class MyEntity : IDataErrorInfo
{
    [Required]
    public string Name { get; set; }

    public string this[string columnName]
    {
        get
        {
            // here you can validate each property of your class (POCO object)
            var result = string.Join(Environment.NewLine, Validator.Validate(this, columnName).Select(x => x.ErrorMessage));
            return result;
        }
    }

    public string Error
    {
        get
        {
            // here you can validate errors related to the whole object (ex: Password, and PasswordConfirmation do not match)
            return string.Join(Environment.NewLine, Validator.Validate(this)
                                                                .Select(x => x.ErrorMessage)
                                                                .Union(ModelError.Select(m => m.Value)));
        }
    }

    public Boolean IsValid
    {
        get { return string.IsNullOrEmpty(Error); }
    }
}

Then if you use a validator like this

public class Validator : IValidator
{
    public IEnumerable<ErrorInfo> Validate(object instance)
    {
        IEnumerable<ErrorInfo> errores = from property in instance.GetType().GetProperties()
            from error in GetValidationErrors(instance, property)
            select error;
        if (!errores.Any())
        {
            errores = from val in instance.GetAttributes<ValidationAttribute>(true)
                where
                    val.GetValidationResult(null, new ValidationContext(instance, null, null)) !=
                    ValidationResult.Success
                select
                    new ErrorInfo(null,
                        val.GetValidationResult(null, new ValidationContext(instance, null, null)).ErrorMessage,
                        instance);
        }

        return errores;
    }

    public IEnumerable<ErrorInfo> Validate(object instance, string propertyName)
    {
        PropertyInfo property = instance.GetType().GetProperty(propertyName);
        return GetValidationErrors(instance, property);
    }

    private IEnumerable<ErrorInfo> GetValidationErrors(object instance, PropertyInfo property)
    {
        var context = new ValidationContext(instance, null, null);
        context.MemberName = property.Name;
        IEnumerable<ErrorInfo> validators = from attribute in property.GetAttributes<ValidationAttribute>(true)
            where
                attribute.GetValidationResult(property.GetValue(instance, null), context) !=
                ValidationResult.Success
            select new ErrorInfo(
                property.Name,
                attribute.FormatErrorMessage(property.Name),
                instance
                );

        return validators.OfType<ErrorInfo>();
    }
}

The errors will appear on the grid per cell or per row depending on the error.

Notice that you should also implement INotifyPropertyChanged if you are planning to in-line editing your objects.

This approach has the benefit of decoupling the validation logic from the user interface.

Juan M. Elosegui
  • 6,471
  • 5
  • 35
  • 48
  • if you want to decouple you can use Fluent Validation, check my answer here http://stackoverflow.com/a/40997368/819153, in that way you put your validation in a separate project. Can be use anywhere, Windows Form, Xamarin, Asp.Net – Zinov Dec 15 '16 at 16:53
0

I have designed a class for bulk data validation. I optimized it so that validation doesn't take so long.

I wanted to make it as simple as possible, and be able to work with predicate functions.

First file "GridValidator.cs"

public class GridValidator
{
    // We make a list of ToValidate to save all validations
    private readonly List<ToValidate> _Validations;

    // List of Error Messages
    private readonly List<string> _ErrorMessages;

    // Get Error Messages list
    public List<string> GetErrorMessages()
    {
        return _ErrorMessages;
    }

    // IsValid
    public bool IsValid
    {
        get
        {
            return _ErrorMessages.Count == 0;
        }
    }

    public GridValidator()
    {
        _Validations = new List<ToValidate>();
        _ErrorMessages = new List<string>();
    }

    // Set Validation
    public void SetValidation(int colNum, Func<string, bool> predicate, string customErrorMessage = null)
    {
        _Validations.Add(new ToValidate()
        {
            ColNum = colNum,
            FunctionPredicate = predicate,
            CustomErrorMessage = customErrorMessage
        });
    }


    // Validate
    public bool Validate(DataGridView grid)
    {
        _ErrorMessages.Clear();

        // We validate that it has at least one validation
        if (_Validations.Count == 0)
        {
            throw new Exception("No validation defined");
        }

        // We create a list of int to store the numbers of the columns to validate and if it is repeated we do not add it
        List<int> colsToValidate = new List<int>();

        foreach (ToValidate validation in _Validations)
        {
            if (!colsToValidate.Contains(validation.ColNum))
            {
                colsToValidate.Add(validation.ColNum);
            }
        }

        // We go through all the columns to validate
        foreach (int colNum in colsToValidate)
        {
            // We validate that the column exists
            if (colNum >= grid.Columns.Count)
            {
                throw new Exception($"Column {colNum} does not exist.");
            }

            // We traverse all the rows of the column
            for (int rows = 0; rows < grid.Rows.Count; rows++)
            {
                // cell object
                DataGridViewCell cell = grid.Rows[rows].Cells[colNum];
                // cell value
                object cellVal = cell.FormattedValue ?? string.Empty;

                // We loop through all the validations of the column
                foreach (ToValidate validation in _Validations.Where(x => x.ColNum == colNum))
                {
                    // In case the predicate returns false, the error message is added
                    if (!validation.FunctionPredicate(cellVal.ToStringSafe()))
                    {
                        // Let's add the error message to the list
                        if(string.IsNullOrEmpty(validation.CustomErrorMessage))
                            _ErrorMessages.Add($"The value of column {grid.Columns[colNum].HeaderText} in row {rows + 1} is invalid. Value: {cellVal}");
                        else
                            _ErrorMessages.Add($"{validation.CustomErrorMessage}, column {grid.Columns[colNum].HeaderText} in row {rows + 1}. Value: {cellVal}");

                        // We set the error in the cell
                        cell.ErrorText = validation.CustomErrorMessage;
                    }
                }
            }
        }

        return IsValid;
    }
}

public class ToValidate
{
    public int ColNum { get; set; }
    public Func<string, bool> FunctionPredicate { get; set; }
    public string CustomErrorMessage { get; set; }

    public ToValidate()
    {
        CustomErrorMessage = null;
    }
}

public static class GridValidatorExtensionMethods
{
    // Validate Only Numbers
    public static bool ValidateOnlyNumbers(this string value)
    {
        // we remove the letters
        value = value.SelectOnlyNumbers();

        // We validate that the value is not null or empty
        if (string.IsNullOrEmpty(value)) return false;

        return value.All(char.IsDigit);
    }

    public static bool ValidateInt(this string value)
    {
        // we remove the letters
        value = value.SelectOnlyNumbers();

        // We validate that the value is not null or empty
        if (string.IsNullOrEmpty(value)) return false;

        // We validate that it does not have decimals
        if (value.Contains(".")) return false;

        return int.TryParse(value, out int newInt) || newInt >= 0;
    }

    public static bool ValidateDecimal(this string value)
    {
        // remove the letters
        value = value.SelectOnlyNumbers();

        // We validate that the value is not null or empty
        if (string.IsNullOrEmpty(value)) return false;

        return decimal.TryParse(value, out decimal newDecimal) || newDecimal >= 0;
    }

    public static bool ValidateCurrencyAmount(this string value)
    {
        // we remove the letters
        value = value.SelectOnlyNumbers();

        // We validate that the value is not null or empty
        if (string.IsNullOrEmpty(value)) return false;

        return decimal.TryParse(value, out decimal newDecimal) || newDecimal >= 0;
    }

}

Usage example:

            bool valid = true;

            // * Grid validator
            var gridValidator = new GridValidator();

             // * We set the grid validations
             // We validate that column 1 Not Empty
             // Product code
            gridValidator.SetValidation(1, x => !string.IsNullOrEmpty(x), "You must enter a product code.");

            // We validate that column 3 Validate Only Numbers 
            // Document number
            gridValidator.SetValidation(3, x => x.ValidateOnlyNumbers(), "Invalid document number.");
            

            // * We validate the grid in a try catch
            try
            {
                valid = gridValidator.Validate(grid);
            }
            catch (Exception ex)
            {
                _ = MessageBox.Show("Error: " + ex.Message, Application.ProductName, MessageBoxButtons.OK, MessageBoxIcon.Error);
                return false;
            }

            // * If it is not valid we print the errors in the log
            if (!valid)
            {
                // Set max errors to print
                int maxErrorsToPrint = 100;

                txtLog.Text += "There are errors in the grid, check the error messages." + Environment.NewLine;

                // We print the errors in the log with a foreach to the gridValidator
                // We print a maximum of 100 errors, since the textBox control can freeze if we print many records
                int countOfErrorPrint = 0;
                foreach (var item in gridValidator.GetErrorMessages())
                {
                    // We print the errors taking into account the maximum number of errors to print
                    if (countOfErrorPrint >= maxErrorsToPrint)
                        break;

                    txtLog.Text += item + Environment.NewLine;

                    countOfErrorPrint++;
                }

                return false;
            }

Note: We can insert multiple validations from a single column:

gridValidator.SetValidation(1, x => !string.IsNullOrEmpty(x), "You must enter a product code.");

gridValidator.SetValidation(1, x => !x.Contains("hello"), "Does not contain the word hello.");

These validations are applied without the need to go loop the grid again.