Data Validation in Silverlight 4

12,197

Solution 1

One way of deferring validation is to set the property UpdateSourceTrigger=Explicit in the bindings. If you do this, the bindings won't update the source objects, and hence won't cause validation errors, until you explicitly tell the bindings to do so. When your button is clicked, you force an update on the bindings, using a line such as the following for each control:

someTextBox.GetBindingExpression(TextBox.TextProperty).UpdateSource();

You then have your property setters throwing exceptions for invalid data.

This approach can be a bit of a pain if there are quite a lot of controls to force binding updates on.

Also, forcing an update on the bindings has to be done in the code-behind of a control. If you're using a Command with the button as well then you might run in to an issue. Buttons can have both a Command and a Click event handler, and both will execute when the button is clicked on, but I don't know the order in which this happens or even if an order can be guaranteed. A quick experiment suggested that the event handler was executed before the command, but I don't know whether this is undefined behaviour. There is therefore the chance that the command will be fired before the bindings have been updated.


An approach to programmaticaly creating validation tooltips is to bind another property of the textbox and then deliberately cause an error with this binding.

'sapient' posted a complete solution, including code on the Silverlight forums (search for the post dated 07-08-2009 4:56 PM). In short, he/she creates a helper object with a property whose getter throws an exception, binds the Tag property of the textbox to this helper object and then forces an update on the binding.

'sapient's code was written before Silverlight 4 was released. We'll 'upgrade' his/her code to Silverlight 4. The class ControlValidationHelper becomes the following:

public class ControlValidationHelper : IDataErrorInfo
{
    public string Message { get; set; }

    public object ValidationError { get; set; }

    public string Error
    {
        get { throw new NotImplementedException(); }
    }

    public string this[string columnName]
    {
        get { return Message; }
    }
}

It's easy enough to knock up a quick demo application to try this out. I created the following three controls:

    <TextBox x:Name="tbx" Text="{Binding Path=Text, ValidatesOnDataErrors=True, NotifyOnValidationError=True, Mode=TwoWay}" />
    <Button Click="ForceError_Click">Force error</Button>
    <Button Click="ClearError_Click">Clear error</Button>

The Text property and the event handlers for the two buttons live in the code-behind and are as follows:

    public string Text { get; set; }

    private void ForceError_Click(object sender, RoutedEventArgs e)
    {
        var helper = new ControlValidationHelper() { Message = "oh no!" };
        tbx.SetBinding(Control.TagProperty, new Binding("ValidationError")
        {
            Mode = BindingMode.TwoWay,
            NotifyOnValidationError = true,
            ValidatesOnDataErrors = true,
            UpdateSourceTrigger = UpdateSourceTrigger.Explicit,
            Source = helper
        });
        tbx.GetBindingExpression(Control.TagProperty).UpdateSource();
    }

    private void ClearError_Click(object sender, RoutedEventArgs e)
    {
        BindingExpression b = tbx.GetBindingExpression(Control.TagProperty);
        if (b != null)
        {
            ((ControlValidationHelper)b.DataItem).Message = null;
            b.UpdateSource();
        }
    }

The 'Force error' button should make a validation error appear on the textbox, and the 'Clear error' button should make it go away.

One potential downside of this approach occurs if you are using a ValidationSummary. The ValidationSummary will list all validation errors against ValidationError instead of against the name of each property.

Solution 2

Although my answer wasn't regarded as preferable, I'm still sure that the MVVM pattern is the best choice to perform validation.

In my code you should use the model validator from this post about validation and any mvvm framework, for example MVVM Light.

It is much easier to add validation rules using the view model and model validator classes:

public class PersonViewModel : ViewModelBase, INotifyDataErrorInfo
{
    private ModelValidator _validator = new ModelValidator();

    public PersonViewModel()
    {
        this._validator.AddValidationFor(() => this.Age)
            .Must(() => this.Age > 0)
            .Show("Age must be greater than zero");
    }
}

And you can validate the model if and only if a user explicitly clicks a button:

    #region INotifyDataErrorInfo

    public IEnumerable GetErrors(string propertyName)
    {
        return this._validator.GetErrors(propertyName);
    }

    public bool HasErrors
    {
        get { return this._validator.ErrorMessages.Count > 0; }
    }

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged = delegate { };

    protected void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
        this.RaisePropertyChanged("HasErrors");
    }
    #endregion

    public bool Validate()
    {
        var result = this._validator.ValidateAll();
        this._validator.PropertyNames.ForEach(OnErrorsChanged);
        return result;
    }

As everyone can see, there is nothing difficult here, just 20-30 lines of code.

Moreover, the MVVM approach is much more flexible and you can reuse some common validation scenaries among several view models.

Share:
12,197
SOF User
Author by

SOF User

Updated on June 04, 2022

Comments

  • SOF User
    SOF User almost 2 years

    I have control in SL4. I want data validation on button click. Big problem is normally SL4 give validation using binding property.

    like example given shown in this example

    http://weblogs.asp.net/dwahlin/archive/2010/08/15/validating-data-in-silverlight-4-applications-idataerrorinfo.aspx

    <TextBox Text="{Binding Name,Mode=TwoWay,ValidatesOnDataErrors=true}" 
        Height="23" 
        Width="120"
        HorizontalAlignment="Left" 
        VerticalAlignment="Top"    />
    

    BUT I WANT TO SHOW ERROR MESSAGE LIKE THIS ....

    enter image description here

    using my own code like on button click i check (textbox1.text == null ) then set this style of error to textbox1