Consider a small winforms application, that has a list of custom objects bound to a DataGridView.
The data class:
public class MyAwesomeClass : IDataErrorInfo, INotifyPropertyChanged{
string _Name;
public string Name { get { return _Name; } set { if(Name is null || !Name.Equals(value)) { _Name = value; Changed(nameof(Name)); } } }
int _MyVal;
[CustomValidation(typeof(MyAwesomeValidator), nameof(MyAwesomeValidator.ValidateIntVal))]
public int MyVal { get { return _MyVal; } set { if(!MyVal.Equals(value)) { _MyVal = value; Changed(nameof(MyVal)); } } }
private void Changed(string property) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property)); }
//Nicked from: https://reza-aghaei.com/dataannotations-validation-attributes-in-windows-forms/
[Browsable(false)]
public string this[string property]
{
get
{
var propertyDescriptor = TypeDescriptor.GetProperties(this)[property];
if (propertyDescriptor == null)
return string.Empty;
var results = new List();
var result = Validator.TryValidateProperty(
propertyDescriptor.GetValue(this),
new ValidationContext(this, null, null)
{ MemberName = property },
results);
if (!result)
return results.First().ErrorMessage;
return string.Empty;
}
}
[Browsable(false)]
public string Error
{
get
{
var results = new List();
var result = Validator.TryValidateObject(this,
new ValidationContext(this, null, null), results, true);
if (!result)
return string.Join(Environment.NewLine, results.Select(x => x.ErrorMessage));
else
return null;
}
}
public static IEnumerable DummyData {get{return new []{
new MyAwesomeClass("a",-2),
new MyAwesomeClass("b",3),
new MyAwesomeClass("c",8)
};}}
}
The custom ValidationResult:
public class CustomValidationResult : ValidationResult {
public enum ResultState { Warning, Error}
public ResultState State {get; private set;}
public CustomValidationResult(string errorMessage, ResultState state):base(errorMessage) {
ResultState = state;
}
public static ValidationResult Success {get{return ValidationResult.Success;}}
}
The validator:
public class MyAwesomeValidator{
public static ValidationResult ValidateIntVal(int value, ValidationContext Context) {
if(value < 0) return new CustomValidationResult("The value cannot be smaller than 0", CustomValidationResult.ResultState.Error);
if(value<5) return new CustomValidationResult("The value is a bit small, isn't it?", CustomValidationResult.ResultState.Warning);
return CustomValidationResult.Success;
}
}
A class responsible for styling cells with errors:
class DataGridViewCellFormatter
{
DataGridView Dgv;
public DataGridViewCellFormatter(DataGridView dgv)
{
Dgv = dgv;
Dgv.CellFormatting += Dgv_CellFormatting;
}
private void Dgv_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
DataGridViewCell cell = Dgv.Rows[e.RowIndex].Cells[e.ColumnIndex];
if (!string.IsNullOrWhiteSpace(cell.ErrorText))
{
cell.Style.BackColor = Color.Red;
}
else
{
cell.Style.BackColor = cell.OwningColumn.DefaultCellStyle.BackColor;
}
}
}
The form (Excerpt):
public partial class frm_DemoForm:Form{
BindingList Data;
DataGridViewCellFormatter coloring;
public frm_DemoForm(){
Data = new BindingList(MyAwesomeClass.DummyData.ToList());
InitializeComponent();
coloring = new DataGridViewCellFormatter(dataGridView1);
dataGridView1.DataSource = Data;
}
}
Now, if I wrote everything correctly, the DataGridView should display 3 rows (a, b and c) and show errors on the first two (the first value is below zero and thus an error and the second is in the "still a bit small"-range and thus a warning). The DataGridViewCellFormatter will check each cell's ErrorText and color the cell accordingly.
BUT.
This form of validation allows only a binary formatting: Error or no error. If I want to colour the cell in orange if it is only a warning, I need to prefix or suffix the error text with "[Warning]" and "[Error]" (That i can probably skip and take everything that has no prefix as an error) to be able to catch the differences in severity.
BUT.
Since I already have such a nice CustomValidationResult that provides the granualrity I want via the State property, I was wondering if there is a way to elegantly hook the DataGridView up to the actual ValidationResult object(s) and act on them rather than on the ErrorText of each cell which I don't want to spam with a "[Warning]" token.