Chapter 23. Input Validation


In This Chapter

Property setter validation

Understanding binding errors

Customizing control visual states in Expression Blend

A reusable custom validation component

Creating a custom ValidationSummary control

Asynchronous and composite validation

Setting control enabled states based on the presence of validation errors

Hiding validation errors until the user has had a chance to fill out the form

Validating a TextBox as the user types


Input validation is the process of validating input to an application before it is used. Most line-of-business applications rely on input validation to ensure that user input is correctly formatted, meaningful, safe to use, and that it properly conveys the intent of the user.

Input validation in a Windows Phone app may be purely client-side, or it can involve sending input to a web service for validation, which normally entails asynchronous processing.

Silverlight for Windows Phone comes equipped to support input validation. There are, however, some crucial validation related control styles missing from the SDK, which need to be put in place before visual indicators are displayed for validation errors.

This chapter begins by looking at the two types of input validation: syntactic and semantic, and then at the Silverlight data binding system as it relates to input validation, its features, and limitations.

The chapter then provides a walk-through for adding custom control styles for enabling visualization of data validation errors and shows how to create a custom error validation summary control.

Finally, the chapter explores an alternative approach to validation, one that leverages the validation system that is new to Silverlight 4 and allows for asynchronous and composite validation.

Defining Input Validation

Alexander Jung does a great job on his blog (http://ajdotnet.wordpress.com/2010/02/28/understanding-validation-in-silverlight/) of defining input validation. He states that input validation involves ensuring that user input passes two types of validation: syntactic and semantic.

Syntactic Validation

Syntactic validation ensures that data entered by a user can be converted to the type required by the application. For example, when a user enters a date string into a TextBox, the input fails syntactic validation if it cannot be converted to a date.

In most cases, the Silverlight data binding system attends to syntactic validation, coercing values to and from source property types.


Note

The Silverlight data binding system performs syntactic validation before assigning a value to a property.


Semantic Validation

Unlike syntactic validation, semantic validation can occur after a value has been assigned. Semantic validation deals with more complex validation and usually encompasses the evaluation of business rules.

The following examples require semantic validation:

• Required fields.

• String length restrictions, for example, the length of a string input must not exceed 140 characters.

• Numeric range restrictions, for example, value must be greater than 0 but less than 100.

• Date range restrictions, for example, a date must occur in the future.

• Mutually dependent fields, for example, value A is required if value B has been provided.

• Distinguishing between absent and invalid fields. The application must be able to distinguish between when a user has chosen not to enter a field, from when a field has been provided by the user, but it is an empty string for example.

Input Validation Using Property Setters

The first validation system discussed in this chapter is the property setter validation system. This system has been with the phone since the first 7.0 release of the Windows Phone SDK and is part of Silverlight 3.

The Silverlight data binding system provides validation support when assigning a value to the source of a data binding expression. For example, when a FrameworkElement (the binding target) has a binding to a property in a viewmodel (the binding source) and the value in the FrameworkElement changes, validation occurs as the value is being assigned to the property in the viewmodel.

To enable property setter validation for an input control, set the binding’s NotifyOnValidationError and ValidatesOnExceptions to true, as shown in the following example:

<TextBox Text="{Binding PropertyName, Mode=TwoWay,
           NotifyOnValidationError=True, ValidatesOnExceptions=True}" />

ValidatesOnExceptions enables property validation for the binding, and NotifyOnValidationError causes an event to be raised when a validation error occurs.

The Silverlight data binding system performs the following three actions during assignment of a source object property:

• Conversion of the target DependencyProperty value using an IValueConverter. This occurs if the target’s binding has its ValueConverter set.

• Type conversion of the result of step 1 to the source property type.

• Assignment of the result of step 2 to the source property, and detection and handling of exceptions raised in the source’s property set accessor.

The validation and assignment of a source property is explored in greater detail in Figure 23.1, which is based on Alexander Jung’s diagram at http://ajdotnet.wordpress.com/2010/02/28/understanding-validation-in-silverlight/.

Image

Figure 23.1. A change in a target property causes validation and assignment of its source property.

The following list refers to numbered items in Figure 23.1, and describes each step performed by the data binding system:

1. The change event is raised for the target’s DependencyProperty. This event is subscribed to by the target’s BindingExpression for the DependencyProperty. When the FrameworkElement’s property is changed, the BindingExpression’s TargetPropertyChanged method is called. The BindingExpression then calls its own UpdateValue method.

2. If there is a ValueConverter defined for the binding, then its ConvertBack method is called. If an exception is raised in the ConvertBack method, it is not handled and must be handled by the Application.UnhandledException event handler, or the app exits.

3. An internal IValueConverter, called DynamicValueConverter, uses a TypeConverter to convert the target value to the source type. The DynamicValueConverter comes into play when, for example, a TextBox.Text string value needs to be converted to an int value. During conversion, noncritical Exceptions are treated as validation errors. Critical Exceptions are described in a later section.

4. The property setter is called using the result from the TypeConverter. Once again, if a noncritical exception is raised, it is treated as a validation error. Be mindful that if the property setter raises the PropertyChanged event and an event handler throws an Exception, it results in a validation error.

5. The BindingExpression class uses the static Validation class to assign validation errors for the FrameworkElement.

6. The Validation class records validation information about a FrameworkElement using the DependencyProperty’s Validation.ErrorsProperty and Validation.HasErrorProperty. Once the error has been set, the FrameworkElement is prompted to display the error. This is normally done by transitioning the VisualState of the control to an invalid state. We look at displaying validation errors, using VisualState and the VisualStateManager, later in this chapter.

7. If the binding has been set to notify of validation errors, discussed further in the following section, the BindingValidationError event is raised.


Note

IValueConverters are unable to participate in input validation because if an IValueConverter throws an exception, it is re-thrown by the BindingExpression.


Validation Class

The static Validation class is used to determine whether a control has validation errors and to retrieve the errors if they exist. The Validation class has the methods: GetHasError and GetErrors, and provides two dependency properties for storing the validation information: ErrorsProperty and HasErrorsProperty.

Critical Exceptions

Most exceptions that occur during the assignment of a value to a source property are handled by the BindingExpression class. Several exception types, however, are deemed critical exceptions and are re-thrown by the BindingExpression. The following is the list of critical exceptions:

OutOfMemoryException

StackOverflowException

AccessViolationException

ThreadAbortException

Binding Errors

Binding errors occur when a noncritical exception is raised after the ConvertBack method of the ValueConverter is called. The following is a list of the three types of binding errors that can occur when modifying the value of a target property:

• The user enters a value that fails to be converted to the source binding type (syntactic validation).

• An exception is raised within the property set accessor.

• The property does not have a set accessor and is, therefore, read-only.


Note

