2009-10-01 21 views
30

Ich verwende WPF-Datenbindung mit Entitäten, die IDataErrorInfo Schnittstelle implementieren. Im Allgemeinen sieht mein Code wie folgt aus:So unterdrücken Sie die Validierung, wenn nichts eingegeben wird

Wirtschaftseinheit:

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

XAML-Datei:

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

Wenn Benutzer klickt auf "Neue Person" folgenden Code wird ausgeführt:

DataContext = new Person(); 

Das Problem ist, dass wenn Person nur erstellt wird, ist der Name leer und WPF sofort zeichnet roten Rahmen und zeigt eine Fehlermeldung an. Ich möchte, dass der Fehler nur angezeigt wird, wenn der Name bereits bearbeitet wurde und der Fokus verloren geht. Kennt jemand den Weg, dies zu tun?

+3

Ich gebe ein Kopfgeld auf diese Frage in der Hoffnung auf eine nicht-hacky Lösung, wenn eine existiert. –

+0

Können Sie nicht einfach die Person erstellen, bevor InitializeComponent() aufgerufen wird? – markmnl

+1

Hinzugefügt Bounty, um eine gute nicht-hacky Lösung zu bekommen .. –

Antwort

15

Sie können Ihre Person Klasse nur ändern, Validierungsfehler ausgelöst, wenn Eigenschaft Name jemals geändert wurde:

gefunden
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; 
     } 
    } 
} 
+5

Ja, ich kann, aber ich möchte WPF-Bindung tune statt ändern dann meine Geschäftseinheiten.Da ich kein WPF-Experte bin, hoffe ich, dass es eine relativ einfache Lösung eines solchen Problems gibt. Es scheint ein typisches Verhalten zu sein - Warnungen für alle Felder nicht anzuzeigen, wenn das Formular gerade geöffnet ist. –

+2

Ich glaube nicht, dass dies auf XAML- oder Binding-Einstellungenebene gesteuert werden kann. Die Daten sind entweder korrekt oder nicht, es liegt also an IDataErrorInfo, sie zu validieren. Alternativ können Sie "if (columnName ==" Name "&& Name ==" ")" "überprüfen" und somit initial "null" als gültig behandeln, was sich beim Editieren in eine ungültige leere Zeichenkette verwandelt. Ich kann mir keinen anderen Weg vorstellen. –

+0

@AlexKofman Ich weiß, es ist ein alter Post, aber das ist etwas, das ich gerade anwende. Ihr Punkt zum Ändern der Geschäftsentitäten ist irrelevant, wenn Sie die IDataErrorInfo-Schnittstelle bereits implementieren mussten. Sie sollten Ihr Objekt hinter ein ViewModel stellen, wie es Uri vorgeschlagen hat, und Ihre Präsentationslogik dort einfügen. – Bronumski

4

Es gibt eine andere Lösung, die ich aber ich tue es nicht sehr gut gefällt. Sie müssen die Überprüfung beim Laden der Seite löschen.

Was ich meine ist, dass Sie haben, dies zu tun:

Validation.ClearInvalid(...) zum Beispiel, wenn Sie eine Textbox Sie nicht wollen, sollten

Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty)) oder so ähnlich nennen validiert werden.

Sie sollten dies für jede Kontrolle tun, die von der Validierung freigeschaltet werden soll.

Ich mochte die Lösung nicht, aber das war das Beste, was ich gefunden habe. Ich hoffte, dass wpf etwas "out of the box" hatte, das funktionierte, aber es nicht fand.

3

Verdammt das hat eine Weile gedauert, aber wie immer, ... beigefügt Verhalten zur Rettung.

Was Sie im Wesentlichen betrachten, ist die Verfolgung des schmutzigen Zustandes. Es gibt viele Möglichkeiten, dies mit Ihrem ViewModel zu tun, aber da Sie Ihre Entitäten nicht ändern wollten, verwenden Sie am besten das Verhalten.

Zuerst entfernen Sie die ValidatesOnDataErrors aus Ihrer Xaml-Bindung. Erstellen Sie ein Verhalten für das Steuerelement, an dem Sie gerade arbeiten (wie unten für TextBox gezeigt) und im Ereignis TextChanged (oder einem anderen gewünschten Ereignis) setzen Sie die Bindung auf setzt auf Datenfehler zurück. Einfach wirklich.

