• WPF model validation


    This post is rooted in this: http://stackoverflow.com/questions/14023552/how-to-use-idataerrorinfo-error-in-a-wpf-program

    As many people know, WPF uses IDataErrorInfo interface for validation. This is IDataErrorInfo's definition:

        // Summary:
        //     Provides the functionality to offer custom error information that a user
        //     interface can bind to.
        public interface IDataErrorInfo
        {
            // Summary:
            //     Gets an error message indicating what is wrong with this object.
            //
            // Returns:
            //     An error message indicating what is wrong with this object. The default is
            //     an empty string ("").
            string Error { get; }
    
            // Summary:
            //     Gets the error message for the property with the given name.
            //
            // Parameters:
            //   columnName:
            //     The name of the property whose error message to get.
            //
            // Returns:
            //     The error message for the property. The default is an empty string ("").
            string this[string columnName] { get; }
        }

    It's quite simple and self-explanatory. For examle:

        class Person : IDataErrorInfo
        {
            public string PersonName { get; set; }
            public int Age { get; set; }
    
            string IDataErrorInfo.this[string propertyName]
            {
                get
                {
                    if(propertyName=="PersonName")
                    {
                        if(PersonName.Length>30 || PersonName.Length<1)
                        {
                            return "Name is required and less than 30 characters.";
                        }
                    }
                    else if (propertyName == "Age")
                    {
                        if (Age<10 || Age>50)
                        {
                            return "Age must between 10 and 50.";
                        }
                    }
                    return null;
                }
            }
    
            string IDataErrorInfo.Error
            {
                get
                {
                    if(PersonName=="Tom" && Age!=30)
                    {
                        return "Tom must be 30.";
                    }
                    return null;
                }
            }
        }

    In order to make this model observable, I also implement the INotifyPropertyChanged interface. (For more information about INotifyPropertyChanged: http://www.codeproject.com/Articles/165368/WPF-MVVM-Quick-Start-Tutorial )

        class Person : IDataErrorInfo, INotifyPropertyChanged
        {
            private string _PersonName;
            private int _Age;
            public string PersonName
            {
                get
                {
                    return _PersonName;
                }
                set 
                {
                    _PersonName = value;
                    NotifyPropertyChanged("PersonName");
                }
            }
            public int Age {
                get
                {
                    return _Age;
                }
                set
                {
                    _Age = value;
                    NotifyPropertyChanged("Age");
                }
            }
    
            string IDataErrorInfo.this[string propertyName]
            {
                get
                {
                    if(propertyName=="PersonName")
                    {
                        if(PersonName.Length>30 || PersonName.Length<1)
                        {
                            return "Name is required and less than 30 characters.";
                        }
                    }
                    else if (propertyName == "Age")
                    {
                        if (Age<10 || Age>50)
                        {
                            return "Age must between 10 and 50.";
                        }
                    }
                    return null;
                }
            }
    
            string IDataErrorInfo.Error
            {
                get
                {
                    if(PersonName=="Tom" && Age!=30)
                    {
                        return "Tom must be 30.";
                    }
                    return null;
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void NotifyPropertyChanged(string propertyName)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    It's not a grace implementation but enough to illustrate this case. I will show a better solution later in this article. Now we create a user interface like that:

    <Window x:Class="WpfModelValidation.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="165" Width="395" TextOptions.TextFormattingMode="Display">
        <Grid>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="176*" />
                <ColumnDefinition Width="327*" />
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="40" />
                <RowDefinition Height="*" />
            </Grid.RowDefinitions>
            <Label Content="Person Name" Height="28" HorizontalAlignment="Right" />
            <Label Content="Age" Grid.Row="1" Height="28" HorizontalAlignment="Right" />
            <TextBox Grid.Column="1" Height="23" HorizontalAlignment="Left" Margin="5,0,0,0" Name="textboxPersonName" VerticalAlignment="Center" Width="120" Text="{Binding PersonName, ValidatesOnDataErrors=True}" />
            <TextBox Grid.Column="1" Grid.Row="1" Height="23" HorizontalAlignment="Left" Margin="5,0,0,0" Name="textBox1" VerticalAlignment="Center" Width="50" Text="{Binding Age, ValidatesOnDataErrors=True}" />
            <StackPanel Grid.Column="0" Grid.Row="2" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Right">
                <TextBlock Name="textblockError" Text="{Binding Error, ValidatesOnDataErrors=True}" />
                <Button Content="Test" Grid.Column="1" Grid.Row="2" Height="23" HorizontalAlignment="Right" VerticalAlignment="Center" Name="buttonTest" Width="75" />
            </StackPanel>
        </Grid>
    </Window>

    And the CSharp code:

        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
    
                Person person = new Person
                    {
                        PersonName = "Tom",
                        Age = 31 //A model error should be occurred because Tom must be 30
                    };
    
                DataContext = person;
            }
        }

    Run the program.

    No error displays? (You can download the code here) That is not we are expecting. After searching a lot of material, I finally realize that the Error property of IDataErrorInfo is useless in WPF! That's a little weird, how should I do model level validation? At last, I found a workaround, I made a special property named ModelError and a method named IsValid. The model is changed to:

        class Person : IDataErrorInfo, INotifyPropertyChanged
        {
            private string _PersonName;
            private int _Age;
            public string PersonName
            {
                get
                {
                    return _PersonName;
                }
                set 
                {
                    _PersonName = value;
                    NotifyPropertyChanged("PersonName");
                }
            }
            public int Age {
                get
                {
                    return _Age;
                }
                set
                {
                    _Age = value;
                    NotifyPropertyChanged("Age");
                }
            }
    
            public string ModelError
            {
                get { return ModelValidation(); }
            }
    
            private string ModelValidation()
            {
                if (PersonName == "Tom" && Age != 30)
                {
                    return "Tom must be 30.";
                }
                return null;
            }
    
            //Call this to trigger the model validation.
            public void Validate()
            {
                NotifyPropertyChanged("");
            }
    
            string IDataErrorInfo.this[string propertyName]
            {
                get
                {
                    if (propertyName=="ModelError")
                    {
                        string strValidationMessage = ModelValidation();
                        if (!string.IsNullOrEmpty(strValidationMessage))
                        {
                            return strValidationMessage;
                        }
                    }
    
                    if(propertyName=="PersonName")
                    {
                        if(PersonName.Length>30 || PersonName.Length<1)
                        {
                            return "Name is required and less than 30 characters.";
                        }
                    }
                    else if (propertyName == "Age")
                    {
                        if (Age<10 || Age>50)
                        {
                            return "Age must between 10 and 50.";
                        }
                    }
                    return null;
                }
            }
    
            string IDataErrorInfo.Error
            {
                get { throw new NotImplementedException(); }
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected void NotifyPropertyChanged(string propertyName)
            {
                PropertyChangedEventHandler handler = PropertyChanged;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }

    And bind the model validation error like this:

    <TextBlock Name="textblockError" Text="{Binding ModelError, ValidatesOnDataErrors=True}" />

    This time, it works.

    Download the code here.

    Obviously, the code above is not good. How about making a base class? That sounds like a ideal. I made the model like that:

        class Person : ViewModelBase
        {
            [Required(ErrorMessage = "Cannot be null.")]
            [StringLength(30, ErrorMessage = "Name is required and less than 30 characters.")]
            public string PersonName
            {
                get { return GetValue(() => PersonName); }
                set { SetValue(() => PersonName, value); }
            }
    
            [Range(10, 50, ErrorMessage = "Age must between 10 and 50.")]
            public int Age
            {
                get { return GetValue(() => Age); }
                set { SetValue(() => Age, value); }
            }
    
            public override string ModelValidate()
            {
                if (PersonName == "Tom" && Age!=30)
                {
                    return "Tom must be 30";
                }
                return null;
            }
        }

    Then, add some style to make the error message more informative.

            <Style TargetType="{x:Type TextBox}">
                <Setter Property="Height" Value="23" />
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="True">
                        <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                    </Trigger>
                </Style.Triggers>
            </Style>

    It works again.

    Quite familiar with ASP.net MVCer? Download the code here.

  • 相关阅读:
    c语言 ,回调函数[个人理解]
    MFC、C++ 、Windows编程高手
    c++, 虚基派生 : 共同基类产生的二义性的解决办法
    c++,命名空间(namespace)
    c++,纯虚函数与抽象类
    c++ ,protected 和 private修饰的构造函数
    c++ 虚析构函数[避免内存泄漏]
    c++,虚函数
    c++,类的组合
    GPU与CPU的区别
  • 原文地址:https://www.cnblogs.com/guogangj/p/2843495.html
Copyright © 2020-2023  润新知