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

A simplified DataForm replacement - Part 1 Create the control and detect commands

(1 votes)
Andrea Boschin
>
Andrea Boschin
Joined Nov 17, 2009
Articles:   91
Comments:   9
More Articles
4 comments   /   posted on Jan 21, 2010

This article is compatible with the latest version of Silverlight.

Working with DataForm I found difficult to use it in many scenarios due to its design. So I've created a control where I do not generate fields but I handle automatic validation and cancel/commit commands enabling also the usage of the control in mvvm scenarios. This is a good starting point to understand about Validation, BindingExpressions and traversing the visual tree.

Read part 2 of this article series called: A simplified DataForm replacement - Part 2 Adding validation support

-----

The first time I've met the DataForm control - it was some days after its beta release in the Silverlight Toolkit - I was really astounded from its power. It is capable of many wonders: it generates fields detecting the properties of the binded item, it automatically validates the input values using a bunch of attributes in a way similar to ASP.NET Dynamic Data, it manages the workflow passing through Edit, ReadOnly and Insert state, and these are only a small subset of the features it exposes.

The truth is that I'm always astonished by a similar concentrate of features and initially I think it may be a good solution for problems it would like to address. As everyone knows, this kind of interfaces are the most boring to be developed because they are always repetitive and this is prone to common errors caused by loss of concentration by the developer. So, it is natural to start searching for some tools that simplify the work, handling known problems of the input interfaces so we can be free to concentrate on what it really matter.

Please raise your hand if you feel always slightly unsatisfied by fields generated by a DataForm. I hate UI generation because it always gives me the feel of having a good solution but finally I must put in place the template and create the fields by hand because I discover there is a need I cannot address with the generated UI. And another thing I do not understand is why I must accept the workflow of the DataForm. I've wasted most of my time trying to hack the DataForm to bend it to my will and finally I understood that I have to accept all the workflow or I have to avoid to use it and this is mostly true if I have to implement a MVVM application.

What I really appreciate is the integrated validation and the DataField control. The first one, that is indeed a feature coming from DataBinding enforced by the DataForm, let me spare a lot of time in defining validation rules on the properties, where it is a most obvious and easy task to do. On the other side I use often the DataField control because it handles automatically the label, the description and the layout of a field. This helps me to move labels to resource files and is a good way to make application easily localizable.

With this in mind I've created a simplified Form control that works in a way most similar to the html tag instead of a DataForm. My intent has been take the good of the DataForm and discard what I do not really need or that creates me troubles. I do not think it is the final solution but to me it has been a good exercise to learn how things works.

How the control must work

So, to summarize, here is what I would like to have from my custom Form control:

1) I want to design the layout of the form by hand. If it is true I never use the generated UI so I do not need to implement any logic to do this task. I have to be able to use Blend for this task.

2) I want to be able to use the DataField control to simplify the task of creating the layout. If you ever tried to customize the template of the DataForm you know what I mean.

3) I want that the Form enforces the validation. This mean having an embedded ValidationSummary, so I do not need to put it in place by hand, but also accomplish some tasks needed to have a fully functional validation.

4) I want to be able of customizing the look & feel of the Form working with the ControlTemplate so I can have the same aspect on the whole application.

5) Finally I would like it leave me free to work with MVVM. This mean having a commit and a cancel event I can attach with a command and use databinding.

Building the templated control

First of all I have to build the control as templated and I've decided to use the ContentControl as base. This let me put the form layout inside the form (no need to use a DataTemplate here) and accomplish the requirement 1 & 4. Using the templated control lets me specify a default template in the generic.xaml that may be easily customized by the designer.

 <Style TargetType="local:Form">
     <Setter Property="Template">
         <Setter.Value>
             <ControlTemplate TargetType="local:Form">
                 <StackPanel Margin="{TemplateBinding Padding}">
                     <ContentControl Content="{TemplateBinding Header}" 
                                     Style="{TemplateBinding HeaderStyle}" />
                     <ContentPresenter x:Name="ContentPresenterElement" 
                                       Content="{TemplateBinding Content}" />
                     <di:ValidationSummary x:Name="ValidationSummaryElement" 
                                           Visibility="{TemplateBinding ValidationSummaryVisibility}" />
                 </StackPanel>
             </ControlTemplate>
         </Setter.Value>
     </Setter>
 </Style>

Here above I shown the template of the Form control I've put in the generic.xaml file. It is composed by three parts: The ContentControl at the very top is the container for the Header. It may be customized using the Header property of the Form and styled by the HeaderStyle property. Then there is the ContentPresenter I use to position the Content of the Form. This is the place where the layout and the fields I've created by hand will be placed. Finally I've added a ValidationSummary that will show the input errors. We will see how this control works in the second part of the article. In the default template these parts are stacked but nothing prevent to change the order or the position. You are also free to remove some parts for your needs.

 [TemplatePart(Name = Form.ValidationSummaryElementName, Type = typeof(ValidationSummary))]
 [TemplatePart(Name = Form.ContentPresenterElementName, Type = typeof(ContentPresenter))]
 public class Form : ContentControl
 {
     #region Parts
  
    public const string ValidationSummaryElementName = "ValidationSummaryElement";
     public ValidationSummary ValidationSummaryElement { get; set; }
     public const string ContentPresenterElementName = "ContentPresenterElement";
     public ContentPresenter ContentPresenterElement { get; set; }
  
     #endregion
  
     public Form()
     {
         this.DefaultStyleKey = typeof(Form);
         this.Language = XmlLanguage.GetLanguage(CultureInfo.CurrentCulture.Name);
     }
     
     public override void OnApplyTemplate()
     {
         this.ValidationSummaryElement = this.GetTemplateChild(Form.ValidationSummaryElementName) as ValidationSummary;
         this.ContentPresenterElement = this.GetTemplateChild(Form.ContentPresenterElementName) as ContentPresenter;
         this.AttachTriggers();
         base.OnApplyTemplate();
     }    
 }