Auf diese Weise müssen sich Ihre Entitäten nicht ändern, Ihr Xaml wird einigermaßen sauber gehalten und Sie erhalten Ihr Verhalten.

Hier ist das Verhalten Code-

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

      } 
     } 
    } 

Und die XAML ist ziemlich gerade zu nach vorne.

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

+2

Sie müssten den Namen des Bindungsziels duplizieren und vergessen Sie nicht, den Eventhandler für das TextChanged-Ereignis zu entfernen, oder es wird jedes Mal eine neue Bindung geben, wenn Sie Ihr Textfeld eingeben. –

3

Vielleicht ist es eine Option für Sie, Ihre Validierung auf die Ansicht zu verschieben: Anstatt IDataErrorInfo zu implementieren, können Sie NotifyOnValidationError in Ihrer Bindung aktivieren und eine ValidationRule hinzufügen, die die Überprüfung durchführt. Für Validationrules gibt es eine standard way to control, if the rule should be applied when the object changes (nicht der Wert der Eigenschaft direkt)

Dies wird bereits ein visuelles Feedback für den Benutzer zur Verfügung stellen (Errortemplate wird angewendet werden). Wenn Sie mehr benötigen, z.B. deaktiviere einige Buttons etc, du kannst das Validation.Error-Event deiner View mit deinem ViewModel oder BusinessEntity, s.th. Sie können sich dort identifizieren, wenn ein Fehler vorliegt.

4

Ich denke @Stanislav Kniazev Ansatz ist der richtige. Ihr Kommentar darüber, dass dem Geschäftsobjekt keine Logik hinzugefügt wird, ist ebenfalls gültig. Um eine saubere Trennung der Bedenken zu erhalten, wie wäre es mit der Person in der Business-Schicht (oder der Datenmodellschicht) zu belassen und eine neue Klasse PersonVm mit View-Logik einzuführen? Für die VM-Ebene mag ich das Containment-Muster mehr als die Vererbung, und auf dieser Ebene implementiere ich auch INotifyPropertyChanged, was auch eine Eigenschaft der VM und nicht des Datenmodells ist.

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

} 

Die Idee ist es, die Logik jeder Schicht in der entsprechenden Klasse zu setzen. Auf der Datenmodellschicht führen Sie eine Validierung durch, die nur die reinen Daten betrifft. Auf der Ebene "Modell anzeigen" fügen Sie Logik hinzu, die das Ansichtsmodell betrifft (sowie Benachrichtigungen und andere View Model-Logik).

+0

Ich stimme zu, Sie sollten Ihre Entitäten wahrscheinlich nicht direkt der Ansicht aussetzen. Und der Punkt zum Ändern der Business Entity ist ein strittiger Punkt, wenn Sie es bereits mit der IDataErrorInfo-Schnittstelle verschmutzen mussten – Bronumski

2

Ich habe folgende Lösung implementiert:

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

Anwendungsbeispiel:

<TextBox Text="{Binding Email}"> 
     <i:Interaction.Behaviors> 
      <local:SkipValidationOnFirstLoadBehavior/> 
     </i:Interaction.Behaviors> 
    </TextBox> 
1

Ich bin nur ein Junior-Entwickler, der nicht viele Kenntnisse hat, aber ich es fest auf diese Weise .

In meiner ValidationErgebnis-Klasse habe ich einen Konstruktor ohne Parameter erstellt, um ein gültiges Validierungsresultat zurückzugeben.

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

Meine XAML-Code sieht wie folgt aus

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

Wenn meine Form Lasten, die Validierung doesnt Feuer, wenn die Fenster geladen wird, aber wenn ich ein Textfeld zu löschen, es tut Feuer.

Es gibt einen Nachteil, wenn ich eine ungültige Entity laden, die einen Empty Name oder einen Code hat, löst die Validierung nicht beim Laden des Fensters, es tut jedoch, wenn Sie das Textfeld ausfüllen und löschen. Aber das passiert nicht wirklich, da ich alle meine Felder validiere, wenn ich die Entität erstelle.

Es ist keine perfekte Lösung, aber es funktioniert für mich.

Verwandte Themen