When a binding error occurs, the target, a TextBox for example, retains the entered value, but the source property is not updated.


By default, the Silverlight data binding infrastructure fails silently when a binding error occurs. The NotifyOnValidationError and the ValidatesOnExceptions binding properties allow you to provide the user with feedback when a value is incorrectly set.

NotifyOnValidationError Binding Property

With the Binding.NotifyOnValidationError property set to true, Silverlight’s data binding system raises a BindingValidationError event when a binding error occurs. The following example demonstrates enabling validation on a TextBox element:

<TextBox Text="{Binding ValidatedString2, Mode=TwoWay,
        NotifyOnValidationError=True, ValidatesOnExceptions=True}"
        Style="{StaticResource ValidatingTextBoxStyle}" />

The BindingValidationError is a RoutedEvent, allowing it to be handled by an ancestor element in the visual tree. RoutedEvents bubble upwards through the visual tree until they are either handled, indicated by the Handled property of the event arguments, or they reach the root element of the visual tree. Subscription to the BindingValidationError event at the page level, therefore, provides the opportunity to handle validation errors for all elements in the page. We leverage this fact later in the chapter to create a validation summary control.

ValidatesOnExceptions Property

The Binding class’s NotifyOnValidationError property works in unison with its ValidatesOnExceptions property. If the ValidatesOnExceptions property is not explicitly set to true and a binding error results from a noncritical exception being raised from a property accessor, the error is silently ignored. Furthermore, the VisualState of the FrameworkElement is not changed, and the BindingValidationError event is not raised.

Defining Validation Visual States in Silverlight for Windows Phone

When a binding error occurs, the data binding system transitions the VisualState of the FrameworkElement to an invalid state. In Silverlight for the browser, various controls come ready to display validation errors. Unfortunately, those styles have not been incorporated into Silverlight for Windows Phone because the styles are not immediately transferable to the phone because of the reduced size of the phone display. Consequently, if you want to harness the existing validation infrastructure, you must replace the template for each control for which you intend to display validation errors.

Replacing a control’s template can be done using Expression Blend. A copy can be made of the template, and error related VisualStates can then be modified.


Note

If you do not have Microsoft Expression Blend already installed, it can be downloaded from http://www.microsoft.com/expression/windowsphone/.


To make a copy of a control’s template, right-click on a control within Expression Blend, select Edit Template, and then select Edit a Copy (see Figure 23.2).

Image

Figure 23.2. Editing a copy of a control’s template from within Expression Blend

To allow the template to be reused across the entire app from any page, define the style at the application level (see Figure 23.3). This causes a style containing the template to be placed in the project’s app.xaml file.

Image

Figure 23.3. Defining the location of a new style in Expression Blend

Once the style has been created, a new Style element is placed in the App.xaml file. See the following excerpt:

<Style x:Key="ValidatingTextBoxStyle" TargetType="TextBox">
    ...
</Style>

The States tab in Expression Blend allows you to define different ways of displaying the control depending on its visual state. The control is placed into a named state by the VisualStateManager. To customize the way the control is displayed when a validation error occurs, select the InvalidUnfocused state (see Figure 23.4).

Image

Figure 23.4. Converting the BorderBrush to a local value

To modify the TextBox border so that it is displayed in a different manner when in the InvalidUnfocused state, select the yellow button next to the BorderBrush item in the Properties tab and choose Convert to Local Value.

Converting the BorderBrush to a local value allows the brush to be customized for the particular VisualState, and for its color to be set to one that better represents the state of the control when a validation error occurs.

Just as the TextBox style was made globally available by placing it in the app’s resources, converting the new color to a resource enables its use in other styles (see Figure 23.5).

Image

Figure 23.5. Creating a new color resource for the BorderBrush color

When the color resource has been created, the template in the style resource contains a definition for the InvalidUnfocused state, with a reference to the InvalidElementColor resource, as shown in the following excerpt:

<VisualState x:Name="InvalidUnfocused">
    <Storyboard>
        <ColorAnimation Duration="0"
            To="{StaticResource InvalidElementColor}"
            Storyboard.TargetProperty
                ="(Border.BorderBrush).(SolidColorBrush.Color)"
            Storyboard.TargetName="EnabledBorder"
            d:IsOptimized="True"/>
    </Storyboard>
</VisualState>

Subsequently, whenever you want to display validation errors for a TextBox, the Style property of the TextBox is set to ValidatingTextBoxStyle, like so:

<TextBox Text="{Binding ValidatedString1, Mode=TwoWay,
        NotifyOnValidationError=True}"
        Style="{StaticResource ValidatingTextBoxStyle}" />

If a binding error occurs, the VisualState of the TextBox automatically transitions to the InvalidUnfocused state.

The following is an excerpt from the ConventionalValidationViewModel class in the downloadable sample code, which contains validation logic for the ValidatedString1 property:

public string ValidatedString1
{
    get
    {
        return validatedString1;
    }
    set
    {
        if (string.IsNullOrEmpty(value))
        {
            throw new ArgumentException("Validated String 1 is required.");
        }
        validatedString1 = value;
    }
}

When a user removes all the text from the TextBox that is bound to this property, the property set accessor raises an ArgumentException. The exception is handled by the data binding system, and the TextBox is placed into the InvalidUnfocused state, which now provides visual indication that the value is invalid (see Figure 23.6).

Image

Figure 23.6. ConventionalValidationView page. A TextBox with a validation error is displayed with the custom BorderBrush.

This approach can be used to validate a variety of semantic rules. For example, data can be made to conform to a valid range. If the data falls outside a specified range, an exception can be thrown.

Custom validation logic can be applied to other control types as well. In some cases, however, it is necessary to provide a style for the InvalidFocused state as well as the InvalidUnfocused state. The InvalidUnfocused state is used in this example because, by default, the TextBox.Text property is set only when the TextBox loses focus.

Validating a TextBox as the User Types

The BindingExpression class’s UpdateSource method forces the target property value to be assigned to its source property. This allows you to perform input validation while the user is entering text into a TextBox, rather than after the TextBox loses focus.

I have created a class called UpdateSourceTriggerExtender, which contains an attached property that is used to trigger an update of the binding source whenever the TextBox’s text changes.

When the attached property is placed on a TextBox, the UpdateSourceTriggerExtender class’s HandleUpdatePropertyChanged method is called, which subscribes to the TextBox.TextChanged event (see Listing 23.1). When the text is changed the BindingExpression for the TextProperty is retrieved, and its UpdateSource method is called, which effectively pushes the Text string value to the source property.

The attached property can also be placed on a PasswordBox, in which case its PasswordChanged event is used to monitor for user input.


Note

The TextBox.LostFocus event is used to explicitly set the visual state of the TextBox. This is required because the Silverlight data binding system does not place the TextBox into the correct visual state when it loses focus if the Text property has not changed, which is the case when you call UpdateSource on the TextChanged event.


