如何输入任何内容时如何禁止验证

时间:2009-10-01 06:42:09

标签: .net wpf validation data-binding

我使用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 立即绘制红框并显示错误消息。我希望它仅在名称已编辑且焦点丢失时显示错误。有人知道这样做的方法吗?

8 个答案:

答案 0 :(得分:16)

只有在更改了Name属性时,才能将您的person类更改为触发验证错误:

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

答案 1 :(得分:4)

我找到了另一种解决方案,但我不喜欢它。您必须清除页面加载验证。

我的意思是你必须这样做:

Validation.ClearInvalid(...)例如,如果您有一个不想要验证的文本框,请致电

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

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

我不喜欢这个解决方案,但那是我找到的最好的解决方案。我希望wpf有一些“开箱即用”的东西有效,但没有找到它。

答案 2 :(得分: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];
        }
    }

}

我们的想法是将每一层的逻辑放在适当的类中。在数据模型层,您只进行与纯数据相关的验证。在View Model层中,您可以添加与View Model相关的逻辑(以及notificaton和其他View Model逻辑)。

答案 3 :(得分: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。

答案 4 :(得分:3)

也许你可以选择将验证转移到View: 您可以在绑定中启用NotifyOnValidationError,并添加执行检查的ValidationRule,而不是实现IDataErrorInfo。 对于ValidationRules,有standard way to control, if the rule should be applied when the object changes(不是直接属性值)

这已经为用户提供了视觉反馈(将应用ErrorTemplate)。 如果您需要更多,例如禁用某些按钮等,您可以将视图的Validation.Error-Event连接到ViewModel或BusinessEntity,s。如果有任何错误,你可以在那里识别。

答案 5 :(得分: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>

答案 6 :(得分: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名称或代码,验证不会在加载窗口时触发,但是当你填写文本框并清除它时它会这样做。 但是,由于我在创建实体时验证了所有字段,所以这并不会发生。

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

答案 7 :(得分:0)

我相信这种行为也是很好的解决方案。必要时,它会删除TextBox上的ErrorTemplate,并且还支持多个“有效”无效值(您也可以通过将ValidInputs设置为依赖项属性来改进它)。

public class NotValidateWhenSpecified : Behavior<TextBox>
{
    private ControlTemplate _errorTemplate;

    public string[] ValidInputs { get; set; } = { string.Empty };

    protected override void OnAttached()
    {
        AssociatedObject.TextChanged += HideValiationIfNecessary;

        _errorTemplate = Validation.GetErrorTemplate(AssociatedObject);
        Validation.SetErrorTemplate(AssociatedObject, null);
    }        

    protected override void OnDetaching()
    {
        AssociatedObject.TextChanged -= HideValiationIfNecessary;
    }

    private void HideValiationIfNecessary(object sender, TextChangedEventArgs e)
    {
        if (ValidInputs.Contains(AssociatedObject.Text))
        {                
            if (_errorTemplate != null)
            {
                _errorTemplate = Validation.GetErrorTemplate(AssociatedObject);
                Validation.SetErrorTemplate(AssociatedObject, null);
            }                
        }
        else
        {
            if (Validation.GetErrorTemplate(AssociatedObject) != _errorTemplate)
            {
                Validation.SetErrorTemplate(AssociatedObject, _errorTemplate);
            }                
        }
    }
}