Hi Guys,
Well after the last few months of building a commercial WPF app (and learning a lot along the way!) I’ve been generally delighted with the experience. A few things I feel could be a lot more polished, or a lot more thought out, but thems the breaks and we can only hope to see some improvements by Visual Studio 2012.
One of the trickiest and sadly, largest shortcoming of WPF I found was a trivial way to perform WPF form validation. It can get very complicated.
So I thought I might just post quickly some strategies I have implemented over the last few days and hopefully you guys can find some value in it and won’t need to go hunting yourselves.
I mainly illustrate a simple Username/Password TextBox Login validation scenario and to understand a bit more on how to set that up with the PasswordBoxAssistant class see the original post on this StackOverflow.com article: http://stackoverflow.com/questions/8942259/error-handling-in-wpf-passwordbox
My approach uses the standard IDataErrorInfo class.
Firstly, I put a call to a new method called OnDataUpdated() in each setter of my view-bound property, for instance:
private string username;
public string Username
{
get { return username; }
set
{
username = value;
OnDataUpdated();
}
}
private string password;
public string Password
{
get { return password; }
set
{
password = value;
OnDataUpdated();
}
}
Then inside OnDataUpdated() mark a private field boolean as true indicating data has changed for the first time (FormType was only necessary for my business case):
private void OnDataUpdated()
{
dataChanged = true;
// .. Any other universal RaisePropertyChanged() events you may want to call to get your UI into sync. Eg. RaisePropertyChanged(() => CanConfirm);
}
Then in my IDataErrorInfo indexer property I do the following (I split it out so ValidForm() can be called manually to perform form validation too.
public string this[string columnName]
{
get
{
string result = null;
if (columnName == "Username")
{
// If other payment amounts have fully paid for balance, and cash amount has been entered, deny
if (!ValidForm(FormType.Step1, columnName))
result = "Please enter the username field.";
}
else if (columnName == "Password")
{
if (!ValidForm(FormType.Step1, columnName))
result = "Please enter the password field.";
}
return result;
}
}
/// summary>
/// Test if valid form.
/// /summary>
/// param name="formType">Specify which form we should validate./param>
/// param name="columnName">If ommitted, entire form will be validated./param>
/// returns>
private bool ValidForm(FormType formType, string columnName = null)
{
// This field is used to denote when data has changed on the form.
// If data has changed, we know we can activate any form validation.
// We do not activate the form validation until after a user has typed
// something in at least.
if (!dataChanged) return true;
var errors = false;
if (formType == FormType.Step1 && ((string.IsNullOrEmpty(columnName) || columnName == "Username") && string.IsNullOrEmpty(Username)))
errors = true;
if (formType == FormType.Step1 && ((string.IsNullOrEmpty(columnName) || columnName == "Password") && string.IsNullOrEmpty(Password)))
errors = true;
return !errors;
}
Works beautifully. Validation occuring great and most importantly bridges the gap on a large shortcoming — validation styles ONLY appear once the user commences editing the form.
If you want some extra icing on the cake, you can comment in my RaisePropertyChanged(() => CanConfirm); in the OnDataUpdated() method and bind that to your Confirm Button IsEnabled={Binding CanConfirm} with the associated property:
/// summary>
/// Can the user confirm step 1?
/// /summary>
public bool CanConfirm
{
get { return ValidForm(FormType.Step1); }
}
and your button will only be enabled when your form is valid too.
Enjoy! and best of luck with the behemoth that is WPF.
I don’t have time right now, but I can put a working sample project up on request; just leave me a comment.
3 Comments
I want to suggest you to use children of the System.ComponentModel.DataAnnotations.ValidationAttribute.
So your code will look something like:
[RequiredAttribute(ErrorMessage ="Please enter the username field.")]
public string Username{…..}
private string username;
public virtual string this[string columnName]
{
get
{
return dataChanged ? AttributesValidation.Validate(this, columnName):string.Empty; }
}
where AttributesValidation is a static class showed here: http://pastebin.com/73yCBnzK
Now you can simply put any attributes you want without tons of if statements.
Hello Graham !
Your post is great !
We are from Brazil. We are about to migrate our applications to WPF, and we have some doubts about that powerfull tool. If you could contribute with your working sample, we’ll be greatful.
Feel free to contact us anytime…
Thank you !
Ok I will try and do this tonight for you Eduardo.
Glad the post was helpful!
Post a Comment