Listing 23.1. UpdateSourceTriggerExtender Class


public class UpdateSourceTriggerExtender
{
    public static readonly DependencyProperty UpdateSourceOnTextChanged
        = DependencyProperty.RegisterAttached(
            "UpdateSourceOnTextChanged", typeof(bool),
            typeof(UpdateSourceTriggerExtender),
            new PropertyMetadata(HandleUpdatePropertyChanged));

    public static bool GetUpdateSourceOnTextChanged(DependencyObject d)
    {
        return (bool)d.GetValue(UpdateSourceOnTextChanged);
    }

    public static void SetUpdateSourceOnTextChanged(
        DependencyObject d, bool value)
    {
        d.SetValue(UpdateSourceOnTextChanged, value);
    }

    static void HandleUpdatePropertyChanged(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        TextBox textBox = d as TextBox;
        if (textBox != null)
        {
            if ((bool)e.OldValue)
            {
                textBox.TextChanged -= HandleTextBoxTextChanged;
                textBox.LostFocus -= HandleBoxLostFocus;
            }

            if ((bool)e.NewValue)
            {
                textBox.TextChanged += HandleTextBoxTextChanged;
                textBox.LostFocus += HandleBoxLostFocus;
            }
            return;
        }

        PasswordBox passwordBox = d as PasswordBox;
        if (passwordBox == null)
        {
            throw new Exception("UpdateSourceTrigger can only be used "
                                    + "on a TextBox or PasswordBox.");
        }

        /* Wire up for password box. */
        if ((bool)e.OldValue)
        {
            passwordBox.PasswordChanged -= HandlePasswordBoxTextChanged;
            passwordBox.LostFocus -= HandleBoxLostFocus;
        }

        if ((bool)e.NewValue)
        {
            passwordBox.PasswordChanged += HandlePasswordBoxTextChanged;
            passwordBox.LostFocus += HandleBoxLostFocus;
        }
    }
    static void HandlePasswordBoxTextChanged(object sender, RoutedEventArgs e)
    {
        UpdateSource((PasswordBox)sender, PasswordBox.PasswordProperty);
    }

    static void HandleTextBoxTextChanged(object sender, TextChangedEventArgs e)
    {
        UpdateSource((TextBox)sender, TextBox.TextProperty);
    }

    static void UpdateSource(
        FrameworkElement element, DependencyProperty property)
    {
        if (element == null)
        {
            return;
        }

        BindingExpression bindingExpression
                = element.GetBindingExpression(property);
        if (bindingExpression != null)
        {
            bindingExpression.UpdateSource();
        }
    }

    static void HandleBoxLostFocus(object sender, RoutedEventArgs e)
    {
        /* This method prevents the control from being placed
         * into the valid state when it loses focus. */
        var control = sender as Control;
        if (control == null)
        {
            return;
        }
        bool hasError = Validation.GetHasError(control);
        if (hasError)
        {
            VisualStateManager.GoToState(control, "InvalidFocused", false);
        }
    }
}


The following example demonstrates how the UpdateSourceTriggerExtender can be applied to a TextBox:

<TextBox Text="{Binding ValidatedString1, Mode=TwoWay,
                            UpdateSourceTrigger=Explicit,
                            NotifyOnValidationError=True,
                            ValidatesOnExceptions=True}"
            Style="{StaticResource ValidatingTextBoxStyle}"
            u:UpdateSourceTriggerExtender.UpdateSourceOnTextChanged="True" />

The UpdateSourceTrigger binding property determines when the source property is updated. It can be set to either Default or Explicit. Default causes the source property to be updated when the TextBox loses focus, and Explicit prevents the source being updated until the binding’s UpdateSource method is explicitly called.

As soon as the text changes, the source property named ValidatedString1 is updated, which causes the input to be validated.

Performing Group Validation

Validating a series of input fields usually entails validating each field as the user leaves the field and then checking for completeness and validity of all fields when the Submit button is tapped (see Figure 23.7).

Image

Figure 23.7. Form validation activity diagram

The UpdateSource method of the BindingExpression class can be used to validate all controls on a form. As you saw in the preceding section, the UpdateSource method causes the target value to be reassigned to its source property. In other words, the value in the control is pushed to its data context, allowing the discovery of any input validation errors.

The following excerpt from ConventionalValidationView.xaml.cs in the downloadable sample code demonstrates how to validate all TextBox fields on a form:

void Button_Click(object sender, System.Windows.RoutedEventArgs e)
{
    IEnumerable<TextBox> children = ContentPanel.GetDescendents<TextBox>();
    foreach (TextBox textBox in children)
    {
        BindingExpression bindingExpression
            = textBox.GetBindingExpression(TextBox.TextProperty);
        if (bindingExpression != null)
        {
            bindingExpression.UpdateSource();
        }
    }
}

The custom GetDescendents extension method recursively retrieves all children, children’s children, and so on, of a particular type for the specified FrameworkElement. The method is located in the VisualTree class in the downloadable sample code and is shown in the following excerpt:

public static IEnumerable<TChild> GetDescendents<TChild>(
    this FrameworkElement parent) where TChild : class
{
    ArgumentValidator.AssertNotNull(parent, "parent");

    int childCount = VisualTreeHelper.GetChildrenCount(parent);
    for (int i = 0; i < childCount; i++)
    {
        DependencyObject child = VisualTreeHelper.GetChild(parent, i);
        TChild candidate = child as TChild;
        if (candidate != null)
        {
            yield return candidate;
        }

        FrameworkElement element = child as FrameworkElement;
        if (element != null)
        {
            /* Could be improved with tail recursion. */
            IEnumerable<TChild> descendents
                        = element.GetDescendents<TChild>();
            foreach (TChild descendent in descendents)
            {
                yield return descendent;
            }
        }
    }
}

When the Submit button of the ConventionalValidationView page is tapped, all TextBoxes are retrieved and the UpdateSource method is called for each control’s Text property binding. If any input validation errors are present, they are displayed in the UI (see Figure 23.8).

Image

Figure 23.8. ConventionalValidationView page. Tapping the Submit button causes all fields to be validated.

Displaying Error Details

Highlighting an invalid field indicates that the field needs attention, but it does not tell the user why. Feedback should be provided describing the nature of the error and how it can be corrected. In Silverlight for the browser, this is achieved using the ValidationSummary control. Unfortunately, no such control exists in the Windows Phone FCL. But that does not stop us from making our own!

A Custom ValidationSummary Control

Included in the downloadable sample code is a simple control for displaying all errors that occur on a page. It is named the same as its browser counterpart: ValidationSummary. When placed on a page, it retrieves the page and subscribes the page’s BindingValidationError event during its Loaded event. When an unhandled validation error occurs for any control on the page, the error is placed in an ObservableCollection and displayed in the view (see Listing 23.2).

