2009-10-01 8 views
30

Uso el enlace de datos WPF con entidades que implementan la interfaz IDataErrorInfo. En general, mi código es el siguiente: EntidadCómo suprimir la validación cuando no se ingresa nada

de actividad:

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

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

Cuando el usuario hace clic en "Crear nueva persona" siguiente código se ejecuta:

DataContext = new Person(); 

El problema es que cuando la persona acaba de crear su nombre está vacío y WPF inmediatamente dibuja el marco rojo y muestra el mensaje de error. Quiero que muestre el error solo cuando el nombre ya fue editado y el foco se pierde. ¿Alguien sabe la manera de hacer esto?

+3

Estoy poniendo una recompensa en esta pregunta con la esperanza de una solución no hacky, si es que existe. –

+0

¿No puedes simplemente crear a la persona antes de llamar a InitializeComponent()? – markmnl

+1

Recompensa adicional para obtener una buena solución no hacky. –

Respuesta

15

usted puede cambiar su clase de persona para disparar error de validación sólo si la propiedad nombre fue cambiado nunca:

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

Sí, puedo, pero me gustaría ajustar el enlace de WPF en lugar de cambiar las entidades comerciales.Como no soy experto en WPF, espero que haya una solución relativamente fácil de ese problema. Parece ser un comportamiento típico: no mostrar alertas para todos los campos cuando el formulario está abierto. –

+2

No creo que esto se pueda controlar a nivel de configuración XAML o Enlace. Los datos son correctos o no, por lo que corresponde a IDataErrorInfo validarlo. Alternativamente, puede marcar "if (columnName ==" Name "&& Name ==" ")", tratando así el "null" inicial como válido, que se convertirá en una cadena vacía no válida al editar. No se puede pensar de otra manera. –

+0

@AlexKofman Sé que es una publicación anterior, pero esto es algo que estoy abordando en este momento. Su punto acerca de cambiar las entidades comerciales es irrelevante cuando ya ha tenido que implementar la interfaz IDataErrorInfo. Es mejor que coloques tu objeto detrás de un ViewModel como lo sugirió Uri y pongas tu lógica de presentación allí. – Bronumski

4

Hay otra solución que he encontrado pero no me gusta mucho. Debe borrar la validación en la carga de la página.

Lo que quiero decir es que hay que hacer esto:

Validation.ClearInvalid(...) por ejemplo, si usted tiene un cuadro de texto que usted no desea ser validados deben llamar

Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty)) o algo por el estilo.

Debe hacer esto para cada control que quiera que se borre de la validación.

No me gustó la solución pero fue la mejor que encontré. Esperaba que wpf tuviera algo "fuera de la caja" que funcionó pero no lo encontró.

3

Maldición esto tomó un tiempo para pensar, pero como siempre, ... comportamientos unidos al rescate.

Lo que estás viendo en esencia es el seguimiento de estado sucio. Hay muchas maneras de hacer esto usando su ViewModel, pero como no quería cambiar sus entidades, la mejor manera es usar comportamientos.

Primero, quite los ValidatesOnDataErrors de su enlace Xaml. Cree un comportamiento para el control en el que está trabajando (como se muestra a continuación para TextBox) y en el evento TextChanged (o el evento que desee) restablezca el enlace a uno que valida en los errores de datos. Simple en realidad.

De esta manera, sus entidades no tienen que cambiar, su Xaml se mantiene razonablemente limpio y usted obtiene su comportamiento.

Aquí es el comportamiento código-

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

      } 
     } 
    } 

Y el Xaml es bastante simple también.

<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

Tendría que duplicar el nombre del destino vinculante y no olvide quitar el eventhandler para el evento TextChanged o habrá un nuevo enlace cada vez que escriba en su cuadro de texto. –

3

Quizás sea una opción para usted, cambiar su validación a la Vista: En lugar de implementar IDataErrorInfo, puede habilitar NotifyOnValidationError en su enlace y agregar una ValidationRule que haga la comprobación. Para ValidationRules hay un standard way to control, if the rule should be applied when the object changes (no el valor de la propiedad directamente)

Esto ya proporcionará comentarios visuales para el usuario (se aplicará ErrorTemplate). Si necesita más, p. deshabilitar algunos botones, etc., puede conectar el Validation.Error-Event de su vista a su ViewModel o BusinessEntity, s.th. puedes identificarlo allí, si hay algún error presente.

4

Creo que el enfoque de @Stanislav Kniazev es el correcto. Su comentario sobre no agregar lógica al objeto comercial también es válido. Para tener una clara separación de preocupaciones, ¿qué le parece mantener a la persona en la capa de negocios (o la capa de modelo de datos) e introducir una nueva clase PersonVm con lógica de vista? Para la capa VM me gusta más el patrón de contención que la herencia, y en esta capa también implemento INotifyPropertyChanged, que también es una propiedad de la VM y no del modelo de datos.

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

} 

La idea es poner la lógica de cada capa en la clase adecuada. En la capa del modelo de datos, realiza una validación que solo concierne a los datos puros. La capa Ver modelo agrega la lógica que concierne al Modelo de visualización (así como la notificación y otra lógica del Modelo de visualización).

+0

Acepto, probablemente no debería exponer sus entidades directamente a la vista. Y el punto de cambiar la Entidad Comercial es un punto discutible cuando ya has tenido que ensuciarlo con la interfaz IDataErrorInfo – Bronumski

2

He implementado la siguiente solución:

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

Ejemplo de uso:

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

Sólo soy un desarrollador junior que no tiene muchos conocimientos, pero me fijo de esta manera .

En mi clase validationresult he creado un constructor sin parámetros para devolver un valor de validación válido.

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

Mi código XAML se parece a esto

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

Cuando mis formulario se carga, la validación duerma fuego cuando se carga la ventana, pero si puedo eliminar un cuadro de texto, que hace fuego.

Hay una desventaja de esto, si cargo una entidad no válida que tiene un nombre o código emty, la validación no se activa al cargar la ventana, pero sí cuando completa el cuadro de texto y lo borra. Pero esto no sucede realmente ya que valido todos mis campos cuando creo la entidad ..

No es una solución perfecta, pero a mí me funciona.

Cuestiones relacionadas