(X) Hide this
    • Login
    • Join
      • Generate New Image
        By clicking 'Register' you accept the terms of use .

A simplified DataForm replacement - Part 2 Adding validation support

(8 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
10 comments   /   posted on Jan 27, 2010

This article is compatible with the latest version of Silverlight.

In the previous part of this article I introduced a custom Form control I made for some real world projects. I've briefly explained the reasons why I choose to not use the Silverlight Toolkit's DataForm, just before to show how to build this control, and I've also detailed what I like of the DataForm and what I should retain in my Form control.  One of this features is the validation of the input and it is what I'm about to describe in the second part.

Download Source Code

The databinding in Silverlight directly supports the validation through a couple of attributes, that let the developer specify if the markup extension has to notify validation exceptions to the control binded to a property so it can show the errors to the user. This kind of validation is based on the check of the value assigned to a property then the user change the value. Just to make an example, the validation of a TextBox is triggered by the writing of some charachters into it. This cause the binded property to be changed and then an eventual validation exception to be raised.

There is one major drawback in this tecnique that takes place if a validation is required when the value of a property has not been changed. The common example is the RequiredAttribute attribute. If you apply this attribute to a property you will expect to notify the user when he does not assign any value to the input control. But no check of the value is done if the user does not change it, so when he tries to submit the input it is considered valid even if it is empty.

Unfortunately the RequiredAttribute is one of most common checks in the input validation, at least common as the type check. The tecnique to solve this problem require to manually trigger the update of the binding source and is very verbose in the page code behind.

The use of the DataForm control solve the problem, because the control is aware of it and takes a reference to any binding expression used in the form and just before submitting the input raise the validation events using the UpdateSource() method. This is the sole available tecnique because it is able to raise the validation exceptions, also if caused by the attributes, and let them to reach the control that has to display the error.

Adding Validation to the Form

After understanding the troubles caused by validation and how the toolkit's DataForm behave, it is time to add validation support to the custom Form control. Basically I need to add the same behavior of the DataForm just because this leave me free to forget the problem when I use the control. What I have to do is:

1) Scan the Form content to detect BindingExpression instances
2) Enumerate the BindingExpressions and call the UpdateSource method to let each Binding updates the related property
3) Check if validation errors has occured and eventually stop the flow before raising the Commit event.

The first task of this checklist is the most hard to understand. You are to be aware that the developer simply adds some markup as content of the Form and every element of this markup may contains a Binding Expression connection a property to the DataContext. So the problem is that we have to scan each element of the content and each DependencyProperty of the every single element. To scan the elements I can use the same extension method I've used in the previous part to detect the Commands but I have to introduce another method that is able to enumerate the DependencyProperties:

 public static IEnumerable<DependencyProperty> DependencyProperties<T>(this T element)
     where T : DependencyObject
 {
     List<DependencyProperty> list = new List<DependencyProperty>();
 
     if (element != null)
     {
         if (element is Panel || 
            element is Button || 
             element is Image || 
             element is ScrollViewer || 
             element is TextBlock || 
             element is Border || 
             element is Shape || 
             element is ContentPresenter || 
             element is RangeBase)
             return list;
  
         FieldInfo[] fields = element
             .GetType()
             .GetFields(BindingFlags.FlattenHierarchy | BindingFlags.Public | BindingFlags.Static);
 
         if (fields == null)
             return list;
  
         foreach (FieldInfo info in fields)
             if (info.FieldType == typeof(DependencyProperty))
                 list.Add((DependencyProperty)info.GetValue(null));
     }
  
     return list;
 }

What I do in this snippet is to use reflection to detect the static fields of the element which are of type DependencyProperty. You probably know that to define a DependencyProperty you have to add a static field used by the framework to contain informations about the property itself. So when I found a field of this type I'm sure to have found a DependencyProperty. I can now use write the code to extract the bindings:

 private List<BindingInfo> DetectBindings()
 {
     List<BindingInfo> bindingExpressions = new List<BindingInfo>();
  
     FrameworkElement content = this.ContentPresenterElement.Content as FrameworkElement;
  
     if (content != null)
     {
         foreach (FrameworkElement element in content.Elements<FrameworkElement>())
         {
             bindingExpressions.AddRange(from be in
                                             from dp in element.DependencyProperties()
                                             select new BindingInfo(element.GetBindingExpression(dp), dp, element)
                                         where be.BindingExpression != null && Object.Equals(this.DataContext, be.BindingExpression.DataItem)
                                         select be);
         }
     }
  
     return bindingExpressions;
 }

Starting from the ContentPresenter I enumerate the elements and for each one I extract the DependencyProperties which have a BindingExpression. The DependencyProperty class has a method named GetBindingExpression that is able to return the instance of the associated BindingExpression.

The DetectBindings method returns a list of the bindings found in the content. For performance purpose I skip some type of elements I'm sure do not have an useful binding. So now I can accomplish the second task of the list and call the UpdateSource method.

 /// <summary>
 /// Updates all sources.
 /// </summary>
 private void UpdateAllSources()
 {
     List<BindingInfo> bindings = this.DetectBindings();
  
    foreach (BindingInfo bi in bindings)
         bi.BindingExpression.UpdateSource();
 }

