[]
        
(Showing Draft Content)

Validation

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.

  1. 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;
    }
  2. 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>

ProductBase

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;

ProductNotifyDataErrorInfo

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; }
    }

ProductNotifyDataErrorInfo.cs

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; }
    }