具有依赖项属性的WPF ValidationRule [英] WPF ValidationRule with dependency property
问题描述
假设您有一个从ValidationRule继承的类:
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) {}
}
在XAML中,您正在像这样进行验证:
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.
但是现在假设,您想具有 ValidationType = {{Binding MyBinding}
,其中 MyBinding
来自 DataContext
。
But suppose now, you want to have ValidationType="{Binding MyBinding}"
where MyBinding
comes from DataContext
.
为此,我需要将 MyValidationRule
用作 DependencyObject
并添加 Dependency属性。
For this purpose I would need to make MyValidationRule
as a DependencyObject
and add a Dependency Property.
我尝试编写一个 DependencyObject $ c的类$ c>,并将其绑定。但是有2个问题..
ValidationRule
没有来自组合框/项的 DataContext
。
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?
推荐答案
由于 ValidationRule
不能从 DependencyObject
继承,您不能在自定义验证类中创建 DependecyProperty
Since ValidationRule
does not inherit from DependencyObject
you cannot create a DependecyProperty
in your custom validation class.
但是,如此链接您可以在验证类中具有普通属性,该属性的类型继承自 DependecyObject
并在该类中创建 DependencyProperty
。
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.
例如,这是自定义的支持可绑定属性的ValidationRule
类:
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;
}
}
比较值
是一个简单的类,继承自 DependencyObject
并具有 DependencyProperty
:
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:
-
由于
ValidationRules
不是可视树的一部分,因此无法正确获取绑定的属性,例如,这种幼稚的方法将不起作用:
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
是一个简单的类:
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
中的属性绑定到另一个对象的属性,则验证
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.
为解决此问题,我们应在 ValidationRule 时更新绑定。 code>的绑定属性已更新。首先,我们应该将该属性绑定到我们的
ComparisonValue
类。然后,当 Value
属性更改时,我们可以更新绑定的来源:
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>
在这种情况下, Text
属性 TextBoxToValidate
已针对 SomeCollection
的 Items.Count
属性进行了验证。当列表中的项目数更改时,将触发对 Text
属性的验证。
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.
这篇关于具有依赖项属性的WPF ValidationRule的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!