The result of this flow is the updating of the values, from the controls to the properties, and obviously the throwing of the exceptions if the values violate one of the rules applied. The user will see the fields become red and the ValidationSummary will show a list of the errors. This flow is started every time an user hit one of the controls connected to the commit command. To avoid the raise of the commit command when an error is found I use a property of the ValidationSummary. The HasDisplayedErrors is true when the summary detect an error.

How to use the control

The control I described until now is available in the attached code. I figure out some of you will want to have a try, so now I will show you how to use the control. As I said at the beginning of the article, the control is pretty much like an HTML form tag where the content is submitted to the server to a page defined by the action attribute. My form let the developer specify bindings in markup, like you always do with a DataTemplate, and raise a commit or cancel event you can attach from the codebehind in traditional event-based programming, or with a Prism command in MVVM scenario. Here is a snippet:

 <controls:Form cmd:Commit.Command="{Binding Parent.AddCommand}" 
                DataContext="{Binding Person}" 
                Header="Add People Form"
                HeaderStyle="{StaticResource FormHeaderStyle}"
                Padding="5" 
                ValidationSummaryVisibility="{Binding Path=IsChecked,ElementName=chkSummary,Converter={StaticResource BoolToVisibility}}">
     <Grid>
         <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <Grid.ColumnDefinitions>
             <ColumnDefinition Width="0.5*"/>
             <ColumnDefinition Width="0.5*"/>
         </Grid.ColumnDefinitions>
         <dft:DataField LabelPosition="Top">
             <TextBox Text="{Binding FirstName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
         </dft:DataField>
         <dft:DataField LabelPosition="Top" Grid.Column="1">
             <TextBox Text="{Binding LastName, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
         </dft:DataField>
         <dft:DataField  LabelPosition="Top" Grid.ColumnSpan="2" Grid.Row="1">
             <swc:DatePicker SelectedDate="{Binding Birthday, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
         </dft:DataField>
         <dft:DataField  LabelPosition="Top" Grid.ColumnSpan="2" Grid.Row="2">
             <TextBox Text="{Binding Email, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" />
         </dft:DataField>
         <StackPanel HorizontalAlignment="Right" Orientation="Horizontal" VerticalAlignment="Bottom" Grid.Row="3" Margin="0,5,5,5" Grid.ColumnSpan="2">
             <CheckBox x:Name="chkSummary" Foreground="Black" Margin="0,0,15,0" VerticalAlignment="Center" Content="Enable validation summary"/>
             <Button controls:Form.TriggerCommand="Commit" controls:Form.TriggerEvent="Click" Content="Add Person" Width="80" />
         </StackPanel>
     </Grid>
 </controls:Form>

As you may have noticed the layout of the Form is a Grid, and I use some DataFields inside of it to define the fields and its labels. The last row of the grid contains a StackPanel where I've put the submit button and a CheckBox to show how to hide the ValidationSummary. The good thing is that you can bind the CheckBox value directly to the ValidationSummaryVisibility property and write less code, thanks to Element-to-Element binding. The Header property lets me specify the content of the header. I've used a text but it is binded to a ContentControl so you can use more complex XAML code.

Finally I've to say two words about the ViewModel. You can see, the Form is connected to a Person property, but this is not an instance of a Person business object, but a child ViewModel where I specified Validation and Display attributes. The major drawback of binding directly on the DataContext property come from the fact that the Commit command have to be binded to the child ViewModel itself also if it have to be used in the parent. So I simply create the child ViewModel with a Parent property to expose the parent ViewModel and its commands. Here is the code:

 public class PersonViewModel<T> : ObservableObject
 {
     public T Parent { get; set; }
  
     [Display(Name = "First Name")]
     [Required]
     [StringLength(10)]
     public string FirstName
     {
         get { return this.GetValue<string>("FirstName"); }
         set { this.SetValue<string>("FirstName", value); }
     }
  
     [Display(Name = "Last Name")]
     public string LastName
     {
         get { return this.GetValue<string>("LastName"); }
         set { this.SetValue<string>("LastName", value); }
     }
  
     [Display(Name = "Birthday")]
     public DateTime Birthday
     {
         get { return this.GetValue<DateTime>("Birthday"); }
         set { this.SetValue<DateTime>("Birthday", value); }
     }
  
     [Display(Name = "Email Address")]
     [RegularExpression(@"^((?>[a-zA-Z\d!#$%&'*+\-/=?^_`{|}~]+\x20*|""((?=[\x01-\x7f])[^""\\]|\\[\x01-\x7f])*""\x20*)*(?<angle><))?((?!\.)(?>\.?[a-zA-Z\d!#$%&'*+\-/=?^_`{|}~]+)+|""((?=[\x01-\x7f])[^""\\]|\\[\x01-\x7f])*"")@(((?!-)[a-zA-Z\d\-]+(?<!-)\.)+[a-zA-Z]{2,}|\[(((?(?<!\[)\.)(25[0-5]|2[0-4]\d|[01]?\d?\d)){4}|[a-zA-Z\d\-]*[a-zA-Z\d]:((?=[\x01-\x7f])[^\\\[\]]|\\[\x01-\x7f])+)\])(?(angle)>)$",
         ErrorMessage="Invalid Email Format")]
     public string Email
     {
         get { return this.GetValue<string>("Email"); }
         set { this.SetValue<string>("Email", value); }
     }
  
     public PersonViewModel(T parent)
     {
         this.Parent = parent;
         this.Birthday = DateTime.Today;
     }
 }

