[英]WPF ValidationRule with dependency property
假設您有一個繼承自 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
類,並綁定它。 但是有 2 個問題...... ValidationRule
沒有來自 Combobox/Item 的DataContext
。
你有什么想法,關於如何解決這個問題?
由於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));
這解決了原來的問題,但不幸的是又帶來了兩個問題:
綁定無法正常工作,因為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>
相反,應按照此答案中的說明使用代理對象:
<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>
在這種情況下, Text
的屬性TextBoxToValidate
驗證對Items.Count
財產SomeCollection
。 當列表中的項目數量發生變化時,將觸發Text
屬性的驗證。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.