Listing 23.2. ValidationSummary Class (excerpt)


public partial class ValidationSummary : UserControl
{
    readonly ObservableCollection<object> errors
                    = new ObservableCollection<object>();

    readonly Dictionary<string, List<object>> errorLookup
                            = new Dictionary<string, List<object>>();
    const int lineHeight = 30;

    public IEnumerable<object> Errors
    {
        get
        {
            return errors;
        }
    }

    public ValidationSummary()
    {
        InitializeComponent();
        Loaded += HandleLoaded;
    }

    bool loaded;

    void HandleLoaded(object sender, System.Windows.RoutedEventArgs e)
    {
        if (loaded)
        {
            return;
        }
        loaded = true;
        Page page = this.GetVisualAncestors<Page>().FirstOrDefault();

        if (page == null)
        {
            throw new InvalidOperationException(
                            "Unable to locate parent page.");
        }
        page.BindingValidationError += HandleBindingValidationError;
        ...
    }

    /// <summary>
    /// Performs property setter validation.
    /// </summary>
    void HandleBindingValidationError(
            object sender, ValidationErrorEventArgs e)
    {
        string content = e.Error.ErrorContent.ToString();

        if (e.Action == ValidationErrorEventAction.Added)
        {
            AddError(content);
        }
        else
        {
            RemoveError(content);
        }
    }

    void AddError(object error)
    {
        string content = error.ToString();

        if (!errors.Contains(content))
        {
            errors.Add(content);
        }
        MinHeight += lineHeight;
    }

    void RemoveError(object error)
    {
        string content = error.ToString();
        errors.Remove(content);
        MinHeight -= lineHeight;
    }
...
}


The ValidationSummary control’s XAML file contains a ListBox, whose ItemsSource has a data binding to the Errors property (see Listing 23.3).

Listing 23.3. ValidationSummary Control XAML


<UserControl x:Class="DanielVaughan.WindowsPhone7Unleashed.ValidationSummary"
             x:Name="control"
...
>
    <StackPanel x:Name="LayoutRoot"
            Background="{StaticResource PhoneBackgroundBrush}">
        <ListBox ItemsSource
                 ="{Binding ElementName=control, Path=Errors}">
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding}"
                        Style="{StaticResource PhoneTextSmallStyle}"
                        Height="30" />
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </StackPanel>
</UserControl>


As soon as the BindingValidationError event is raised in the host page, the ObservableCollection of errors is updated, and the user is provided with detailed feedback for each validation error (see Figure 23.9).

Image

Figure 23.9. The ValidationSummary control displays validation errors as they occur.

Property Setter Validation Limitations

While the Silverlight 3 property setter validation system is simple and easy to use, it has two significant limitations: mutually dependent property validation is cumbersome, and there is no support for asynchronous validation.

The Silverlight 3 property setter validation system allows you to validate one property at a time. Some properties, however, may be mutually dependent, with the value of one property altering the validity of another property.

One approach to solving this problem, while still using the property setter validation system, is to use a set of shadow properties, which perform composite semantic validation. This approach, however, falls short because it adds undue complexity and often leads to substantial code duplication.

The asynchronous validation features of Silverlight 4 decouple input validation from properties and provide a better solution where composite validation is required.

Furthermore, asynchronous validation is needed in situations such as validating input on a remote server, where asynchronous WCF service calls are performed.

Asynchronous and Composite Validation

New to Windows Phone 7.1 SDK is the INotifyDataErrorInfo interface, which is present in Silverlight 4. Silverlight 4 eliminates the need to rely on exceptions to indicate invalid property values. In addition, implementing INotifyDataErrorInfo makes it possible to evaluate the validity of a property on a secondary thread.

The INotifyDataErrorInfo interface contains three members (see Figure 23.10). The HasErrors property is used to determine whether the control contains validation errors. The GetErrors method returns all errors that are detected, while the ErrorsChanged event notifies listeners when an error has been added or removed from the set of known errors.

Image

Figure 23.10. INotifyDataErrorInfo interface is used to monitor input validation errors.

To enable validation using the INotifyDataErrorInfo interface, the binding expression’s NotifyOnValidationError property is set to true as shown:

<TextBox Text="{Binding ValidatedString1, Mode=TwoWay,
                    NotifyOnValidationError=True}" />

The next section looks at implementing INotifyDataErrorInfo so that it can be reused across your entire app.

A Reusable Implementation of the NotifyDataErrorInfo Interface

The DataErrorNotifier class in the downloadable sample code is a reusable implementation of the INotifyDataErrorInfo interface.

DataErrorNotifier makes implementing asynchronous validation easy. With the addition of a single validation method to your viewmodel, DataErrorNotifier manages the list of errors and takes care of raising data error events. Furthermore, validation can be restricted by registering only those properties that you want to be validated.

DataErrorNotifier is designed to validate classes that implement a custom IValidateData interface, such as the ViewModelBase class. The validation logic has been decoupled from the ViewModelBase class to be reusable for other types as well.

The DataErrorNotifier requires an instance of IValidateData. The IValidateData interface defines an asynchronous validation mechanism with a nonblocking BeginValidate method and an event to signal when validation is complete (see Figure 23.11).

Image

Figure 23.11. DataErrorNotifier provides validation for the ViewModelBase class.

The ViewModelBase class implements IValidateData. ViewModelBase creates an instance of the DataErrorNotifier class and passes itself to the DataErrorNotifier’s constructor, as shown:

protected ViewModelBase()
{
    dataErrorNotifier = new DataErrorNotifier(this, this);
...
}

DataErrorNotifier subscribes to the viewmodel’s PropertyChanged event. When the PropertyChanged event is raised, validation is performed automatically.


Note

If you want validation to be performed prior to the setting of the property’s backing field, modify the DataErrorNotifier so that it subscribes to the PropertyChanging event of the viewmodel rather than its PropertyChanged event.


The DataErrorNotifier takes responsibility for the INotifyDataErrorInfo implementation. Ordinarily, a viewmodel calls only the ViewModelBase’s AddValidationProperty and IsComplete methods. Both the ViewModelBase class and the DataErrorNotifier implement INotifyDataErrorInfo. The ViewModelBase class, however, merely calls through to its DataErrorNotifier instance.

Leveraging the DataErrorNotifier Class

To register a property for validation in a viewmodel, the AddValidationProperty is called in the constructor of the viewmodel, as demonstrated in the following excerpt:

AddValidationProperty(() => ARequiredStringProperty);

AddValidationProperty causes the DataErrorNotifier to automatically attempt to validate the property when the property changes or when the viewmodel is being validated in its entirety.