The GetValue and SetValue members come from the base class that implements the INotifyPropertyChanged interface using a Dictionary. As in a generated business object when I set a value I expect to get validation exceptions. Obviously you can do this by hand implementing the interface directly on the ViewModel but I prefer using a custom ObservableObject base class. The SetValue method checks that the value is valid:

 protected virtual void SetValue<T>(string name, T value)
 {
     if (this.Properties.ContainsKey(name))
    {
         if (!object.Equals(this.Properties[name], value))
         {
             this.ValidateProperty(name, value);
             this.Properties[name] = value;
             this.OnPropertyChanged(name);
         }
     }
     else
     {
         this.ValidateProperty(name, value);
         this.Properties.Add(name, value);
         this.OnPropertyChanged(name);
     }
 }
   
 protected virtual void ValidateProperty(string name, object value)
 {
     ValidationContext ctx = new ValidationContext(this, null, null);
     ctx.MemberName = name;
     Validator.ValidateProperty(value, ctx);
 }

Conclusion

I've used the control in an MVVM scenario to accomplish two tasks: the first one is the creation of input forms related to the editing of the rows of a table (something you usually do with the regular DataForm) , but I found it useful also to validate the input of an user that is applying a filter to a search. Enjoy the control in the attached code and feel free to contact me if you have any question.


Subscribe

Comments

  • Semkaa

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by Semkaa on Mar 07, 2010 03:08
    Great article. Thanks. This is exactly what I need.
    But I can't embed this control into my project.
    I made a page like yours MainPage.xaml, but in OnApplyTemplate() method GetTemplateChild() returns null, which makes impossible further control initialization.
  • Semkaa

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by Semkaa on Mar 07, 2010 03:40
    Fixed that, it was all about generic.xaml.
    Andrea, thanks again for great job.
  • -_-

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by Andrea Boschin on Mar 07, 2010 10:19
    Good to know you have solved you issue. Thanks.
  • -_-

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by mbrig on May 06, 2010 16:39
    i agree, great article.  i am having the same problem though.  gettemplatechild returns null.  if the problem is in generic.xaml.  how can this be solved?
  • -_-

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by mbrig on May 07, 2010 16:50
    Thank you Semkaa for the help.  The build action had to be set to Resource.  Problem solved!  Thanks again to Andrea for a great article.  This will be a great addition to our project.
  • -_-

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by Andrea Boschin on May 07, 2010 16:55
    thank you all for the feedbacks. It's good to know my work is useful. bye.
  • -_-

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by razor on Jul 27, 2010 05:30
    I had this exact problem and solved it in a much simpler and less complex way. First, the dataform is just fine however it does need a little push in the MVVM drection. I solved mine with a behavior that exposes two things. A ValidateAndCommit command and a EditComplete event. Simply tell any control to call the command on the the behavior and wire any event handler you want to the EditComplete event on the behavior and your done. What are the advantages? You don't need a custom control so all your themes will still work. Any control can call the command as it normally would. It can be dragged on your dataform in blend and wired up. Your still using the dataform and should it even support these things like it should have you can throw away the behavior and keep rockin...
  • PeterUstinof

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by PeterUstinof on Sep 03, 2010 17:38

    Great article. 

    But the standard DataForm controls align the label and the textbox in a nice way (labels has got all the same width), whereas your solution it looks a bit flattered. 

    I've no idea, how I could solve this. "SharedSizeGroup" seems not to be possible in silverlight.

    Thanks 

    Peter 

  • -_-

    RE: A simplified DataForm replacement - Part 2 Adding validation support


    posted by Andrea Boschin on Sep 03, 2010 18:00

    I also noticed the behavior you describe but I didn't have the time to investigate the issue. I figure out the DataForm scans the fields after the are rendered and make the alignment someway.

    Thanks for your message.

  • -_-

    Validation on Cancel


    posted by wizardnet on Jan 08, 2011 16:28

    Hello Andrea,

    I observed a small problem when canceling the form, the validation still occurs, I guess because

    if (!this.ValidateItem())
    return;

    if (string.Compare(commandName, Form.CommitCommand, StringComparison.OrdinalIgnoreCase) == 0){
       this.OnCommit();
    }
    else if (string.Compare(commandName, Form.CancelCommand, StringComparison.OrdinalIgnoreCase) == 0)
      
    this.OnCancel();

    I think the validation part shall be put only inside the Commit if, just before the this.OnCommit.

    right?

Add Comment

Login to comment:
  *      *       

From this series