真正简单的WPF表单数据验证 - 如何?

时间:2013-12-10 13:16:44

标签: c# wpf validation xaml

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

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属性,以及一个“Add”按钮。

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属性的setter中,您可以理解该名称对我来说是必需的,因此如果Name TextBox留空,我希望验证事件上升。通过WPF的验证规则 - 一旦用户将焦点移出文本框,并且那里没有值 - 它应该将边框颜色更改为红色。出于某种原因 - 这没有发生,我也不知道为什么。我的过程有什么问题?

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

最终,我希望表单看起来像Brian Noyes中第一个链接上的优秀文章(没有10个学分,所以我无法附上照片......对不起)。

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

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

9 个答案:

答案 0 :(得分:31)

我肯定会建议使用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更新。我没有在你的代码中看到这一点,但为了简单起见,人们常常将其留下:)

答案 1 :(得分:2)

您未指定验证规则。在保留控件之前将调用验证规则,然后可以执行您想要验证输入的任何内容。

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

答案 2 :(得分:2)

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

答案 3 :(得分:0)

认为问题可能是您的类没有实现INotifyPropertyChanged,因此不会像您期望的那样绑定。

实现INotifyPropertyChanged接口,在属性发生变化时引发事件并且它应该有效。

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

答案 4 :(得分:0)

<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

答案 5 :(得分:0)

我知道这则帖子很旧,但这对我来说还是不错的。没有延迟或长时间编码,但是我只在双精度值上使用了它。您可以根据需要进行更改。

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

        }

希望有帮助。

答案 6 :(得分:0)

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数据绑定引擎将忽略数据objetc的setter过程中引发的异常。您已将ValidatesOnExceptions正确设置为true,以指示数据绑定系统对异常做出反应。但是,您在UpdateSourceTrigger上设置了PropertyChanged,因此只有当Target属性({目标元素(Name)的{1}}已更改。如果您从一个空的Customer开始,然后依次跳入标签,然后再移开,则Text属性尚未更改,因此将不会触发Source属性(名称)的更新(此操作即使将TextBox作为UpdateSourceTrigger模式也会发生)。您可以在构造函数或TextBox事件处理程序中将Text属性初始化为LostFocusText,将其更正。这样,一旦呈现窗口,该文本框将显示为红色边框。如果您将null设置为String.Empty(这是Loaded的{​​{1}}属性的默认设置),则UpdateSourceTrigger最初会出现错误,但是如果您标签进入和退出,它将以预期的红色边框突出显示。 注意:所有这些工作都是有效的,因为LostFocus的{​​{1}}属性使用TextBox作为默认绑定模式,因此数据可以从目标到源。

Text
TextBox

3)在这种情况下,不需要INotifyPropertyChanged,因为您只想通过用户在TextBox中的交互来更改Source属性(名称)的值,而无需与其他属性一起修改Name属性C#代码。实现INotifyPropertyChanged的目的是为了通知WPF数据绑定系统有关数据对象的更改,以便WPF可以更新用户界面中的数据(由于代码过程而更改Source时,请更新Target)。

答案 7 :(得分:0)

您没有实现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
    }
}

答案 8 :(得分:-1)

<ControlTemplate x:Key="TextBoxErrorTemplate">
    <StackPanel>
        <StackPanel Orientation="Horizontal">
            <Image Height="16" Margin="0,0,5,0" 
                    Source="Assets/warning_48.png"/>
            <AdornedElementPlaceholder x:Name="Holder"/>
        </StackPanel>
        <Label Foreground="Red" Content="{Binding ElementName=Holder, 
               Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
    </StackPanel>
</ControlTemplate> 


<TextBox x:Name="Box" 
         Validation.ErrorTemplate="{StaticResource TextBoxErrorTemplate}">
    <TextBox.Text>
        <Binding Path="ValueInBox" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ValidationExpamples:DoubleRangeRule Min="0.5" Max="10"/>
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>           
</TextBox>

IntRangeRule类:

public class IntRangeRule : ValidationRule
{
    private int _min;
    private int _max;
    public IntRangeRule()
    {
    }
    public int Min
    {
        get { return _min; }
        set { _min = value; }
    }

    public int Max
    {
        get { return _max; }
        set { _max = value; }
    }
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        int l_input = 0;
        try
        {
            if (((string)value).Length > 0)
            {
                l_input = Int32.Parse((String)value);
            }
        }
        catch (Exception e)
        {
            return new ValidationResult(false, "Illegal characters or " + e.Message);
        }

        if ((l_input < Min) || (l_input > Max))
        {
            return new ValidationResult(false, "Please enter an value in the range: " + Min + " - " + Max + ".");
        }

        return new ValidationResult(true, null);
    }
}