The lambda expression provides the ViewModelBase class with the means to resolve both the property and the name of the property. The ViewModelBase.AddValidationProperty method retrieves the name of the property and passes it and a Func to the DataErrorNotifier, as shown:

protected void AddValidationProperty(Expression<Func<object>> expression)
{
    PropertyInfo propertyInfo = PropertyUtility.GetPropertyInfo(expression);
    string name = propertyInfo.Name;
    Func<object> getter = (Func<object>)Delegate.CreateDelegate(
                                typeof(Func<object>),
                                this,
                                propertyInfo.GetGetMethod());

    dataErrorNotifier.AddValidationProperty(name, getter);
}

DataErrorNotifier stores the association between the property name and the Func, which allows the property to be retrieved by name during validation.

The AddValidationProperty method of the DataErrorNotifier is shown in the following excerpt:

public void AddValidationProperty(string name, Func<object> property)
{
    lock (propertyDictionaryLock)
    {
        propertyDictionary[name] = property;
    }
}

Provisioning for Asynchronous or Synchronous Validation

The validation infrastructure contained in the ViewModelBase and the DataErrorNotifier classes caters for both asynchronous and synchronous validation models. When using this system, validation code should reside in one of two overridable methods: BeginValidation or GetPropertyErrors.

BeginValidation uses an asynchronous event driven validation model, while GetPropertyErrors provides for a simpler synchronous model for when asynchronous processing is not required.

DataValidationError Class

When using either the synchronous or asynchronous validation models, validation errors are represented using a custom class named DataValidationError. DataValidationError has an ErrorMessage property of type string and an Id property of type int. The purpose of the Id property is that it allows you to use the same error message for multiple fields.

Provisioning for Synchronous Validation

The ViewModelBase.GetPropertyErrors method allows your viewmodel to perform input validation using a simple synchronous approach. The GetPropertyErrors method is overridden to validate specific properties in your viewmodel. The method returns all errors for the specified property as an IEnumerable<DataValidationError>.

In the following example, the GetPropertyErrors method has been overridden in a viewmodel containing two properties, neither of which is allowed to be null or to consist of entirely whitespace:

protected override IEnumerable<DataValidationError> GetPropertyErrors(
    string propertyName, object value)
{
    if (!string.IsNullOrWhiteSpace((string)value))
    {
        yield break;
    }
    switch (propertyName)
    {
        case "ValidatedString1":
            yield return new DataValidationError(1,
                "ValidatedString1 is required.");
            break;
        case "ValidatedString2":
            yield return new DataValidationError(2,
                "ValidatedString2 is required.");
            break;
    }
    yield break;
}

If an exception is raised during the execution of this method, it is handled by the ViewModelBase class. Exception handling is discussed further in the next section.

Provisioning for Asynchronous Validation

The BeginValidation method of the ViewModelBase class is the main extensibility point for providing asynchronous validation. This method should be overridden in subclasses to perform composite validation or validation of property values using, for example, WCF services.

The ViewModelBase class implementation of the BeginValidation method calls the virtual GetPropertyErrors method, which, as you saw in the preceding section, retrieves the list of errors for a specified property. See the following excerpt:

public virtual void BeginValidation(string memberName, object value)
{
    IEnumerable<DataValidationError> errors;
    try
    {
        errors = GetPropertyErrors(memberName, value);
    }
    catch (Exception ex)
    {
        OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, ex));
        return;
    }
    OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, errors));
}

The base implementation of the BeginValidation method executes synchronously, retrieving the list of errors and raising the ValidationComplete event.


Note

Once the BeginValidation method has finished validating, it is imperative that the ValidationComplete event is raised; otherwise, the DataErrorNotifier will not raise the INotifyDataErrorInfo.ErrorsChanged event, which prevents the UI from signalling that a validation error exists.


The following excerpt provides an example implementation of the BeginValidation method:

public override void BeginValidation(string memberName, object value)
{
    try
    {
        if (string.IsNullOrEmpty((string)value))
        {
            List<DataValidationError> errors
                = new List<DataValidationError>();
            DataValidationError error = null;

            switch (memberName)
            {
                case "ValidatedString1":
                    error = new DataValidationError(1,
                        "ValidatedString1 is required.");
                    break;
                case "ValidatedString2":
                    error = new DataValidationError(2,
                        "ValidatedString2 is required.");
                    break;
            }

            if (error != null)
            {
                errors.Add(error);
            }
            OnValidationComplete(
                new ValidationCompleteEventArgs(memberName, errors));
        }
        else
        {
            OnValidationComplete(
                new ValidationCompleteEventArgs(memberName));
        }
    }
    catch (Exception ex)
    {
        OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, ex));
    }
}

The ViewModelBase class contains a string keyed Dictionary of errors for each property, which is of type Dictionary<string, List<DataValidationError>>.

Decoupling Validation

The DataErrorNotifier instance relies on an IValidateData object to perform the actual semantic validation of viewmodel property values. Thus, the strategy for validating the viewmodel can be decoupled from the viewmodel itself. In most cases, however, the viewmodel implements IValidateData.

Validating Properties as They Change

The DataErrorNotifier class subscribes to the viewmodel’s PropertyChanged event so that when a property is changed, it is also validated. See the following excerpt:

public DataErrorNotifier(
    INotifyPropertyChanged owner, IValidateData validator)
{
    this.validator
             = ArgumentValidator.AssertNotNull(validator, "validator");
    ArgumentValidator.AssertNotNull(owner, "owner");

    validator.ValidationComplete += validator_ValidationComplete;
    owner.PropertyChanged += HandleOwnerPropertyChanged;
}

void HandleOwnerPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e == null || e.PropertyName == null)
    {
        return;
    }
    BeginGetPropertyErrorsFromValidator(e.PropertyName);
}

When a viewmodel property changes, a potentially asynchronous validation operation is commenced from the BeginGetPropertyErrorsFromValidator method, shown in the following excerpt:

void BeginGetPropertyErrorsFromValidator(string propertyName)
{
    Func<object> propertyFunc;
    lock (propertyDictionaryLock)
    {
        if (!propertyDictionary.TryGetValue(propertyName, out propertyFunc))
        {
            /* No property registered with that name. */
            return;
        }
    }
    validator.BeginValidation(propertyName, propertyFunc());
}

The BeginGetPropertyErrorsFromValidator method performs the following tasks:

• The property Func is retrieved from the property Dictionary.

• The property value is retrieved by invoking the property Func.

• The BeginValidation method of the viewmodel (implementing IValidateData) is called using the property name and the property value.

When the IValidateData finishes validating a property value, it raises the IValidateData.ValidationComplete event. This event is handled by the HandleValidationComplete method.

