There are several ways to validate in WPF.
I personally prefer reusable custom validation rules and I like to inherit from Binding
instead of having to repeatedly write all those small things (like UpdateSourceTrigger
) in xaml or use bloated full tag syntax to add validation rules.
You want to have a property to which you can bind custom validation error text. One possibility is to use attached property to which you can bind and which is then used by validation rule to return the error. We can encapsulate everything into custom binding class:
public class Bind : Binding
{
// validation rule
class Rule : ValidationRule
{
public Rule() : base(ValidationStep.RawProposedValue, true) { }
public override ValidationResult Validate(object value, CultureInfo cultureInfo) => ValidationResult.ValidResult;
public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
{
if (!string.IsNullOrEmpty((string)value))
return new ValidationResult(false, GetError(owner.Target));
return base.Validate(value, cultureInfo, owner);
}
}
// attached property to hold error text
public static string GetError(DependencyObject obj) => (string)obj.GetValue(ErrorProperty);
public static void SetError(DependencyObject obj, string value) => obj.SetValue(ErrorProperty, value);
public static readonly DependencyProperty ErrorProperty = DependencyProperty.RegisterAttached("Error", typeof(string), typeof(Bind));
// custom binding
public Bind() : base() => Init();
public Bind(string path) : base(path) => Init();
void Init()
{
UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
ValidationRules.Add(new Rule());
}
}
Usage in xaml:
<TextBox Text="{local:Bind FirstName}" local:Bind.Error="{Binding FirstNameError}" />
You have to name Bind
class more properly to distinguish from other bindings if you decide to follow my preferences.
Few words about implementation. Notice validation rule constructor, it's needed to specify when we want to get control to validate. In this case: when raw value is changed in the view. Another thing is what we need to access binding target to retrieve attached property, this is why overload with BindingExpression
is used and another one (required when inheriting from ValidationRule
) simply returns.
red boarder and tool tip preferred
Default Validation.ErrorTemplate
will provide red border and you can add tooltip easily:
ToolTip="{Binding RelativeSource={RelativeSource self}, Path=(Validation.Errors)[0].ErrorContent}"