The initial skeleton of the Form is a normal templated ContentControl. In the constructor of the class I attach the DefaultStyleKey to connect the default template in the generic.xaml file to the control itself. I define two parts: the ContentPresenter and the ValidationSummary. These parts will be inspected or changed by the code of the control so I need to get a reference to them in the OnApplyTemplate override method.

Doing this make the control ready to be put in the page and it will behave as a normal ContentControl, with an Header at the top and a ValidationSummary at the bottom. Nothing strange to here, but let me introduce the next question.

Detecting commands

When we create a input Form, it is usual to have some button inside of it to let the user confirm or cancel the inputs. If we do not need to validate the input it may be done simply attaching the click event of the buttons. But to correctly handle the validation we need to let the Form scans the properties just before raise the a commit event. The toolkit's DataForm control behaves exactly this way. When you hit one of the command buttons it starts a scan of the binded properties to confirm that the object is valid and up to date. I will return on this topic in the second part of the article. For now we simply have to find a way to connect a Button (or every other control inside the form) to a commit or cancel action.

To accomplish this task I've created two attached properties. This kind of properties are very powerful because let the developer to add some informations to elements in the xaml. In my case I need to have a TriggerEvent and a TriggerCommand. TriggerEvent property lets me specify the event to attach on the element and the TriggerCommand property gives the command to raise (commit or cancel). Here is how to use the properties:

 <Button controls:Form.TriggerCommand="Commit" 
         controls:Form.TriggerEvent="Click" 
         Content="Add Person" Width="80" />

In the construction phase of the Form we need to scans the elements of the form to find them. Ideally the developer may add multiple command buttons, and many of them may raise some command, but other may do actions we must not to handle (think at a button that pops out a childwindow to search for an item).

Silverlight gives a useful class named VisualTreeHelper that helped me to scan the visual tree inside the ContentPresenter of the Form. Using this class you can detect child items so I've created an extension method that enumerate all the children recursively. This extension method enables linq queries on the visual tree.

 public static IEnumerable<T> Elements<T>(this T root)
     where T : DependencyObject
 {
     if (root != null)
     {
         Stack<T> elements = new Stack<T>();
         elements.Push(root);
  
         while (elements.Count > 0)
         {
             T element = elements.Pop();
  
             for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
             {
                 T childElement = VisualTreeHelper.GetChild(element, i) as T;
  
                 if (childElement != null)
                     elements.Push(childElement);
             }
  
             yield return element;
         }
     }
 }

The method uses a stack to avoid a recursive method that may be source of a stack overflow exception. So for each element in the visual tree I check if the attached property has a value then attach the event to a single handler in the Form class.

 private void AttachTriggers()
 {
     foreach (TriggerBinding bnd in this.DetectTriggers())
     {
         Type fweType = bnd.Element.GetType();
         EventInfo eventInfo = fweType.GetEvent(bnd.TriggerEvent);
         MethodInfo minfo = this.GetType().GetMethod("Command_Event", BindingFlags.NonPublic | BindingFlags.Instance);
         Delegate commandHandler = Delegate.CreateDelegate(eventInfo.EventHandlerType, this, minfo);
         eventInfo.AddEventHandler(bnd.Element, commandHandler);
     }
 }
   
 private IEnumerable<TriggerBinding> DetectTriggers()
 {
     FrameworkElement content = this.ContentPresenterElement.Content as FrameworkElement;
  
     if (content != null)
     {
         return (from element in content.Elements<FrameworkElement>()
                 where !string.IsNullOrEmpty(Form.GetTriggerEvent(element))
                 select new TriggerBinding(Form.GetTriggerEvent(element), Form.GetTriggerCommand(element), element));
     }
  
     return Enumerable.Empty<TriggerBinding>();
 }
  
 private void Command_Event(object sender, EventArgs e)
 {
     FrameworkElement fwe = sender as FrameworkElement;
  
     if (fwe != null)
     {
         string commandName = Form.GetTriggerCommand(fwe);
  
         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();
     }
 

AttachTriggers is called at the end of the OnApplyTemplate method to attach the event at the very beginning of the control lifetime. It scans the Attached Properties and uses reflection to take a reference to the event specified by the command and attaches it to the Command_Event method responsible to handle the events.

Then when an event is received I raise the right event of the form, only if Validation is confirmed, but this will be argument of the next part... In that occasion I will explain why we need to make some additional work to validate the input and I will show a working example of the complete form, with a fully functional downloadable sample.


Subscribe

Comments

  • -_-

    RE: A simplified DataForm replacement - Part 1 Create the control and detect commands


    posted by Fallon on Jan 22, 2010 04:38
    Will the code be added in part deux?
  • -_-

    RE: A simplified DataForm replacement - Part 1 Create the control and detect commands


    posted by Andrea Boschin on Jan 22, 2010 10:00
    yes it will be published when the article will be completed. bye
  • -_-

    RE: A simplified DataForm replacement - Part 1 Create the control and detect commands


    posted by Gareth on May 12, 2010 16:55
    Where's part 2?
  • iiordanov

    RE: A simplified DataForm replacement - Part 1 Create the control and detect commands


    posted by iiordanov on May 12, 2010 17:02

Add Comment

Login to comment:
  *      *       

From this series