2009-10-01 69 views
30

我使用WPF数据绑定与实现IDataErrorInfo接口的实体。一般来说我的代码看起来是这样的:如何在不输入任何内容的情况下禁止验证

业务实体:

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}" /> 

在执行上的 “创建新的人” 下面的代码用户点击:

DataContext = new Person(); 

问题是,当人刚刚创建它的名字是空的,WPF 立即绘制红色框架并显示错误消息。我希望它仅在名称已被编辑并且焦点丢失时才显示错误。有人知道如何做到这一点?

+3

我在这个问题上付出了一笔赏金,希望得到一个非hacky解决方案(如果存在的话)。 – 2010-11-11 10:26:02

+0

不能在调用InitializeComponent()之前创建Person吗? – markmnl 2011-03-08 02:44:06

+1

增加了赏金,以获得一个好的非hacky解决方案.. – 2012-01-31 16:20:43

回答

15

你可以改变你的个人类射击验证错误只有当Name属性被不断改变:

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

是的,我可以,但我想调整WPF绑定,而不是改变我的业务实体。由于我不是WPF专家,所以希望有这样的问题比较容易解决。这似乎是典型的行为 - 不在窗体刚刚打开时显示所有字段的警报。 – 2009-10-01 11:39:35

+2

我不认为这可以在XAML或绑定设置级别进行控制。数据是正确的或不正确的,所以需要IDataErrorInfo来验证它。或者,您可以选择“if(columnName ==”Name“&& Name ==”“)”,因此将初始的“null”视为有效,这将在编辑时变为无效的空字符串。想不出别的办法。 – 2009-10-01 12:38:01

+0

@AlexKofman我知道这是一个旧帖子,但这是我现在正在处理的事情。当您必须实现IDataErrorInfo接口时,您关于更改业务实体的观点是无关紧要的。按照Uri的建议,将您的对象放在ViewModel的后面,并将您的表示逻辑放在那里。 – Bronumski 2012-10-23 23:10:46

4

还有另一种解决方案,我发现,但我不喜欢它了。您必须清除页面加载时的验证。

我的意思是,你必须这样做:如果你有你不一个文本框要验证应该叫

Validation.ClearInvalid(txtSomething.GetBindingExpression(TextBox.TextProperty))或类似的东西

Validation.ClearInvalid(...)例如。

您应该对每个想要清除验证的控件执行此操作。

我不喜欢解决方案,但这是我发现的最好的。我希望wpf有一些“开箱即用”的工作,但没有找到它。

3

该死的花了一段时间想出来,但一如既往,......附加的行为救援。

你在本质上看什么是脏状态跟踪。有许多方法可以使用ViewModel来完成此操作,但由于您不想更改实体,所以最好的方法是使用行为。

首先从Xaml绑定中移除ValidatesOnDataErrors。为您正在使用的控件(如下面显示的TextBox)和TextChanged事件(或任何您想要的事件)创建一个行为,将绑定重置为确实对进行验证。真的很简单。

这样,您的实体不必更改,您的Xaml保持合理清洁,并获得您的行为。

这里的代码 -

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

      } 
     } 
    } 

行为和XAML是非常简单的了。

<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

您必须复制绑定目标的名称,并且不要忘记删除TextChanged事件的事件处理程序,否则每次键入文本框时都会有新的绑定。 – 2012-02-02 21:51:15

3

也许这是你的选择,你的验证转移到查看: 无需实现IDataErrorInfo的,你可以在你的绑定使NotifyOnValidationError并添加有效性规则,做检查。 对于ValidationRules有一个standard way to control, if the rule should be applied when the object changes(不是直接属性值)

这将已经向用户提供视觉反馈(ErrorTemplate将被应用)。 如果您需要更多,例如禁用某些按钮等,您可以将视图的Validation.Error事件连接到ViewModel或BusinessEntity,s.th.如果出现任何错误,您可以在那里找到。

4

我认为@Stanislav Kniazev的方法是正确的。您关于不向业务对象添加逻辑的评论也是有效的。为了清楚地分离关注点,如何将Person保留在业务层(或数据模型层)中,并引入一个具有视图逻辑的新类PersonVm。对于VM层,我更喜欢包含模式而不是继承,并且在这一层,我还实现了INotifyPropertyChanged,它也是VM的属性,而不是数据模型。

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

} 

这个想法是把每一层的逻辑放在合适的类。在数据模型层,您进行仅涉及纯数据的验证。在视图模型层,您添加了关于视图模型的逻辑(以及通知和其他视图模型逻辑)。

+0

我同意,你可能不应该将你的实体直接暴露给视图。关于更改业务实体的观点是一个有争议的问题,因为您必须使用IDataErrorInfo接口对其进行肮脏处理 – Bronumski 2012-10-23 23:07:55

2

我已经实现了以下解决方案:

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

使用示例:

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

我只是谁没有多少知识,初级开发者,但我固定它这样。

在我的validationresult类中我做了一个没有参数的构造函数来返回一个有效的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); 
    } 
} 

我的XAML代码看起来像这样

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

当我的形式加载,确认犯规火当窗口加载,但如果我明确一个文本框,但它确实火。

这有一个缺点,如果我加载了一个无效的实体,它有一个emty名称或代码,验证不会在加载窗口时触发,但是当你填入文本框并清除它时,它确实会触发。 但是,这并不是真的发生,因为我在创建实体时验证了所有的字段。

它不是一个完美的解决方案,但它适用于我。

相关问题