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.
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 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.
The Silverlight data binding system performs syntactic validation before assigning a value to a property.
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.
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/.
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.
IValueConverters
are unable to participate in input validation because if an IValueConverter
throws an exception, it is re-thrown by the BindingExpression
.
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
.
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 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.
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.
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.
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.
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.
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).
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.
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).
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).
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).
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.
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.
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.
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.
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).
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).
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!
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).
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).
<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).
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.
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.
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.
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).
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.
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.
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;
}
}
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.
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.
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.
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.
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
>>
.
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
.
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));
});
}
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;
}
}
}
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).
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).
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.
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.
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.
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.
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.
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).
When the Send button is tapped, the viewmodel is validated. If invalid, the Send button is disabled (see Figure 23.15).
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.
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();
}
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.
3.145.85.178