[]
Validation lets you control and define the type of data that is acceptable in a particular column cell, such as text, numbers, and dates. It allows you to limit the range of acceptable values or restrict the entries to a specific format. There are various ways in which data can be validated such as, restricting invalid input keys, showing the error or warning information or revert the value to original on getting an invalid input from the user. FlexGrid supports data validation when bound to a datatable and allows you to style the validation rules to provide visual cues and enhance the user experience by making the validation rules noticeable and user-friendly. It provides the following properties to control how the errors are displayed:
ShowErrors: By default, this property is set to true, and it avoids showing any error when set to false.
ErrorIconTemplate: It changes the icon displayed in the row-headers of the rows having errors.
ErrorStyle, RowHeaderErrorStyle, RowErrorStyle: These properties allow changing the appearance of the cells that either have errors directly or belong to a row that has errors. In addition, these styles are applied on top of the current style of the cell.
The following image showcases validation errors in FlexGrid.
Let us have a look at an example that showcases how validation errors are displayed in FlexGrid and how they appear on setting different error styles. Validation in FlexGrid honors standard IDataErrorInfo and INotifyDataErrorInfo interfaces implemented in the data-items. In this example, we have used ProductBase class, which contains data and ProductNotifyDataErrorInfo class, which implements INotifyDataErrorInfo and contains all the validation error conditions.
Use the following code to create a list that accesses ProductNotifyDataErrorInfo class to display validation errors in the FlexGrid control.
public MainWindow()
{
InitializeComponent();
var list = new List<ProductNotifyDataErrorInfo>();
for (int i = 0; i < 20; i++)
{
list.Add(new ProductNotifyDataErrorInfo());
}
list[0].Price = -10; // to check error on property level
grid.ItemsSource = list;
}
private void ShowErrorCheckedChanged(object sender, RoutedEventArgs e)
{
grid.ShowErrors = (sender as CheckBox).IsChecked.Value;
}
Apply styles to the cells and rows showing errors using the following code. In this example, we have used ErrorStyle, RowErrorStyle and RowHeaderErrorStyle properties to apply different styles to the cells and rows showing errors.
<CheckBox Content="Show Erors" Checked="ShowErrorCheckedChanged" Unchecked="ShowErrorCheckedChanged" />
<c1:FlexGrid x:Name="grid" Grid.Row="1" HeadersVisibility="All" ShowErrors="False">
<c1:FlexGrid.Columns>
<c1:GridColumn Binding="Line"/>
<c1:GridColumn Binding="Name"/>
<c1:GridColumn Binding="Price"/>
<c1:GridColumn Binding="Cost"/>
<c1:GridColumn Binding="Color"/>
</c1:FlexGrid.Columns>
<c1:FlexGrid.RowErrorStyle>
<Style TargetType="ContentControl">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="#FFC0CB"/>
</Style>
</c1:FlexGrid.RowErrorStyle>
<c1:FlexGrid.ErrorStyle>
<Style TargetType="ContentControl">
<Setter Property="Foreground" Value="White"/>
<Setter Property="Background" Value="Red"/>
</Style>
</c1:FlexGrid.ErrorStyle>
<c1:FlexGrid.RowHeaderErrorStyle>
<Style TargetType="ContentControl">
<Setter Property="Foreground" Value="Red"/>
</Style>
</c1:FlexGrid.RowHeaderErrorStyle>
</c1:FlexGrid>
public class ProductBase :
INotifyPropertyChanged
{
static string[] _lines = "Computers|Washers|Stoves".Split('|');
static string[] _colors = "Red|Green|Blue|White".Split('|');
static Random _rnd = new Random();
// ctor
public ProductBase()
{
Line = _lines[_rnd.Next() % _lines.Length];
Color = _colors[_rnd.Next() % _colors.Length];
Name = string.Format("{0} {1}{2}", Line.Substring(0, Line.Length - 1), Line[0], _rnd.Next(1, 1000));
Price = _rnd.Next(1, 1000);
Cost = _rnd.Next(1, 600);
}
// object model
[Display(Name = "Line")]
public virtual string Line
{
get { return (string)GetValue("Line"); }
set { SetValue("Line", value); }
}
[Display(Name = "Color")]
public virtual string Color
{
get { return (string)GetValue("Color"); }
set { SetValue("Color", value); }
}
[Display(Name = "Name")]
public virtual string Name
{
get { return (string)GetValue("Name"); }
set { SetValue("Name", value); }
}
[Display(Name = "Price")]
public virtual double? Price
{
get { return (double?)GetValue("Price"); }
set { SetValue("Price", value); }
}
[Display(Name = "Cost")]
public virtual double? Cost
{
get { return (double?)GetValue("Cost"); }
set { SetValue("Cost", value); }
}
// get/set values
Dictionary<string, object> _values = new Dictionary<string, object>();
protected virtual object GetValue(string p)
{
object value;
_values.TryGetValue(p, out value);
return value;
}
protected virtual void SetValue(string p, object value)
{
if (!object.Equals(value, GetValue(p)))
{
_values[p] = value;
OnPropertyChanged(p);
}
}
protected virtual void OnPropertyChanged(string p)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(p));
}
// factory
public static string[] GetLines()
{
return _lines;
}
public static string[] GetColors()
{
return _colors;
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
public class ProductNotifyDataErrorInfo :
ProductBase,
INotifyDataErrorInfo
{
// validate before setting the value
protected override void SetValue(string p, object value)
{
// set property value
base.SetValue(p, value);
// validate
var valid = IsValid(p, value);
// item-level validation
if (Price <= Cost)
{
AddError(string.Empty, "Price must be > Cost");
}
else
{
RemoveError(string.Empty);
}
}
bool IsValid(string propName, object value)
{
// assume all is OK
bool valid = true;
RemoveError(propName);
// property-level validation
switch (propName)
{
case "Price":
{
var p = value as double?;
if (p <= 0)
{
AddError(propName, "Price must be > 0");
valid = false;
}
}
break;
case "Cost":
{
var c = value as double?;
if (c <= 0)
{
AddError(propName, "Cost must be > 0");
valid = false;
}
}
break;
}
return valid;
}
// error dictionary
Dictionary<String, List<String>> _errors = new Dictionary<string, List<string>>();
void AddError(string propName, string error)
{
if (!_errors.ContainsKey(propName))
{
_errors[propName] = new List<string>();
}
if (!_errors[propName].Contains(error))
{
_errors[propName].Insert(0, error);
RaiseErrorsChanged(propName);
}
}
void RemoveError(string propName)
{
if (_errors.ContainsKey(propName))
{
_errors.Remove(propName);
RaiseErrorsChanged(propName);
}
}
void RaiseErrorsChanged(string propName)
{
if (ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propName));
}
}
#region ** INotifyDataErrorInfo
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable INotifyDataErrorInfo.GetErrors(string propName)
{
if (propName == null)
{
propName = string.Empty;
}
if (!_errors.ContainsKey(propName))
{
return null;
}
return _errors[propName];
}
bool INotifyDataErrorInfo.HasErrors
{
get { return _errors.Count > 0; }
}
public class ProductNotifyDataErrorInfo :
ProductBase,
INotifyDataErrorInfo
{
// validate before setting the value
protected override void SetValue(string p, object value)
{
// set property value
base.SetValue(p, value);
// validate
var valid = IsValid(p, value);
// item-level validation
if (Price <= Cost)
{
AddError(string.Empty, "Price must be > Cost");
}
else
{
RemoveError(string.Empty);
}
}
bool IsValid(string propName, object value)
{
// assume all is OK
bool valid = true;
RemoveError(propName);
// property-level validation
switch (propName)
{
case "Price":
{
var p = value as double?;
if (p <= 0)
{
AddError(propName, "Price must be > 0");
valid = false;
}
}
break;
case "Cost":
{
var c = value as double?;
if (c <= 0)
{
AddError(propName, "Cost must be > 0");
valid = false;
}
}
break;
}
return valid;
}
// error dictionary
Dictionary<String, List<String>> _errors = new Dictionary<string, List<string>>();
void AddError(string propName, string error)
{
if (!_errors.ContainsKey(propName))
{
_errors[propName] = new List<string>();
}
if (!_errors[propName].Contains(error))
{
_errors[propName].Insert(0, error);
RaiseErrorsChanged(propName);
}
}
void RemoveError(string propName)
{
if (_errors.ContainsKey(propName))
{
_errors.Remove(propName);
RaiseErrorsChanged(propName);
}
}
void RaiseErrorsChanged(string propName)
{
if (ErrorsChanged != null)
{
ErrorsChanged(this, new DataErrorsChangedEventArgs(propName));
}
}
#region ** INotifyDataErrorInfo
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
IEnumerable INotifyDataErrorInfo.GetErrors(string propName)
{
if (propName == null)
{
propName = string.Empty;
}
if (!_errors.ContainsKey(propName))
{
return null;
}
return _errors[propName];
}
bool INotifyDataErrorInfo.HasErrors
{
get { return _errors.Count > 0; }
}