The first thing that the HandleValidationComplete method does is update the list of validation errors for the specified property. It does this by calling the SetPropertyErrors method, which attempts to retrieve the list of errors for that property. If the specified list of errors is different from the existing list of known errors for that property, the list of known errors is updated (or removed if empty) and the ErrorsChanged event is raised:

public void SetPropertyErrors(
    string propertyName, IEnumerable<DataValidationError> dataErrors)
{
    ArgumentValidator.AssertNotNullOrEmpty(propertyName, "propertyName");

    List<DataValidationError> list;
    bool raiseEvent = false;
    lock (errorsLock)
    {
        bool created = false;

        int paramErrorCount = dataErrors == null ? 0 : dataErrors.Count();
        if ((errorsField == null || errorsField.Count < 1)
            && paramErrorCount < 1)
        {
            return;
        }
        if (errorsField == null)
        {
            errorsField
                = new Dictionary<string, List<DataValidationError>>();
            created = true;
        }

        bool listFound = false;
        if (created ||
            !(listFound = errorsField.TryGetValue(propertyName, out list)))
        {
            list = new List<DataValidationError>();
        }

        if (paramErrorCount < 1)
        {
            if (listFound)
            {
                errorsField.Remove(propertyName);
                raiseEvent = true;
            }
        }
        else
        {
            var tempList = new List<DataValidationError>();
            foreach (var dataError in dataErrors)
            {
                if (created || list.SingleOrDefault(
                    e => e.Id == dataError.Id) == null)
                {
                    tempList.Add(dataError);
                    raiseEvent = true;
                }
            }
            list.AddRange(tempList);
            errorsField[propertyName] = list;
        }
    }

    if (raiseEvent)
    {
        OnErrorsChanged(propertyName);
    }
}

The OnErrorsChanged method raises the ErrorsChanged event on the UI thread using the app’s Dispatcher. An extension method is used to ensure that the method is invoked only if the thread is not the UI thread; otherwise, the delegate is called from the current thread.

protected virtual void OnErrorsChanged(string property)
{
    Dispatcher dispatcher = Deployment.Current.Dispatcher;
    dispatcher.InvokeIfRequired(
        delegate
        {
            ErrorsChanged.Raise(this,
                new DataErrorsChangedEventArgs(property));
        });
}

Asynchronous Validation of All Properties

As the user completes a form, individual properties are validated. To validate an entire form at once, the same approach is used; however, each property is validated, one by one, until all are validated, or until one is deemed invalid.

The IsComplete method of the DataErrorValidator attempts to validate each known property and accepts handlers that are invoked when one of the following three conditions is met:

completeAction—The viewmodel is complete; that is, no properties have associated data validation errors.

incompleteAction—The viewmodel is incomplete; that is, one or more properties has an associated data validation error.

unknownAction—Determination of the viewmodel’s completeness failed because an exception was raised.

To validate each property, the DataErrorValidator creates a list of all known property names, and as each property is validated, the property name is removed from the list, as shown in the following excerpt:

public void IsComplete(Action completeAction,
                       Action incompleteAction,
                       Action<Exception> unknownAction)
{
    this.completeAction = completeAction;
    this.incompleteAction = incompleteAction;
    this.unknownAction = unknownAction;

    try
    {
        if (!LockedOperations.TrySetTrue(
            ref isEvaluating, isEvaluatingLock))
        {
            return;
        }

        if (propertyDictionary == null)
        {
            if (completeAction != null)
            {
                completeAction();
            }
            return;
        }

        lock (waitingForPropertiesLock)
        {
            waitingForProperties.Clear();
            foreach (KeyValuePair<string, Func<object>> pair
                                                 in propertyDictionary)
            {
                waitingForProperties.Add(pair.Key);
            }
        }
        foreach (KeyValuePair<string, Func<object>> pair
            in propertyDictionary)
        {
            validator.BeginValidation(pair.Key, pair.Value());
        }
    }
    catch (Exception ex)
    {
        isEvaluating = false;
        if (unknownAction != null)
        {
            unknownAction(ex);
        }
    }
}

The LockedOperations.TrySetTrue method provides for thread safety when reading and setting the isEvaluating flag. If isEvaluating is already true, the call to the IsComplete method is ignored, as shown:

public static bool TrySetTrue(ref bool value, object valueLock)
{
    ArgumentValidator.AssertNotNull(valueLock, "valueLock");
    if (!value)
    {
        lock (valueLock)
        {
            if (!value)
            {
                value = true;
                return true;
            }
        }
    }
    return false;
}

The TrySetTrue method reduces the amount of locking related code in the IsComplete method.

When the list of property names in the IsComplete method is empty or an exception is raised, the evaluation of the IsComplete method is deemed to be complete.

At completion, one of the three IsComplete action arguments is invoked. If the ValidationCompleteEventArgs contains an Exception, the unknownAction is invoked. If there are any data validation errors for the property, the incompleteAction is invoked. If there are no properties left to validate, the completeAction is invoked. See the following excerpt:

void HandleValidationComplete(object sender, ValidationCompleteEventArgs e)
{
    try
    {
        if (e.Exception == null)
        {
            SetPropertyErrors(e.PropertyName, e.Errors);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine("Unable to set property error." + ex);
    }

    if (!isEvaluating)
    {
        return;
    }

    lock (isEvaluatingLock)
    {
        if (!isEvaluating)
        {
            return;
        }

        try
        {
            bool finishedEvaluating;
            lock (waitingForPropertiesLock)
            {
                waitingForProperties.Remove(e.PropertyName);
                finishedEvaluating = waitingForProperties.Count < 1;
            }

            if (e.Exception != null)
            {
                isEvaluating = false;
                if (unknownAction != null)
                {
                    unknownAction(e.Exception);
                }
            }

            if (e.Errors != null && e.Errors.Count() > 0)
            {
                isEvaluating = false;
                if (incompleteAction != null)
                {
                    incompleteAction();
                }
            }

            if (finishedEvaluating)
            {
                bool success = isEvaluating;
                isEvaluating = false;
                if (success && completeAction != null)
                {
                    completeAction();
                }
            }
        }
        catch (Exception ex)
        {
            Debug.WriteLine("Unable to validate property." + ex);
            isEvaluating = false;
        }
    }
}

An Example of Asynchronous Input Validation

The downloadable sample code contains a number of examples of asynchronous input validation. The simplest of which is located in the AsyncValidationViewModel class.

The AsyncValidationView contains two string properties that are required to be non-null or whitespace strings (see Figure 23.12).

Image

Figure 23.12. AsyncValidationView page

When either of the two properties is set to an empty or whitespace string, a data validation error is created for that property. This validation occurs when the user modifies the text or when the Submit button is tapped (see Figure 23.13).

Image

Figure 23.13. The Validated String 1 form field is deemed invalid.

The two TextBox controls are each bound to a property in the viewmodel, as shown in the following excerpt:

<StackPanel x:Name="ContentPanel" Grid.Row="1" Margin="12,60,12,0">
    <TextBlock Text="Validated String 1"
                Style="{StaticResource PhoneTextTitle3Style}" />
    <TextBox Text="{Binding ValidatedString1, Mode=TwoWay,
                                NotifyOnValidationError=True}"
                Style="{StaticResource ValidatingTextBoxStyle}" />
    <TextBlock Text="Validated String 2"
                Style="{StaticResource PhoneTextTitle3Style}" />
    <TextBox Text="{Binding ValidatedString2, Mode=TwoWay,
                                NotifyOnValidationError=True}"
                Style="{StaticResource ValidatingTextBoxStyle}" />
    <StackPanel Orientation="Horizontal">
        <Button Content="Submit"
            Command="{Binding SubmitCommand}"
            Width="144" Height="75" HorizontalAlignment="Left" />
        <controls:ValidationSummary />
    </StackPanel>
    <TextBlock Text="{Binding Message}"
                Style="{StaticResource PhoneTextNormalStyle}" />
