When I first display my screen to the user, I\'d rather not have all the validation messages show up for required fields and such before the user has had a chance to fill in
Just to indicate how I handled this utilising IDataErrorInfo
...
I put a call to a new method called OnDataUpdated()
in each setter of my view-bound property, such as:
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></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. Now I only have validation styles appearing after a user edits 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.
You can get better answers if you make an effort to post a snippet of your relevant code/XAML. It would make it easier to reproduce and eliminate much of the guesswork.
Try setting ValidatesOnTargetUpdated="False"
on your validation rules and see if that helps.
I don't put validation logic in the indexer. That turns control over the timing of validation to the view. In your scheme, the view triggers validation whenever it asks for the property's error info. I don't know every circumstance in which that's going to happen and I bet you don't either.
Instead, I put a property's validation logic (more accurately, the call to the validation function) in its setter. I store the error message in a dictionary keyed on property name, and have the indexer look up the error message for the property.
By default, then, the error message is up to date with the property's current value. Whenever the view updates a property and requests its new error info, it'll get the right answer.
But you also have pretty fine-grained control over what's actually in that dictionary. If you want a property to show up in the UI as valid, just clear its error message in the dictionary (and raise PropertyChanged
, so the UI will know to get the new error message). Or you can set the properties' backing fields instead of the properties themselves, bypassing validation, when you construct the view model object.