2010-10-05 18 views
13

Suponga que tiene una clase que hereda de ReglaDeValidación:WPF ReglaDeValidación con propiedad de dependencia

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

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

en XAML está validando así:

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

Qué funciona y todo está bien.

Pero supongamos que ahora quiere tener ValidationType="{Binding MyBinding}" donde MyBinding viene de DataContext.

Para ello necesitaría para hacer MyValidationRule como DependencyObject y añadir una propiedad de dependencia.

He intentado escribir una clase que es DependencyObject, y enlazarlo. Sin embargo, hay 2 problemas ... el ValidationRule NO tiene el DataContext del cuadro combinado/artículo.

¿Tiene alguna idea, cómo solucionar eso?

¡Gracias!

Respuesta

14

Dado que ValidationRule no hereda de DependencyObject no puede crear un DependecyProperty en su clase de validación personalizada.

Sin embargo, como se explica en this link puede tener una propiedad normal en su clase de validación, que es de un tipo que hereda de DependecyObject y crear un DependencyProperty en esa clase.

Por ejemplo, aquí es una ValidationRule clase personalizada que admite la propiedad enlazable:

[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 es una clase simple que hereda de DependencyObject y tiene 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)); 

Esto resuelve el problema original, pero desafortunadamente trae dos problemas más:

  1. El enlace no funciona correctamente ya que el ValidationRules no es parte del árbol visual y, por lo tanto, no puede obtener la propiedad enlazada correctamente.Por ejemplo, este enfoque ingenuo no funcionará:

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

    En lugar de un objeto proxy debe utilizarse como se explica en this respuesta:

    <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 es una clase simple:

    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. Si la propiedad en el artículo personalizado ValidationRule está vinculada a la propiedad de otro objeto, la lógica de validación de la propiedad original no se activará cuando cambie la propiedad de ese otro objeto.

    Para resolver este problema debemos actualizar el enlace cuando se actualiza la propiedad enlazada ValidationRule. Primero debemos vincular esa propiedad a nuestra clase ComparisonValue. Entonces, podemos actualizar el origen de la unión cuando cambia la propiedad 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)); 
    } 
    

    El mismo problema proxy en el primer caso también existe aquí. Por lo tanto debemos crear otro objeto 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> 
    

    En este caso la propiedad de TextTextBoxToValidate se valida con la propiedad de Items.CountSomeCollection. Cuando la cantidad de elementos en la lista cambie, se activará la validación de la propiedad Text.

Cuestiones relacionadas