簡體   English   中英

具有依賴屬性的 WPF ValidationRule

[英]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));

這解決了原來的問題,但不幸的是又帶來了兩個問題:

  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>

    相反,應按照答案中的說明使用代理對象:

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

  1. 如果自定義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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM