【问题标题】:Really simple WPF form data validation - how to?真正简单的 WPF 表单数据验证 - 如何?
【发布时间】:2020-04-01 05:54:57
【问题描述】:

我有一个非常简单的课程,我们称之为客户。 它看起来像这样:

namespace TestValidation
{
     class Customer
     {
        private string _name;
        public string Name
        {
            get { return _name; }
            set
            {
                _name = value;
                if (String.IsNullOrEmpty(value))
                {
                    throw new Exception("Customer name is mandatory.");
                }
            }
        }
    }
}

现在,我创建了一个基本表单,用户可以在其中将客户添加到数据库中。该表单包含简单的 TextBox,绑定到 Customer 的 Name 属性,以及一个“添加”按钮。

XAML 代码是:

<Window x:Class="TestValidation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TestValidation"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
<TextBox Margin="119,86,107,194" Name="CustomerName"
        Text="{Binding Path=Customer.Name, 
                ValidatesOnExceptions=True, 
                ValidatesOnDataErrors=True,
                UpdateSourceTrigger=PropertyChanged,
                NotifyOnValidationError=True}"
    />
        <Button Content="Add" HorizontalAlignment="Left" Margin="204,176,0,0" VerticalAlignment="Top" Width="74"/>
    </Grid>
</Window> 

从 Name 属性的设置器中,您可以了解名称对我来说是强制性的,因此我希望在 Name TextBox 留空时触发验证事件。通过 WPF 的验证规则-一旦用户将注意力移出文本框,并且那里没有任何值-它应该将边框颜色更改为红色。出于某种原因 - 这没有发生,我不知道为什么。我的流程有什么问题?

现在,我已经阅读了很多关于 WPF 中的验证的好文章(例如 Enforcing Complex Business Data Rules with WPFData validation in WPF 和 Windows Presentation Foundation 中的验证),但没有一篇能帮助我解决我的问题。

最后,我希望表单看起来像第一个链接上的 Brian Noyes 优秀文章中的表单(没有 10 个学分,所以我不能附上照片……抱歉)。

如果有人能向我解释它是如何工作的,我将不胜感激。

重要提示 - 我正在使用 .Net 框架 4,因此我需要一个适合此版本的解决方案。

