具有依赖项属性的WPF ValidationRule

PaN*_*1Me 16 validation wpf dependency-properties

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

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

    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {}
}
Run Code Online (Sandbox Code Playgroud)

在XAML中,您正在验证如下:

<ComboBox.SelectedItem>
    <Binding Path="MyPath" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
        <Binding.ValidationRules>
            <qmvalidation:MyValidationRule  ValidationType="notnull"/>
        </Binding.ValidationRules>
    </Binding>
</ComboBox.SelectedItem>
Run Code Online (Sandbox Code Playgroud)

哪个有效,一切都好.

但是现在假设,你想拥有ValidationType="{Binding MyBinding}"它的MyBinding来源DataContext.

为此,我需要创建MyValidationRule一个DependencyObject并添加一个依赖属性.

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

你有什么想法,怎么解决?

谢谢 !

Yus*_*dın 20

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

但是,如此链接中所述,您可以在验证类中具有普通属性,该属性是从该类继承DependecyObjectDependencyProperty在该类中创建的类型.

例如,这是一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

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));
Run Code Online (Sandbox Code Playgroud)

这解决了原始问题,但遗憾的是还带来了两个问题:

  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>
    
    Run Code Online (Sandbox Code Playgroud)

    相反,应该使用代理对象,如答案中所述:

    <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>
    
    Run Code Online (Sandbox Code Playgroud)

    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));
    }
    
    Run Code Online (Sandbox Code Playgroud)

  1. 如果custom中的属性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));
    }
    
    Run Code Online (Sandbox Code Playgroud)

    第一种情况下的代理问题也存在于此.因此我们应该创建另一个代理对象:

    <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>
    
    Run Code Online (Sandbox Code Playgroud)

    在这种情况下,Text属性TextBoxToValidate是根据Items.Count属性验证的SomeCollection.当列表中的项目数更改时,Text将触发属性的验证.