Comment supprimer la validation quand rien n'est entré

j'utilise WPF data binding avec des entités qui implémentent IDataErrorInfo interface. En général, mon code ressemble à ceci:

Business entité:

public class Person : IDataErrorInfo 
{
  public string Name { get; set;}

  string IDataErrorInfo.this[string columnName]
  {
    if (columnName=="Name" && string.IsNullOrEmpty(Name))
      return "Name is not entered";
    return string.Empty;
  }  
}

fichier Xaml:

<TextBox Text="{Binding Path=Name, Mode=TwoWay, ValidatesOnDataErrors=true}" />

Lorsque l'utilisateur clique sur "Créer une nouvelle personne" code suivant est exécuté:

DataContext = new Person();

le problème est que quand la personne est juste créée son nom est vide et WPF tirages cadre rouge et affiche le message d'erreur. Je veux qu'elle affiche erreur seulement lorsque le nom a déjà été modifié et le focus est perdu. Quelqu'un sait la façon de le faire?

31
demandé sur H.B. 2009-10-01 10:42:09

7 réponses

Vous pouvez changer votre classe de personne en feu erreur de validation seulement si la propriété de nom a été changée:

public class Person : IDataErrorInfo {

    private bool nameChanged = false;
    private string name;
    public string Name {
        get { return name; }
        set { 
            name = value;
            nameChanged = true;
        }
    }

//... skipped some code

    string IDataErrorInfo.this[string columnName] {
        get {
            if(nameChanged && columnName == "Name" && string.IsNullOrEmpty(Name)) 
                return "Name is not entered"; 
            return string.Empty;
        }
    }
}
16
répondu Stanislav Kniazev 2009-10-01 10:02:42

je pense que @Stanislav Kniazev approche est la bonne. Votre commentaire sur le fait de ne pas ajouter de logique à l'objet d'affaires est également valide. Pour avoir une séparation claire des préoccupations, pourquoi ne pas garder la personne dans la couche business (ou la couche data model), et introduire une nouvelle PersonVm de classe avec la logique de vue. Pour la couche VM j'aime le modèle de confinement plus qu'héritence, et à cette couche je mets également en œuvre INotifyPropertyChanged, qui est également une propriété de la VM et non les données modèle.

public class PersonVm : IDataErrorInfo, INotifyPropertyChanged
{
    private Person _person;

    public PersonVm( ) {
        // default constructor
        _person = new Person( );
        _dirty = false;
    }

    public PersonVm( Person p ) {
        // User this constructor when you get a Person from database or network
        _person = p;
        _dirty = false;
    }

    void fire( string prop ) {
        PropertyChanged( this, new PropertyChangedEventArgs( prop ) );
    }

    public string name {
        get { return _person.name; }
        set { _person.name = value; fire( "name" ); dirty = true; }
    }

    ...

    string IDataErrorInfo.this[string columnName] { 
        get {
            if( dirty ) return _person[columnName];
        }
    }

}

L'idée est de mettre de la logique de chaque couche à la catégorie appropriée. À la couche data model, vous faites une validation qui ne concerne que les données pures. La couche de modèle de vue, vous ajoutez la logique qui concerne le modèle de vue (ainsi que la notification et autre logique de modèle de vue).

5
répondu Uri 2012-02-06 16:07:48

Ce que je veux dire, c'est que vous avez à faire ceci :

Validation.ClearInvalid(...) par exemple, si vous avez une zone de texte que vous ne voulez pas être validé doit appeler

Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty)) ou quelque chose comme ça.

vous devriez faire cela pour chaque contrôle que vous voulez être lavé de la validation.

je n'aimais pas la solution, mais c'est le mieux que j'ai trouvé. Je j'espérais que la wpf avait quelque chose qui" sortait de la boîte " qui fonctionnait mais qui ne l'a pas trouvé.

5
répondu 6lix 2012-12-12 09:13:13

Bon sang cela a pris un certain temps de réfléchir, mais comme toujours,...comportements liés au sauvetage.

ce que vous regardez essentiellement, c'est un traqueur d'état sale. Il y a plusieurs façons de le faire en utilisant votre modèle de vue, mais puisque vous ne vouliez pas changer vos entités, la meilleure façon est d'utiliser les comportements.

tout d'abord, supprimez les ValidatesOnDataErrors de votre reliure Xaml. Créer un comportement pour le contrôle vous travaillez ( comme indiqué ci-dessous pour TextBox ) et dans le TextChanged événement (ou quelque soit l'événement que vous voulez) réinitialisation de la liaison à un valider sur les erreurs de données. Simple, vraiment.

de cette façon, vos entités n'ont pas à changer, votre Xaml reste raisonnablement propre et vous obtenez votre comportement.

