¿Cómo validar dos propiedades que dependen unas de otras?

8

Tengo un modelo de vista con 2 propiedades: Ay Bquiero validar eso A < B.

A continuación se muestra mi implementación simplificada donde uso la regla de validación personalizada. Dado que cada propiedad se valida de forma independiente, conduce a un problema molesto: si el Avalor ingresado no es válido, entonces permanece así incluso después de cambiar B, ya que la validación de Bno sabe nada A.

Esto se puede ver en esta demostración:

Aes inválido después de ingresar 11, eso es correcto desde entonces 11 > 2. Cambiar Ba 22no volver a evaluar A, tengo que editar Apara que se apruebe la validación.

¿Lo que quiero? Quiero que después de entrar 22en Bel borde rojo (error de validación) desaparezca y A = 11, B = 22que sean valores de origen en el modelo de vista.

¿Cómo puedo en la Bvalidación forzar de alguna manera la Avalidación después de que el nuevo Bvalor se sincronice con la fuente?


Ver modelo:

public class ViewModel : INotifyPropertyChanged
{
    int _a;
    public int A
    {
        get => _a;
        set
        {
            _a = value;
            OnPropertyChanged();
        }
    }

    int _b;
    public int B
    {
        get => _b;
        set
        {
            _b = value;
            OnPropertyChanged();
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    public virtual void OnPropertyChanged([CallerMemberName] string property = "") =>
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}

Ver:

<StackPanel>
    <TextBox Margin="10" Text="{local:MyBinding A}" />
    <TextBox Margin="10" Text="{local:MyBinding B}" />
</StackPanel>

Ver código:

public MainWindow()
{
    InitializeComponent();
    DataContext = new ViewModel { A = 1, B = 2 };
}

Unión:

public class MyBinding : Binding
{
    public MyBinding(string path) : base(path)
    {
        UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
        ValidationRules.Add(new MyValidationRule());
    }
}

Regla de validación:

public class MyValidationRule : ValidationRule
{
    public MyValidationRule() : base(ValidationStep.ConvertedProposedValue, false) { }

    public override ValidationResult Validate(object value, CultureInfo cultureInfo) => ValidationResult.ValidResult; // not used

    public override ValidationResult Validate(object value, CultureInfo cultureInfo, BindingExpressionBase owner)
    {
        var binding = owner as BindingExpression;
        var vm = binding?.DataItem as ViewModel;
        switch (binding.ResolvedSourcePropertyName)
        {
            case nameof(vm.A):
                if ((int)value >= vm.B)
                    return new ValidationResult(false, "A should be smaller than B");
                break;
            case nameof(vm.B):
                if ((int)value <= vm.A)
                    return new ValidationResult(false, "B should be bigger than A");
                break;
        }
        return base.Validate(value, cultureInfo, owner);
    }
}
Sinatr
fuente
2
¿Funciona si también llama OnPropertyChanged(nameof(B))al setter de a A(y el equivalente para el setter de B)?
Dirk
1
La regla de validación solo es realmente adecuada para casos de uso simples. inotifydataerrorinfo en el vm es el enfoque habitual para cualquier cosa sustancial. Echa un vistazo a la validación fluida. Ejemplo de introducción: gist.github.com/holymoo/11243164 fluentvalidation.net/built-in-validators De improviso : ambas propiedades probablemente tendrían la misma regla con un predicado. Ambos son buenos.
Andy
1
Solo es relevante si expone directamente un modelo. No expongas directamente un modelo. Usa modelos de vista. Solo comprometer datos válidos.
Andy
1
@Sinatr: "Me parece más correcto el concepto de regla de validación" está mal. Debe implementar INotifyDataErrorInfoen su modelo de vista si desea realizar este tipo de validación. No es compatible con las reglas de validación.
mm8
1
@Rekshino, sí, también estaba pensando cuándo validar A: antes o después de validar B(en otras palabras, antes de que el valor de B sea aceptado y sincronizado con la fuente o después). El orden importa. Y, de hecho, primero tengo que tener todos los valores modificados a mano y solo entonces hacer la validación en orden normal.
Sinatr

Respuestas:

6

ValidationRules no admite la invalidación de una propiedad al configurar otra propiedad.

Lo que debe hacer es implementar INotifyDataErrorInfoen su modelo de vista y generar el ErrorsChangedevento cada vez que desee actualizar el estado de validación de una propiedad.

Hay un ejemplo disponible en este artículo de TechNet .

mm8
fuente