具有依赖项属性的WPF ValidationRule

时间:2010-10-05 09:28:16

标签: wpf validation dependency-properties

假设您有一个继承自ValidationRule的类:

public class MyValidationRule : ValidationRule
{
    public string ValidationType { get; set; }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {}
}
在XAML中你是这样验证的:

<ComboBox.SelectedItem>
    <Binding Path="MyPath" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
        <Binding.ValidationRules>
            <qmvalidation:MyValidationRule  ValidationType="notnull"/>
        </Binding.ValidationRules>
    </Binding>
</ComboBox.SelectedItem>

哪个有效,一切正常。

但是现在假设您希望ValidationType="{Binding MyBinding}" MyBinding来自DataContext

为此,我需要将MyValidationRule作为DependencyObject并添加依赖属性

我尝试编写一个DependencyObject的类,然后绑定它。但是有两个问题.. ValidationRule没有来自Combobox / Item的DataContext

你有什么想法,如何解决?

谢谢!

1 个答案:

答案 0 :(得分:19)

由于ValidationRule未从DependencyObject继承,因此您无法在自定义验证类中创建DependecyProperty

但是如this link中所述,您可以在验证类中拥有一个普通属性,该属性属于从DependecyObject继承的类型,并在该类中创建DependencyProperty

例如,这是一个支持可绑定属性的自定义ValidationRule类:

[ContentProperty("ComparisonValue")]
public class GreaterThanValidationRule : ValidationRule
{
    public ComparisonValue ComparisonValue { get; set; }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        string s = value?.ToString();
        int number;

        if (!Int32.TryParse(s, out number))
        {
            return new ValidationResult(false, "Not a valid entry");
        }

        if (number <= ComparisonValue.Value)
        {
            return new ValidationResult(false, $"Number should be greater than {ComparisonValue}");
        }

        return ValidationResult.ValidResult;
    }
}

ComparisonValue是一个简单的类,它继承自DependencyObject并且有一个DependencyProperty

public class ComparisonValue : DependencyObject
{
    public int Value
    {
        get { return (int)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
        nameof(Value),
        typeof(int),
        typeof(ComparisonValue),
        new PropertyMetadata(default(int));

这解决了原始问题,但不幸的是又带来了两个问题:

  1. 绑定无法正常工作,因为ValidationRules不是可视树的一部分,因此无法正确获取绑定属性。例如,这种天真的方法是行不通的:

    <TextBox Name="TextBoxToValidate">
        <TextBox.Text>
            <Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <numbers:GreaterThanValidationRule>
                        <numbers:ComparisonValue Value="{Binding Text, ElementName=TextBoxToValidate}"/>
                    </numbers:GreaterThanValidationRule>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
    

    应该使用代理对象,如this answer:

    中所述
    <TextBox Name="TextBoxToValidate">
        <TextBox.Resources>
            <bindingExtensions:BindingProxy x:Key="TargetProxy" Data="{Binding Path=Text, ElementName=TextBoxToValidate}"/>
        </TextBox.Resources>
        <TextBox.Text>
            <Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
                <Binding.ValidationRules>
                    <numbers:GreaterThanValidationRule>
                        <numbers:ComparisonValue Value="{Binding Data, Source={StaticResource TargetProxy}}"/>
                    </numbers:GreaterThanValidationRule>
                </Binding.ValidationRules>
            </Binding>
        </TextBox.Text>
    </TextBox>
    

    BindingProxy是一个简单的类:

    public class BindingProxy : Freezable
    {
        protected override Freezable CreateInstanceCore()
        {
            return new BindingProxy();
        }
    
        public object Data
        {
            get { return GetValue(DataProperty); }
            set { SetValue(DataProperty, value); }
        }
        public static readonly DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
    
    1. 如果自定义ValidationRule中的属性绑定到另一个对象的属性,则当其他对象的属性发生更改时,不会触发原始属性的验证逻辑。

      要解决此问题,我们应该在更新ValidationRule的绑定属性时更新绑定。首先,我们应该将该属性绑定到我们的ComparisonValue类。然后,我们可以在Value属性更改时更新绑定源:

      public class ComparisonValue : DependencyObject
      {
          public int Value
          {
              get { return (int)GetValue(ValueProperty); }
              set { SetValue(ValueProperty, value); }
          }
          public static readonly DependencyProperty ValueProperty = DependencyProperty.Register(
              nameof(Value),
              typeof(int),
              typeof(ComparisonValue),
              new PropertyMetadata(default(int), OnValueChanged));
      
          private static void OnValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              ComparisonValue comparisonValue = (ComparisonValue) d;
              BindingExpressionBase bindingExpressionBase = BindingOperations.GetBindingExpressionBase(comparisonValue, BindingToTriggerProperty);
              bindingExpressionBase?.UpdateSource();
          }
      
          public object BindingToTrigger
          {
              get { return GetValue(BindingToTriggerProperty); }
              set { SetValue(BindingToTriggerProperty, value); }
          }
          public static readonly DependencyProperty BindingToTriggerProperty = DependencyProperty.Register(
              nameof(BindingToTrigger),
              typeof(object),
              typeof(ComparisonValue),
              new FrameworkPropertyMetadata(default(object), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
      }
      

      此处也存在第一种情况下的相同代理问题。因此,我们应该创建另一个代理对象:

      <ItemsControl Name="SomeCollection" ItemsSource="{Binding ViewModelCollectionSource}"/>
      
      <TextBox Name="TextBoxToValidate">
          <TextBox.Resources>
              <bindingExtensions:BindingProxy x:Key="TargetProxy" Data="{Binding Path=Items.Count, ElementName=SomeCollection}"/>
              <bindingExtensions:BindingProxy x:Key="SourceProxy" Data="{Binding Path=Text, ElementName=TextBoxToValidate, Mode=TwoWay}"/>
          </TextBox.Resources>
          <TextBox.Text>
              <Binding Path="ViewModelProperty" UpdateSourceTrigger="PropertyChanged">
                  <Binding.ValidationRules>
                      <numbers:GreaterThanValidationRule>
                          <numbers:ComparisonValue Value="{Binding Data, Source={StaticResource TargetProxy}}" BindingToTrigger="{Binding Data, Source={StaticResource SourceProxy}}"/>
                      </numbers:GreaterThanValidationRule>
                  </Binding.ValidationRules>
              </Binding>
          </TextBox.Text>
      </TextBox>
      

      在这种情况下,Text的{​​{1}}属性将根据TextBoxToValidate的{​​{1}}属性进行验证。当列表中的项目数发生变化时,将触发Items.Count属性的验证。