【问题讨论】:

    标签: c# wpf validation xaml


    【解决方案1】:

    我绝对推荐使用IDataErrorInfo 进行 WPF 验证,因为 WPF 已经了解如何使用它,而且它易于实现。

    首先,将接口添加到包含您要验证的数据的类。所需的方法可能看起来像这样:

    public class Customer : IDataErrorInfo
    {
        ...
    
        #region IDataErrorInfo Members
    
        string IDataErrorInfo.Error
        {
            get { return null; }
        }
    
        string IDataErrorInfo.this[string columnName]
        {
            get
            {
                if (columnName == "Name")
                {
                    // Validate property and return a string if there is an error
                    if (string.IsNullOrEmpty(Name))
                        return "Name is Required";
                }
    
                // If there's no error, null gets returned
                return null;
            }
        }
        #endregion
    }
    

    接下来,您需要在 TextBox 绑定中设置 ValidatesOnDataErrors=True,以便在 Name 属性更改时运行验证:

    <TextBox Text="{Binding Path=Customer.Name, ValidatesOnDataErrors=True}" ... />
    

    最后,在 XAML 中创建一个验证模板,告诉 WPF 如何绘制验证错误。这是我通常使用的样式/模板:

    <!-- ValidatingControl Style -->
    <Style TargetType="{x:Type FrameworkElement}" x:Key="ValidatingControl">
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="ToolTip" Value="{Binding 
                    Path=(Validation.Errors)[0].ErrorContent, 
                    RelativeSource={x:Static RelativeSource.Self}}" />
            </Trigger>
        </Style.Triggers>
    </Style>
    

    另外,请确保您的 Customer 类实现了 INotifyPropertyChanged,以便正确响应 UI 更新。我在您的代码中看不到这一点,但为了简单起见,人们通常会忽略它:)

    【讨论】:

    • 我只想为 OP 添加区分大小写的重要性,如果它的绑定路径与预期的大小写不同,它仍然会运行,但永远不会真正绑定到不t存在:)
    • 你能解释一下关于 ValidatingControl 风格的更多信息吗?我在 xaml 中的几个地方进行了尝试,并得到了关于 IDictionary 中包含的 key must bge 的错误。 key 应该是我的控件的实际名称吗?我是把它嵌入到控制标签内还是外面?我是 xaml 和 wpf 的新手,并试图让验证工作。
    • @James &lt;Style&gt; 位于您的.Resources 某处,例如&lt;Window.Resources&gt;,并且可以给它一个x:Key 来引用它。任何想要使用该样式的控件都可以通过key引用它,例如&lt;TextBlock Style="{StaticResource ValidatingControlStyleKey}" ...&gt;
    • @Rachel 感谢您在这里的精彩帖子。我试过了,效果很好。现在,我想检查 Customer 对象在将其保存到数据库时是否有效。如何查看?
    【解决方案2】:

    您没有指定验证规则。将在离开控件之前调用验证规则,然后可以执行任何您想要验证输入的操作。

    here 提供了一个简单的示例 - 我想这就是你想要做的 - 提供。

    【讨论】:

      【解决方案3】:

      使用IDataErrorInfo 进行验证。这个link 会帮助你。

      【讨论】:

      • 试过了,没有帮助。在您共享的链接的示例中,在 TextBox 标记内有 Validation.Error="Validation_Error" 声明,这给了我下一个编译错误:'TestValidation.MainWindow' does not contain a definition for 'Validation_Error' and no extension method 'Validation_Error' accepting a first argument of type 'TestValidation.MainWindow' could be found (are you missing a using directive or an assembly reference?) 。我真的错过了什么?
      • Validation_Error 是代码后面的方法。该方法将调用的验证错误。有一个链接可以下载该示例的完整源代码。下载并试用。
      【解决方案4】:

      认为问题可能是你的类没有实现 INotifyPropertyChanged,所以没有像你期望的那样绑定。

      实现 INotifyPropertyChanged 接口,在属性更改时引发事件并且它应该可以工作。

      请参阅http://msdn.microsoft.com/en-us/library/ms743695(v=vs.110).aspx 了解演练。

      【讨论】:

        【解决方案5】:
        <Binding Path=Name UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <ExceptionValidationRule />
          </Binding.ValidationRules>
        </Binding>
        

        http://msdn.microsoft.com/en-us/library/ms752347%28v=vs.110%29.aspx#what_is_data_binding

        请使用此博客:prasadcsharp.blogspot.com

        【讨论】:

          【解决方案6】:

          这里有一些对我很有效的东西。没有滞后或长编码,但我只在双值上使用它。您可以根据需要更改它。

           private void search_box_TextChanged(object sender, TextChangedEventArgs e)
              {
                  //  box text and background to normal state if user types numbers
                  search_box.Foreground = Brushes.Black;
                  search_box.Background = Brushes.White;
          
                    if (search_id.IsSelected == true)
                  {
                      try
                      {
                          //convert while user is typing
                          if (string.IsNullOrEmpty(search_box.Text)==false)
                        Convert.ToDouble(search_box.Text);
                          search_error.Text = null;
                      }
          
                      //if user types a letter or a space or a symbol  ====>
                      catch (Exception)
                      {
                    //  user cant type any value other than numbers as exception prevents it and clears the box text value <======
                          search_box.Text = null;
                          search_box.Foreground = Brushes.White;
                          search_box.Background = Brushes.Red;
                          search_error.Text="id is numberic value";
                      }
                  }
          
                  }
          

          希望对你有帮助。

          【讨论】:

            【解决方案7】:

            1) 当您使用异常进行验证时,我建议在将值分配给属性支持字段之前抛出异常,因此您拒绝它并且您的数据对象(在这种情况下为 Customer 对象)将只包含有效数据:

            using System;
            
            namespace TestValidation
            {
                public class Customer
                {
                    private string _name;
                    public string Name
                    {
                        get => this._name;
                        set
                        {
                            if (String.IsNullOrEmpty(value))
                                throw new ArgumentException("Customer name is mandatory.", nameof(Name));
            
                            _name = value;
                        }
                    }
                }
            }
            

            2) 默认情况下,WPF 数据绑定引擎会忽略数据对象的 setter 过程中引发的异常。您正确地将ValidatesOnExceptions 设置为true 以指示数据绑定系统对异常做出反应。但是,您在PropertyChanged 上设置了UpdateSourceTrigger,因此只有在目标元素的目标属性(Text)( TextBox) 已更改。如果您从一个空的TextBox 开始,然后只是 Tab 进入它,然后再次离开,Text 属性没有被更改,因此不会触发 Source 属性(名称)的更新(即使使用LostFocus 作为 UpdateSourceTrigger 模式)。您可以更正此问题,只需在 costructor 或 Loaded 事件处理程序中将 Text 属性初始化为 nullString.Empty。这样,一旦窗口被渲染,文本框就会出现一个红色边框。如果您将UpdateSourceTrigger 设置为LostFocus(这是TextBoxText 属性的默认值),TextBox 最初会出现且不会出现错误,但如果您使用 Tab 进出,它将突出显示为预期的红色边框。 注意:这一切都行得通,因为TextBoxText 属性使用TwoWay 作为默认绑定模式,数据可以从目标到源。

            using System.Windows;
            
            namespace TestValidation
            {
                public partial class MainWindow: System.Windows.Window
                {
                    public CustomerTest()
                    {
                        InitializeComponent();
                    }
            
                    private void window_Loaded(object sender, RoutedEventArgs e)
                    {
                        this.txtCustomerName.Text = null;
                    }
                }
            }
            
            <Window x:Class="TestValidation.MainWindow"
                    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:ValidationTests"
                    Title="MainWindow" Height="350" Width="525"
                    Loaded="window_Loaded">
            
                <Window.Resources>
                    <local:Customer x:Key="customer" />
                </Window.Resources>
            
                <Grid DataContext="{StaticResource customer}">
                    <TextBox Margin="119,86,107,194"
                             x:Name="txtCustomerName" x:FieldModifier="protected"
                             Text="{Binding Path=Name, 
                                            ValidatesOnExceptions=True,
                                            UpdateSourceTrigger=PropertyChanged}" />
                    <Button Content="Add" HorizontalAlignment="Center" Margin="204,176,0,0" VerticalAlignment="Top" Width="74"/>
                </Grid>
            </Window>
            

            3) 在这种情况下,不需要 INotifyPropertyChanged,因为您只是想通过用户在 TextBox 中的交互来更改 Source 属性 (Name) 的值,而不是使用其他修改 Name 属性C# 代码。 INotifyPropertyChanged 用于通知 WPF 数据绑定系统数据对象的变化,以便 WPF 可以更新用户界面中的数据(当 Source 因代码过程发生变化时更新 Target)。

            【讨论】:

              【解决方案8】:

              你没有实现INotifyPropertyChanged
              还要注意IDataErrorInfoINotifyDataErrorInfo.. 如果您想将验证逻辑移出设置器,则使用它们。
              还需要承认,在现代应用程序中,最好将验证逻辑移动到单独的类型中。 (见fluentValidation

              using System;
              
              namespace TestValidation
              {
                  public class Customer : INotifyPropertyChanged
                  {
                      private string _name;
                      public string Name
                      {
                          get => this._name;
                          set
                          {
                              if(_name == value) return;
              
                              if (String.IsNullOrEmpty(value))
                                  throw new ArgumentException("Customer name is mandatory.", nameof(Name));
              
                              _name = value;
              
                              OnPropertyChanged();
                          }
                      }
              
                      #region INotifyPropertyChanged
                      // TODO: Impelemnt interface INotifyPropertyChanged
              
                      // Create the OnPropertyChanged method to raise the event
                      // The calling member's name will be used as the parameter.
                      protected void OnPropertyChanged([CallerMemberName] string name = null)
                      {
                          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
                      }
                      #endregion
                  }
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2012-08-27
                • 2011-05-18
                • 1970-01-01
                • 2013-05-19
                • 2011-03-12
                • 2013-11-16
                • 2012-01-15
                • 1970-01-01
                相关资源
                最近更新 更多