具有依赖属性的 WPF ValidationRule

Posted

技术标签:

【中文标题】具有依赖属性的 WPF ValidationRule【英文标题】:WPF ValidationRule with dependency property 【发布时间】:2011-04-21 05:09:31 【问题描述】:

假设你有一个继承自 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 的类,并绑定它。虽然有 2 个问题.. ValidationRule 没有来自组合框/项目的 DataContext

你有什么想法,如何解决这个问题?

【问题讨论】:

【参考方案1】:

由于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));

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

    绑定无法正常工作,因为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 回答中所述:

    <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));
    
    

    如果自定义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>
    

    在这种情况下,TextBoxToValidateText 属性将根据SomeCollectionItems.Count 属性进行验证。当列表中的项目数发生变化时,将触发对Text 属性的验证。

【讨论】:

感谢您提供这个非常有用的答案。

以上是关于具有依赖属性的 WPF ValidationRule的主要内容,如果未能解决你的问题,请参考以下文章

WPF 依赖属性概念

WPF--依赖属性

WPF--依赖属性

WPF入门教程系列十一——依赖属性

WPF学习第十一章 理解依赖项属性

Wpf:通用集合依赖属性