具有依赖属性的 WPF ValidationRule

发布于 2024-09-25 23:58:38 字数 1077 浏览 0 评论 0 原文

假设您有一个继承自 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 并添加 Dependency Property

我尝试编写一个 DependencyObject 类,并绑定它。但有两个问题...... ValidationRule 没有来自组合框/项目的 DataContext

您对如何解决这个问题有什么想法吗?

Suppose you have a class inheriting from ValidationRule:

public class MyValidationRule : ValidationRule
{
    public string ValidationType { get; set; }
    
    public override ValidationResult Validate(object value, CultureInfo cultureInfo) {}
}

In XAML you are validating like this:

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

Which works and everything is ok.

But suppose now, you want to have ValidationType="{Binding MyBinding}" where MyBinding comes from DataContext.

For this purpose I would need to make MyValidationRule as a DependencyObject and add a Dependency Property.

I've tried to write a class that is DependencyObject, and bind it. There are 2 problems though.. the ValidationRule DOES NOT have the DataContext from the Combobox / Item.

Do you have any ideas, on how to solve that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

春花秋月 2024-10-02 23:58:38

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

但是,正如 此链接中所述 您可以在验证类中拥有一个继承自 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不是可视化树的一部分,因此无法正确获取绑定属性。例如,这种幼稚的方法是行不通的:

    
        
            <绑定路径=“ViewModelProperty”UpdateSourceTrigger=“PropertyChanged”>
                
                    <数字:大于验证规则>
                        ;
                    
                
            
        
    
    

    相反,应该按照这个答案中的说明使用代理对象:

    
        
            
        
        
            <绑定路径=“ViewModelProperty”UpdateSourceTrigger=“PropertyChanged”>
                
                    <数字:大于验证规则>
                        
                    
                
            
        
    
    

    BindingProxy 是一个简单的类:

    公共类 BindingProxy :Freezable
    {
        protected 覆盖 Freezable CreateInstanceCore()
        {
            返回新的 BindingProxy();
        }
    
        公共对象数据
        {
            获取 { 返回 GetValue(DataProperty); }
            设置 { SetValue(DataProperty, 值); }
        }
        公共静态只读 DependencyProperty DataProperty = DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
    }
    

  1. 如果自定义 ValidationRule 中的属性绑定到另一个对象的属性,则当其他对象的属性更改时,将不会触发原始属性的验证逻辑。< /p>

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

    公共类 ComparisonValue : DependencyObject
    {
        公共整型值
        {
            获取 { 返回 (int)GetValue(ValueProperty); }
            设置 { SetValue(ValueProperty, 值); }
        }
        公共静态只读 DependencyProperty ValueProperty = DependencyProperty.Register(
            名称(值),
            类型(int),
            typeof(比较值),
            新的 PropertyMetadata(default(int), OnValueChanged));
    
        私有静态无效OnValueChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
        {
            比较值 比较值 = (比较值) d;
            BindingExpressionBase bindingExpressionBase = BindingOperations.GetBindingExpressionBase(comparisonValue, BindingToTriggerProperty);
            绑定ExpressionBase?.UpdateSource();
        }
    
        公共对象 BindingToTrigger
        {
            获取 { 返回 GetValue(BindingToTriggerProperty); }
            设置 { SetValue(BindingToTriggerProperty, 值); }
        }
        公共静态只读 DependencyProperty BindingToTriggerProperty = DependencyProperty.Register(
            名称(绑定到触发器),
            类型(对象),
            typeof(比较值),
            新 FrameworkPropertyMetadata(默认(对象), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault));
    }
    

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

    ;
    
    
        
            ;
            
        
        
            <绑定路径=“ViewModelProperty”UpdateSourceTrigger=“PropertyChanged”>
                
                    <数字:大于验证规则>
                        
                    
                
            
        
    
    

    在本例中,TextBoxToValidateText 属性根据 SomeCollectionItems.Count 属性进行验证。当列表中的项目数发生变化时,将触发 Text 属性的验证。

Since ValidationRule does not inherit from DependencyObject you cannot create a DependecyProperty in your custom validation class.

However as explained in this link you can have a normal property in your validation class which is of a type that inherits from DependecyObject and create a DependencyProperty in that class.

For example here is a custom ValidationRule class that support bindable property:

[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 is a simple class that inherits from DependencyObject and has a 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));

This solves the original problem but unfortunately brings two more problems:

  1. The binding does not work correctly since the ValidationRules is not part of visual tree and therefore cannot get the bound property correctly. For example this naive approach will not work:

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

    Instead a proxy object should be used as explained in 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 is a simple class:

    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. If the property in custom ValidationRule is bound to another object's property, the validation logic for the original property will not fire when that other object's property changes.

    To solve this problem we should update the binding when the ValidationRule's bound property is updated. First we should bind that property to our ComparisonValue class. Then, we can update the source of the binding when the Value property changes:

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

    The same proxy problem in the first case also exists here. Therefore we should create another proxy object:

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

    In this case the Text property of TextBoxToValidate is validated against the Items.Count property of SomeCollection. When the number of items in the list changes, the validation for the Text property will be triggered.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文