</StackPanel>

The submit Button is bound to the viewmodel’s SubmitCommand. The SubmitCommand is instantiated in the constructor of the AsyncValidationViewModel class, as shown in the following excerpt:

public AsyncValidationViewModel()
{
    AddValidationProperty(() => ValidatedString1);
    AddValidationProperty(() => ValidatedString2);

    submitCommand = new DelegateCommand(
        delegate
        {
            IsComplete(
                () => Message = "Data Submitted!",
                () => Message = string.Empty,
                obj => Message = "An error occured: " + obj.Message);
        });
}

When the button is pressed, the SubmitCommand calls the IsComplete method of the ViewModelBase class, which calls the DataErrorNotifier.IsComplete method with the same signature. Recall that the IsComplete method has the following three parameters:

• An Action to perform if there are no validation errors.

• An Action to perform if there are validation errors.

• A Func to perform if an exception is raised and the validation process fails.

The IsComplete method causes the overridden BeginValidation method of the AsyncValidationViewModel class to be called. Asynchronous behavior is simulated by calling a private Validate method using a thread from the ThreadPool. In a less trivial application, we could imagine that this call could be to a web service for example.

public override void BeginValidation(string memberName, object value)
{
    try
    {
        /* Perform validation asynchronously. */
        ThreadPool.QueueUserWorkItem(state => Validate(memberName, value));
    }
    catch (Exception ex)
    {
        OnValidationComplete(
            new ValidationCompleteEventArgs(memberName, ex));
    }
}

As previously stated, it is imperative that the ValidationComplete event is raised no matter what the outcome of the validation activity. Therefore, the validation logic is wrapped in a try-catch block, so that if an exception occurs, the ValidationComplete event is still raised, as shown in the following excerpt:

void Validate(string propertyName, object value)
{
    try
    {
        IEnumerable<DataValidationError> errors
            = GetPropertyErrors(propertyName, value);
        if (errors != null && errors.Count() > 0)
        {
            OnValidationComplete(
                new ValidationCompleteEventArgs(propertyName, errors));
        }
        else
        {
            OnValidationComplete(
                new ValidationCompleteEventArgs(propertyName));
        }
    }
    catch (Exception ex)
    {
        OnValidationComplete(
            new ValidationCompleteEventArgs(propertyName, ex));
    }
}

If either of the viewmodel’s string properties fails validation, a DataValidationError for the property is added to the list of validation errors for that property.

Detecting a Change of Data Context

The FrameworkElement.DataContext property is a fundamental part of the Silverlight data binding system and allows an object to be associated with a top level element in the visual tree and then inherited by descendants of that element.

Unfortunately, Silverlight 4 for Windows Phone does not include a public event for detecting when the DataContext of a FrameworkElement is changed. As a workaround, I have included in the downloadable sample code a class named DataContextChangedListener that uses attached properties to emulate a DataContextChanged event, which allows you to receive notification when a FrameworkElement’s data context is changed (see Listing 23.4).

The Subscribe method of the DataContextChangedListener associates a PropertyChangedCallback delegate with a specified FrameworkElement. The delegate is invoked when a change to the DataContextProperty is detected.

The HandleDataContextChanged handler is called when the FrameworkElement object’s DataContext is changed because changing the DataContext of the FrameworkElement causes all bindings for the FrameworkElement to be reevaluated.

The fact that the DataContextProperty is named DataContextProperty is arbitrary and has no effect on the association between the attached property and the FrameworkElement.DataContext property.

Listing 23.4. DataContextChangedListener Class


public class DataContextChangedListener
{
    static readonly DependencyProperty DataContextProperty
        = DependencyProperty.RegisterAttached(
            "DataContextProperty",
            typeof(object),
            typeof(FrameworkElement),
            new PropertyMetadata(HandleDataContextChanged));
    static readonly DependencyProperty HandlersProperty
        = DependencyProperty.RegisterAttached(
            "HandlersProperty",
            typeof(PropertyChangedCallback),
            typeof(FrameworkElement),
            new PropertyMetadata(
                (object)((PropertyChangedCallback)(delegate { }))));

    public static void Subscribe(
        FrameworkElement element, PropertyChangedCallback handler)
    {
        ArgumentValidator.AssertNotNull(element, "element");
        ArgumentValidator.AssertNotNull(handler, "handler");

        PropertyChangedCallback handlers
            = (PropertyChangedCallback)element.GetValue(HandlersProperty);

        handlers += handler;

        element.SetValue(HandlersProperty, handlers);

        if (element.GetBindingExpression(DataContextProperty) == null)
        {
            element.SetBinding(DataContextProperty, new Binding());
        }
    }

    public static void Unsubscribe(
        FrameworkElement element, PropertyChangedCallback handler)
    {
        ArgumentValidator.AssertNotNull(element, "element");
        ArgumentValidator.AssertNotNull(handler, "handler");

        PropertyChangedCallback handlers
            = (PropertyChangedCallback)element.GetValue(HandlersProperty);

        handlers -= handler;
        element.SetValue(HandlersProperty, handlers);
    }

    static void HandleDataContextChanged(
        DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        FrameworkElement element = (FrameworkElement)d;
        PropertyChangedCallback handlers
            = (PropertyChangedCallback)element.GetValue(HandlersProperty);

        PropertyChangedCallback tempEvent = handlers;
        if (tempEvent != null)
        {
            tempEvent(d, e);
        }
    }
}


The ValidationSummary control uses the DataContextChangedListener to subscribe to the INotifyDataErrorInfo.ErrorsChanged event of the viewmodel.

When the control is loaded, the control uses the static DataContextChangedListener.Subscribe method to add its HandleDataContextChanged method to the list of handlers.

