2010-10-05 13 views
13

Supponiamo di avere una classe che eredita da ValidationRule:WPF ValidationRule con proprietà di dipendenza

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

    public override ValidationResult Validate(object value, CultureInfo cultureInfo) 
    {} 
} 

in XAML state convalidando così:

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

che lavora e tutto è ok.

Ma supponiamo ora, si desidera avere ValidationType="{Binding MyBinding}" dove MyBinding viene da DataContext.

A questo scopo avrei bisogno di fare MyValidationRule come DependencyObject e aggiungere una proprietà di dipendenza .

Ho provato a scrivere una classe che è DependencyObject e collegarla. Ci sono 2 problemi però .. il ValidationRule non ha il DataContext dalla casella combinata/Item.

Avete qualche idea, come risolvere questo?

Grazie!

risposta

14

Poiché ValidationRule non eredita da DependencyObject non è possibile creare un DependecyProperty nella classe di convalida personalizzata.

Tuttavia, come spiegato in this link si può avere una proprietà normale nella classe di convalida, che è di un tipo che eredita da DependecyObject e creare un DependencyProperty in quella classe.

Per esempio ecco un ValidationRule classe personalizzata che supportano proprietà associabile:

[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 è una semplice classe che eredita da DependencyObject e ha un 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)); 

questo risolve il problema originale, ma purtroppo porta altri due problemi:

  1. L'associazione non funziona correttamente poiché lo ValidationRules non fa parte dell'albero visivo e pertanto non può ottenere correttamente la proprietà associata.Per esempio, questo approccio naive non funzionerà:

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

    Invece un oggetto proxy dovrebbe essere usato come spiegato in this risposta:

    <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 è una semplice classe:

    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. Se la proprietà in custom ValidationRule è associata alla proprietà di un altro oggetto, la logica di convalida per la proprietà originale non verrà attivata quando la proprietà di quell'altro oggetto cambia.

    Per risolvere questo problema, è necessario aggiornare il binding quando la proprietà associata di viene aggiornata. Per prima cosa dovremmo legare quella proprietà alla nostra classe ComparisonValue. Poi, possiamo aggiornare la fonte del legame quando cambia Value di proprietà:

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

    Lo stesso problema proxy nel primo caso esiste anche qui. Quindi dovremmo creare un altro oggetto proxy:

    <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 questo caso la proprietà di TextBoxToValidateText viene convalidato contro il Items.Count proprietà di SomeCollection. Quando viene modificato il numero di elementi nell'elenco, verrà avviata la convalida della proprietà Text.

Problemi correlati