Regular readers may observe that my interest in Silverlight is primarily in making business applications and Visual Tools. I am not an artist and I don't stream music or movies. No, I'd just like to see Silverlight replace HTML, CSS, and Javascript. There are a few issues with this plan right now (other than wish thinking) - binding ought to be closer to what WPF has. There are some handy controls missing.
There's no built in Validation strategy.
Obviously nothing stops me from doing validation in a completely manual fashion but it's unlike Microsoft to leave holes like this...
Strategy
We have Extension Methods, but not Extension Properties or Extension Events, so simply adding Validating event handlers isn't an option. What's more, the typical Windows Forms strategy requires an Extender Provider framework - perhaps when I get further on the AGT Project I'll revisit this, but for now something quicker to implement would be better.
For some reason the ASP.Net model was intriguing to me given the current state of Silverlight.
- The Validators have a visual representation and function essentially as a Control.
- The Validators "point to" the Control they will validate, so they are completely outside the rest of the functionality on the Page so to speak.
- Making sure to trigger Validation via code like if(Page.IsValid) { /* do something*/} is a fair enough way to write code.
So, could we create something like this, but in a reasonably Silverlight-y way? Read on.
Test Form and three sample validation scenarios
I have created a very simple Wine Order Form:
I have some simple data requirements for this order form.
- For Name, I'd like something similar to the Asp.Net RequiredFieldValidator
- For Email, I'd like a RegexValidator
- For Bottles of Wine I'd like a RangeValidator
Design Goals
So, how would I like this to work and what should the programming experience be?
- I'd like some sort of Control in the Visual Tree representing the Validation state of an associated Control; it should be easy to Template
- I do not want to require any interface implementation, base classes, or any changes to the Visual Tree besides the addition of the Validation visuals. (ie no <v:ValidationArea>... your XAML here </v:ValidationArea>, although this is a common enough model in Silverlight and WPF)
- Calling an arbitrary # of Validators on any given Control or Panel should require as little code as possible
- Adding new Validator types should be as easy as possible.
- It should feel like the Validators have saved programming time and created consistency versus pure manual validation.
Implementation
How did I do on these goals? Let's see.
Validator Control Class
As I stated, I've taken the approach of creating a Control which will live in the Silverlight Visual Tree. Any inheritance involving XAML & User Controls is painful or impossible, but I wanted to see about directly inheriting from Control and using a ControlTemplate instead of working at any kind of visual inheritance. I'm happy with the results.
using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
namespace DamonPayne.AGContrib.Validation
{
[TemplatePart(Name = "LayoutRoot", Type = typeof(Panel))]
public class Validator : Control
{
public Validator()
{
DefaultStyleKey = typeof(Validator);
Loaded += new RoutedEventHandler(Validator_Loaded);
}
private Panel _layoutRoot;
void Validator_Loaded(object sender, RoutedEventArgs e)
{
CheckValidState(this);
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_layoutRoot = (Panel)GetTemplateChild("LayoutRoot");
}
/// <summary>
/// A single Framework Element to display when Valid, perhaps an empty TextBlock?
/// </summary>
public FrameworkElement ValidContent
{
get { return (FrameworkElement)GetValue(ValidContentProperty); }
set { SetValue(ValidContentProperty, value); }
}
// Using a DependencyProperty as the backing store for ValidContent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValidContentProperty =
DependencyProperty.Register("ValidContent", typeof(FrameworkElement), typeof(Validator), new PropertyMetadata(new TextBlock()));
public FrameworkElement InValidContent
{
get { return (FrameworkElement)GetValue(InValidContentProperty); }
set { SetValue(InValidContentProperty, value); }
}
//Inheritance, does NOT like sharing visual elements, very sneaky
public static readonly DependencyProperty InValidContentProperty =
DependencyProperty.Register("InValidContent", typeof(FrameworkElement),
typeof(Validator),
new PropertyMetadata(new TextBlock { Text="*", Foreground=new SolidColorBrush(Colors.Red) }));
/// <summary>
/// Tool tip content, possibly explaining more information about why the value is invalid
/// </summary>
public FrameworkElement InvalidToolTip
{
get { return (FrameworkElement)GetValue(InvalidToolTipProperty); }
set { SetValue(InvalidToolTipProperty, value); }
}
// Using a DependencyProperty as the backing store for InvalidToolTip. This enables animation, styling, binding, etc...
public static readonly DependencyProperty InvalidToolTipProperty =
DependencyProperty.Register("InvalidToolTip", typeof(FrameworkElement), typeof(Validator), new PropertyMetadata(null));
/// <summary>
/// Is the target Control currently valid
/// </summary>
public bool IsValid
{
get { return (bool)GetValue(IsValidProperty); }
set { SetValue(IsValidProperty, value); }
}
// Using a DependencyProperty as the backing store for IsValid. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsValidProperty =
DependencyProperty.Register("IsValid", typeof(bool), typeof(Validator), new PropertyMetadata(true, IsValidChanged));
public static void IsValidChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is Validator)
{
var v = (Validator)obj;
CheckValidState(v);
}
}
/// <summary>
/// show/hide the valid/invalid FrameworkElement
/// </summary>
/// <param name="v"></param>
private static void CheckValidState(Validator v)
{
if (null != v.ValidContent && null != v.InValidContent)
{
if (v.IsValid)
{
v.ValidContent.Visibility = Visibility.Visible;
v.InValidContent.Visibility = Visibility.Collapsed;
}
else
{
v.ValidContent.Visibility = Visibility.Collapsed;
v.InValidContent.Visibility = Visibility.Visible;
}
}
}
/// <summary>
/// The name of the Control within the visual tree we should try to validate
/// </summary>
public string ControlToValidate
{
get { return (string)GetValue(ControlToValidateProperty); }
set { SetValue(ControlToValidateProperty, value); }
}
// Using a DependencyProperty as the backing store for ControlToValidate. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ControlToValidateProperty =
DependencyProperty.Register("ControlToValidate", typeof(string), typeof(Validator), new PropertyMetadata(null));
/// <summary>
/// When overridden in a derrived class, checks for a valid value
/// in <paramref name="ctv"/>
/// </summary>
/// <param name="ctv"></param>
/// <returns></returns>
public virtual bool Validate(FrameworkElement ctv)
{
return true;
}
/// <summary>
/// Helper method for derived classes
/// </summary>
/// <param name="fe"></param>
/// <returns></returns>
protected virtual string GetInputValue(FrameworkElement fe)
{
string val = string.Empty;
if (fe is TextBox)
{
val = ((TextBox)fe).Text;
} //else if ComboBox, etc.
return val;
}
}
}
We define three dependency properties for showing content, presumably using a ContentPresenter in the default template for Validator. ValidContent, InValidContent, and InvalidToolTip. We leave the Validate function empty, and the work of showing or hiding the correct content happens in CheckValidState.
Default Validator ControlTemplate
We target Validator with this ControlTemplate:
<ControlTemplate TargetType="local:Validator">
<Canvas x:Name="LayoutRoot">
<ContentPresenter x:Name="ValidPresenter" Content="{TemplateBinding ValidContent}"></ContentPresenter>
<ContentPresenter x:Name="InValidPresenter" Content="{TemplateBinding InValidContent}">
<ToolTipService.ToolTip>
<ContentPresenter x:Name="InvalidToolTipPresenter" Content="{TemplateBinding InvalidToolTip}"></ContentPresenter>
</ToolTipService.ToolTip>
</ContentPresenter>
</Canvas>
</ControlTemplate>
This should be easy to understand. Since this is an early experiment I haven't created any Visual States yet.
Validator Implementations
Surprisingly, creating classes that extend Validator worked out fairly well. We can review the main pitfall in the ValidContentProperty Dependency Property registration. Note the default value inside the PropertyMetadata constructor. I left this in to make a point - only one instance of this TextBlock will be created, and a single Control cannot appear multiple times in the Visual Tree. Be sure to provide values in each instance or you will get hard to grok errors. For the sake of brevity, we can just review the implementation for RegexValidator:
using System.Text.RegularExpressions;
using System.Windows;
namespace DamonPayne.AGContrib.Validation
{
public class RegexValidator : Validator
{
public RegexValidator()
{
DefaultStyleKey = typeof(Validator);
}
public string RegularExpression
{
get { return (string)GetValue(RegularExpressionProperty); }
set { SetValue(RegularExpressionProperty, value); }
}
// Using a DependencyProperty as the backing store for RegularExpression. This enables animation, styling, binding, etc...
public static readonly DependencyProperty RegularExpressionProperty =
DependencyProperty.Register("RegularExpression", typeof(string), typeof(RegexValidator), new PropertyMetadata(string.Empty));
public override bool Validate(FrameworkElement ctv)
{
if (!string.IsNullOrEmpty(RegularExpression))
{
Regex r = new Regex(RegularExpression);
string text = GetInputValue(ctv);
return r.IsMatch(text);
}
return true;
}
}
}
There are a couple of things to note here. Because this class is simply adding some logic we use DefaultStyleKey = typeof(Validator); in the constructor. We could conceivably want different Validators to have utterly different visual representations, but in those cases we can create a ControlTemplate for those instances only. Surprisingly this works just fine. We've defined a RegularExpression property we can set from XAML and overridden Validate to do the Regex check.
Adding the Validators in XAML
Given all of this, it's easy to add the Validators onto my order form:
<!-- Name Row-->
<TextBlock Grid.Row="1">Name:</TextBlock>
<TextBox x:Name="NameTxt" Grid.Row="1" Grid.Column="1" Height="25" VerticalAlignment="Top"></TextBox>
<v:RequiredFieldValidator x:Name="NameReq" ControlToValidate="NameTxt" Grid.Column="2" IsValid="True">
</v:RequiredFieldValidator>
Obviously, the value of ControlToValidate must be a valid Name somewhere in the visual tree. Because we allow the display of anything for valid vs. invalid Controls, and we can set the initial valid state my order form now looks like this when first loaded, calling out the fact that Wine Bottles needs a real value:
Firing the Validators
Given the primitive state of the example application so far, a single extension method for type FrameworkElement will cause all of the validators contained within that FrameworkElement to do their thing. The extension code is worth taking a quick look at.
namespace DamonPayne.AGContrib.Validation
{
public static class FrameworkElementExtensions
{
/// <summary>
/// Search the control's Visual Tree for Validators, and run them!
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
public static bool IsValid(this FrameworkElement c)
{
bool valid = true;
var lv = FindAllValidators(c);
foreach (var v in lv)
{
var ctv = (Control)c.FindName(v.ControlToValidate);
if (null != ctv)
{
if (!v.Validate(ctv))
{
valid = false;
v.IsValid = false;
}
else
{
v.IsValid = true;
}
}
}
return valid;
}
public static List<Validator> FindAllValidators(FrameworkElement fe)
{
return FindAllValidators(fe, new List<Validator>());
}
public static List<Validator> FindAllValidators(FrameworkElement fe, List<Validator> lst)
{
int count = VisualTreeHelper.GetChildrenCount(fe);
for (int i = 0; i < count; ++i)
{
DependencyObject d = VisualTreeHelper.GetChild(fe, i);
if (d is Validator)
{
lst.Add((Validator)d);
}
if (d is FrameworkElement)
{
FindAllValidators((FrameworkElement)d, lst);
}
}
return lst;
}
}
}
You may note that only certain elements such as Canvas define a "Children" property. VisualTreeHelper is the only way I know of to walk the Visual Tree in a generic fashion. This allows us to look for Validators within the target FrameworkElement no matter how nested. I will revisit my choice of FrameworkElement as the entry point. With this Extension Method, validating my form when I attempt to submit is easy, "Form" being the name of my top level Grid.
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Form.IsValid())
{
//Do something meaninful
}
}
So, if we push the Submit button without doing anything, we see this ...
... but providing valid values ...
Conclusion
So, what do you think? Is this worth a little more work? I would probably add some kind of Validation Summary, some Visual States to Validator, potentially support Validation Groups, and add a way to dynamically fire the Validators when a Control loses focus.
Send me a note with your comments.
Source code:
DamonPayne.AGContrib.zip (44.75 KB)