【问题标题】:How to suppress validation when nothing is entered如何在未输入任何内容时禁止验证
【发布时间】:2010-12-02 21:39:09
【问题描述】:

我将 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立即画红框并显示错误信息。我希望它仅在名称已被编辑且焦点丢失时才显示错误。有人知道怎么做吗?

【问题讨论】:

  • 我在这个问题上悬赏,希望有一个非hacky的解决方案,如果存在的话。
  • 你不能在 InitializeComponent() 被调用之前创建 Person 吗?
  • 增加赏金以获得良好的非hacky解决方案..

标签: .net wpf validation data-binding


【解决方案1】:

只有在 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;
        }
    }
}

【讨论】:

  • 是的,我可以,但我想调整 WPF 绑定而不是更改我的业务实体。由于我不是 WPF 专家,希望有相对简单的解决方案。这似乎是典型的行为 - 当表单刚刚打开时,不显示所有字段的警报。
  • 我认为这不能在 XAML 或绑定设置级别进行控制。数据要么正确,要么不正确,因此由 IDataErrorInfo 来验证它。或者,您可以检查 "if(columnName == "Name" && Name == "")" 从而将初始的 "null" 视为有效,这将在编辑时变成无效的空字符串。想不出别的办法了。
  • @AlexKofman 我知道这是一篇旧帖子,但这是我现在正在处理的问题。当您已经必须实现 IDataErrorInfo 接口时,您关于更改业务实体的观点是无关紧要的。您最好按照 Uri 的建议将您的对象放在 ViewModel 后面,然后将您的演示逻辑放在那里。
  • 我不喜欢这样,如果您有 50、100 个属性,这将开始变得笨拙,并且代码量超出了应有的范围。 BTW WPF 验证是可怕的,任何接近的方式。没有干净的方法来处理它。
【解决方案2】:

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

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

Validation.ClearInvalid(...) 例如,如果您有一个不想被验证的文本框应该调用

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

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

我不喜欢这个解决方案,但这是我找到的最好的解决方案。我希望 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。

    【讨论】:

    • 您必须复制绑定目标的名称,并且不要忘记删除 TextChanged 事件的事件处理程序,否则每次您在文本框中键入时都会有一个新的绑定。
    【解决方案4】:

    也许您可以选择将验证转移到视图: 您可以在绑定中启用 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.th。如果存在任何错误,您可以在那里识别。

    【讨论】:

      【解决方案5】:

      我认为@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 相关的逻辑(以及通知和其他 View Model 逻辑)。

      【讨论】:

      • 我同意,您可能不应该将实体直接暴露在视图中。当您已经不得不使用 IDataErrorInfo 接口弄脏它时,关于更改业务实体的点是一个有争议的问题
      【解决方案6】:

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

       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>
      

      【讨论】:

        【解决方案7】:

        我只是一个没有太多知识的初级开发人员,但我是这样解决的。

        在我的验证结果类中,我创建了一个不带参数的构造函数来返回有效的验证结果。

        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>
        

        当我的表单加载时,当窗口加载时验证不会触发,但如果我清除文本框,它会触发。

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

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

        【讨论】:

          【解决方案8】:

          我相信这种行为也是一个很好的解决方案。它会在需要时删除 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);
                      }                
                  }
              }
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2023-03-14
            • 1970-01-01
            • 2021-12-17
            • 1970-01-01
            • 2021-12-13
            • 2016-12-16
            • 1970-01-01
            相关资源
            最近更新 更多