If and when the handler is called, the control unsubscribes from the previous INotifyDataErrorInfo and subscribes to the new object’s event. See the following excerpt:

void HandleLoaded(object sender, System.Windows.RoutedEventArgs e)
{
...
    DataContextChangedListener.Subscribe(this, HandleDataContextChanged);
}

void HandleDataContextChanged(
    DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    /* Unsubscribe to previous notifier. */
    INotifyDataErrorInfo oldNotifier = e.OldValue as INotifyDataErrorInfo;
    if (oldNotifier != null)
    {
        oldNotifier.ErrorsChanged -= HandleErrorsChanged;
    }

    /* When the DataContext is an INotifyDataErrorInfo,
     * monitor it for errors. */
    INotifyDataErrorInfo notifier = DataContext as INotifyDataErrorInfo;
    if (notifier != null)
    {
        notifier.ErrorsChanged += HandleErrorsChanged;
    }
}

We can expect to see the FrameworkElement.DataContextChanged event made public in the next major release of the Windows Phone OS; in the meantime, however, the custom DataContextChangedListener is a simple solution that can be used wherever we need to respond to a change of an element’s data context.

Adding INotifyDataErrorInfo Support to the ValidationSummary Control

Now that you have looked at the INotifyDataErrorInfo interface and at the inner workings of the custom validation system, let us examine how the ValidationSummary control has been extended to support the INotifyDataErrorInfo interface.

As you saw in the previous section, the ValidationSummary control monitors its data context for validation errors if the data context implements INotifyDataErrorInfo. When the INotifyDataErrorInfo.ErrorsChanged event is raised by the data context, the control’s HandleErrorsChanged handler is called (see Listing 23.5).

ValidationSummary maintains a dictionary of validation errors, which are keyed by the associated property name. The HandleErrorsChanged method retrieves the list of validation errors from the INotifyDataErrorInfo and combines the list with the list in its own dictionary. If no list exists for the particular property name, then a new list is added to the dictionary.

Listing 23.5. ValidationSummary.HandleErrorsChanged Method


void HandleErrorsChanged(object sender, DataErrorsChangedEventArgs args)
{
    INotifyDataErrorInfo notifier = sender as INotifyDataErrorInfo;
    if (notifier == null)
    {
        return;
    }

    string propertyName = args.PropertyName;
    if (string.IsNullOrEmpty(propertyName))
    {
        return;
    }

    IEnumerable notifierErrors = notifier.GetErrors(args.PropertyName);

    List<object> errorList;
    if (!errorLookup.TryGetValue(propertyName, out errorList))
    {
        errorList = new List<object>();
    }

    foreach (var error in errorList)
    {
        RemoveError(error);
    }

    errorList.Clear();

    foreach (var error in notifierErrors)
    {
        AddError(error);
    }
    errorLookup[propertyName] = errorList;

    foreach (var error in errorList)
    {
        AddError(error);
    }
}


The ValidationSummary control now has support for both the property setter validation approach and for the Silverlight 4 INotifyDataErrorInfo validation approach.

Incorporating Group Validation

In the previous example, you saw how the ViewModelBase.IsComplete method is used to trigger validation of a viewmodel. This section looks at extending the validation to set control-enabled states based on the presence of validation errors and at hiding validation errors until the user has had a chance to fill out the form.

Before proceeding, you may want to briefly review the section “Performing Group Validation” earlier in the chapter and in particular Figure 23.7.

The SendEmailViewModel in the downloadable sample code is a more complex example of asynchronous input validation. In it, the sendEmailCommand is prevented from executing if it is already in execution or if the form is invalid.

When SendEmailView is first displayed, the Send button is enabled and no validation errors are displayed (see Figure 23.14).

Image

Figure 23.14. SendEmailView page. The Send button is enabled and no validation errors are shown, even though the form is not complete.

When the Send button is tapped, the viewmodel is validated. If invalid, the Send button is disabled (see Figure 23.15).

Image

Figure 23.15. SendEmailView page. The Send button is disabled once the form is deemed incomplete.

The Send button is bound to the viewmodel’s ICommand named SendEmailCommand, as shown:

<Button Content="Send"
        Command="{Binding SendEmailCommand}" />

Evaluation of whether the command can execute depends on the following criteria:

• If the command is in execution, it cannot be executed.

• If an attempt to execute the command has not occurred before, it can be executed.

• If the viewmodel has validation errors, it cannot be executed.

SendEmailCommand is instantiated in the viewmodel constructor, as shown:

sendEmailCommand = new DelegateCommand(arg => SendEmail(),
                         arg => !sending && (!sendAttempted || !HasErrors));

The SendEmail method passes three Lambda expressions to the ViewModelBase class’s IsComplete method (see Listing 23.6). The first action is performed if the form is deemed complete, in which case an EmailComposeTask is used to dispatch the email. The second action is performed if the form is not complete, and the third displays a warning message if an error is raised.

Listing 23.6. SendEmailViewModel.Send Method


void SendEmail()
{
    if (sending)
    {
        return;
    }

    try
    {
        sending = sendAttempted = true;
        Message = string.Empty;
        RefreshCommands();
        IsComplete(() =>
                    {
                        sending = false;
                        EmailComposeTaskAdapter task = getEmailTaskAdapter();
                        task.Body = body;
                        task.Cc = cc;
                        task.Subject = subject;
                        task.To = to;
                        task.Show();
                    },
                    () =>
                    {
                        sending = false;
                        RefreshCommands();
                    },
                    obj =>
                    {
                        sending = false;
                        RefreshCommands();
                        string errorMessage = obj != null
                                    ? obj.Message : string.Empty;
                        Message = "Unable to send email: "
                                    + errorMessage;
                    });
    }
    catch (Exception ex)
    {
        sending = false;
        Message = "Unable to send email: " + ex.Message;
    }
}


A call to RefreshCommands raises the CanExecuteChanged event of the command, which in turn sets the IsEnabled property of the Button:

void RefreshCommands()
{
    sendEmailCommand.RaiseCanExecuteChanged();
}

Summary

This chapter began by defining the two types of input validation: syntactic and semantic. Syntactic validation ensures that user input has the correct format and can be converted to the correct type, while semantic validation may entail more complex validation such as the evaluation of business rules.

This chapter then looked at the Silverlight 3 property setter validation system. You saw how this system relies on the raising of exceptions to indicate validation errors. Noncritical exceptions raised in property setters produce binding errors, which signal validation errors.

You saw how Silverlight for Windows Phone requires custom templates for enabling visualization of input validation errors, and you looked at using Expression Blend to copy and customize the way error related visual states are displayed.

Finally, the chapter examined the Silverlight 4 validation system, which enables asynchronous and composite input validation. You looked at setting control enabled states based on the presence of validation errors and at hiding validation errors until the user has had a chance to fill out the form.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.85.178