Parent Child ValidationRule

一曲冷凌霜 提交于 2020-01-05 07:04:15

问题


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 ValidationRules, and can be applied once in the app on all TextBoxes:

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 TextBoxes within DataGridTextColumn in edit mode, and for full code, see this.



来源:https://stackoverflow.com/questions/11802872/parent-child-validationrule

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!