问题
I have a complicated scenario using ValidationRules that I need some help with. I have a User Control that is roughly organized like this:
Parent (ItemsControl)
Child 1
Property 1
Property 2
Child 2
Property 1
Property 2
When Child 1.Property 1 is changed, I need to perform validation on it. However, the validation rule requires the value of Child 1.Property 1 as well as the Property 1 values for all its siblings (variable number) to perform the validation. I could put a ValidationRule on the Parent ItemsControl, but I need the Control bound to Child1.Property1 to show the error. Currently when I place the validation on the parent the error is displayed on the parent, not the child. I've also considered using BindingGroups but I want the validation to fire automatically when a property is changed. There isn't, to my knowledge, a way to automatically force Validation to fire for a BindingGroup.
Is there a way to accomplish what I am trying to do?
回答1:
This automatically turns all the object's data annotation attributes into ValidationRule
s, and can be applied once in the app on all TextBox
es:
using System;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using DaValidationResult = System.ComponentModel.DataAnnotations.ValidationResult;
using WinValidationResult = System.Windows.Controls.ValidationResult;
public sealed class DataAnnotationsBehavior
{
public static bool GetValidateDataAnnotations(DependencyObject obj) =>
(bool)obj.GetValue(ValidateDataAnnotationsProperty);
public static void SetValidateDataAnnotations(DependencyObject obj, bool value) =>
obj.SetValue(ValidateDataAnnotationsProperty, value);
public static readonly DependencyProperty ValidateDataAnnotationsProperty =
DependencyProperty.RegisterAttached("ValidateDataAnnotations", typeof(bool),
typeof(DataAnnotationsBehavior), new PropertyMetadata(false, OnPropertyChanged));
private static void OnPropertyChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
var boolean = (bool)e.NewValue;
if (!(d is TextBox textBox))
throw new NotSupportedException(
@$"The behavior " +
"'{typeof(DataAnnotationsBehavior)}' can only be applied " +
"on elements of type '{typeof(TextBox)}'.");
var bindingExpression =
textBox.GetBindingExpression(TextBox.TextProperty);
if (boolean)
{
var dataItem = bindingExpression.DataItem;
if (bindingExpression.DataItem == null)
return;
var type = dataItem.GetType();
var prop = type.GetProperty(bindingExpression.ResolvedSourcePropertyName);
if (prop == null)
return;
var allAttributes = prop.GetCustomAttributes(typeof(ValidationAttribute), true);
foreach (var validationAttr in allAttributes.OfType<ValidationAttribute>())
{
var context = new ValidationContext(dataItem, null, null)
{ MemberName = bindingExpression.ResolvedSourcePropertyName };
bindingExpression
.ParentBinding
.ValidationRules
.Add(new AttributesValidationRule(context, validationAttr));
}
}
else
{
var das =
bindingExpression
.ParentBinding
.ValidationRules
.OfType<AttributesValidationRule>()
.ToList();
if (das != null)
foreach (var da in das)
bindingExpression.ParentBinding.ValidationRules.Remove(da);
}
}
abstract class DaValidationRule : ValidationRule
{
public ValidationContext ValidationContext { get; }
public DaValidationRule(ValidationContext validationContext)
{
ValidationContext = validationContext;
}
}
class AttributesValidationRule : DaValidationRule
{
public ValidationAttribute ValidationAttribute { get; }
public AttributesValidationRule(ValidationContext validationContext,
ValidationAttribute attribute)
: base(validationContext) =>
ValidationAttribute = attribute;
public override WinValidationResult Validate(object value, CultureInfo cultureInfo)
{
var result = ValidationAttribute.GetValidationResult(value, ValidationContext);
return result == DaValidationResult.Success
? WinValidationResult.ValidResult
: new WinValidationResult(false, result.ErrorMessage);
}
}
}
Usage:
<Window.DataContext>
<local:ViewModel />
</Window.DataContext>
<Window.Resources>
<ResourceDictionary>
<Style TargetType="TextBox">
<Setter
Property="local:DataAnnotationsBehavior.ValidateDataAnnotations"
Value="True"/>
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter
Property="ToolTip"
Value="{Binding (Validation.Errors)[0].ErrorContent,
RelativeSource={RelativeSource Self}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ResourceDictionary>
</Window.Resources>
<StackPanel DataContext="{Binding Model}">
<TextBox Text="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
I made the DaValidationRule
class abstract because I think I might want to expand that in the future so it also covers IValidationAttribute
and maybe other scenarios.
For the version that covers TextBox
es within DataGridTextColumn
in edit mode, and for full code, see this.
来源:https://stackoverflow.com/questions/11802872/parent-child-validationrule