voici le code de comportement-

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

    namespace IDataErrorInfoSample
    {
        public static class DirtyStateBehaviours
        {


            public static string GetDirtyBindingProperty(DependencyObject obj)
            {
                return (string)obj.GetValue(DirtyBindingPropertyProperty);
            }

            public static void SetDirtyBindingProperty(DependencyObject obj, string value)
            {
                obj.SetValue(DirtyBindingPropertyProperty, value);
            }

            // Using a DependencyProperty as the backing store for DirtyBindingProperty.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty DirtyBindingPropertyProperty =
                DependencyProperty.RegisterAttached("DirtyBindingProperty", typeof(string), typeof(DirtyStateBehaviours),
                new PropertyMetadata(new PropertyChangedCallback(Callback)));


            public static void Callback(DependencyObject obj,
                DependencyPropertyChangedEventArgs args)
            {
                var textbox = obj as TextBox;
                textbox.TextChanged += (o, s) =>
                {
                    Binding b = new Binding(GetDirtyBindingProperty(textbox));
                    b.ValidatesOnDataErrors = true;
                    textbox.SetBinding(TextBox.TextProperty, b);
                };

            }
        }
    }

et le Xaml est assez simple aussi.

<Window x:Class="IDataErrorInfoSample.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:IDataErrorInfoSample"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    Title="MainWindow"
    Height="350"
    Width="525">

<Window.DataContext>
    <local:Person />
</Window.DataContext>
<StackPanel Margin="20">
    <TextBox Height="20"
             Margin="0,0,0,10"
             local:DirtyStateBehaviours.DirtyBindingProperty="Name"
             Text="{Binding Path=Name}">
    </TextBox>
    <Button Content="Go" />
</StackPanel>

HTH, Stimul8d.

4
répondu Stimul8d 2010-11-16 11:17:15

peut-être que c'est une option pour vous, de déplacer votre validation vers la vue: Au lieu de mettre en œuvre IDataErrorInfo, vous pouvez activer NotifyOnValidationError dans votre reliure et ajouter une règle de Validationqui effectue le contrôle. Pour les règles de validation, il y a un standard de moyen de contrôle, si la règle doit être appliquée lorsque les modifications de l'objet (pas la valeur de la propriété directement)

cela fournira déjà une rétroaction visuelle à l'utilisateur (Errortimplate sera appliqué). Si vous avez besoin d' plus, par exemple désactiver certains boutons, etc., vous pouvez activer la Validation.Erreur-Événement de votre point de vue à votre ViewModel ou BusinessEntity, s.th. vous pouvez identifier là, si une erreur est présente.

4
répondu Simon D. 2012-02-05 22:38:46

j'ai implémenté la solution suivante:

 public class SkipValidationOnFirstLoadBehavior : Behavior<TextBox>
 {
        protected override void OnAttached()
        {
            AssociatedObject.LostFocus += AssociatedObjectOnLostFocus;
        }

        private void AssociatedObjectOnLostFocus(object sender, RoutedEventArgs routedEventArgs)
        {
            //Execute only once
            AssociatedObject.LostFocus -= AssociatedObjectOnLostFocus;

            //Get the current binding
            BindingExpression  expression = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
            if (expression == null) return;
            Binding parentBinding = expression.ParentBinding;

            //Create a new one and trigger the validation 
            Binding updated = new Binding(parentBinding.Path.Path);
            updated.ValidatesOnDataErrors = true;
            updated.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;  
            AssociatedObject.SetBinding(TextBox.TextProperty, updated);
        }
 }

Exemple d'utilisation:

    <TextBox Text="{Binding Email}">
        <i:Interaction.Behaviors>
            <local:SkipValidationOnFirstLoadBehavior/>
        </i:Interaction.Behaviors>
    </TextBox>
3
répondu Andrei Schneider 2013-07-18 08:57:39

je suis juste un développeur junior qui n'a pas beaucoup de connaissances mais je l'ai arrangé de cette façon.

dans ma classe validationresult j'ai fait un constructeur sans paramètres pour retourner un validationresult.

public  class NotEmptyValidation : ValidationRule
{
  public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  {
      if (string.IsNullOrEmpty(value as string))
      {
          return new ValidationResult(false,"Veld kan niet leeg zijn");
      }

      return new ValidationResult(true,null);

 }
  public NotEmptyValidation() : base()
  {
      Validate();
  }


  public ValidationResult Validate()
  {
      return new ValidationResult(true,null);
  }
}

Mon code xaml ressemble à ceci

<!--TEXTBOXES-->
                <TextBox Grid.Column="1" Grid.ColumnSpan="2" Margin="5">
                    <TextBox.Text>
                        <Binding Path="SelectedEntity.Code" UpdateSourceTrigger="PropertyChanged">
                            <Binding.ValidationRules>
                                <val:NotEmptyValidation  />
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>
                <TextBox Grid.Column="1" Grid.ColumnSpan="3" Grid.Row="1" Margin="5">
                    <TextBox.Text>
                        <Binding Path="SelectedEntity.Name" UpdateSourceTrigger="PropertyChanged">
                            <Binding.ValidationRules>
                                <val:NotEmptyValidation />
                            </Binding.ValidationRules>
                        </Binding>
                    </TextBox.Text>
                </TextBox>

Quand ma forme se charge, la validation ne s'enflamme pas quand la fenêtre se charge, mais si je nettoie une boîte de texte, elle s'enflamme.

il y a un inconvénient à cela, si je charge une entité invalide qui a un emty nom ou code, la validation ne démarre pas lors du chargement de la fenêtre, il le fait cependant quand u remplit la zone de texte et l'efface. Mais cela ne se produit pas vraiment puisque je valide tous mes champs quand je crée l'entité..

ce n'est pas une solution parfaite mais ça marche pour moi.

2
répondu Anthony 2014-09-16 17:05:05