保存按钮上的IDataErrorInfo错误消息框单击

时间:2015-12-03 13:55:31

标签: c# wpf idataerrorinfo

我正在构建一个 wpf 应用程序,我在我的类中实现了IDataErrorInfo接口。一切正常,textbox被正确绑定,带有错误消息的工具提示正确显示,边框在属性更改时变为红色。

但是,当我单击“保存”按钮(创建我的实体并将其保存到我的数据库中)时,即使我的文本框中的值错误,它也会将错误的值保存在数据库中。

过去我在属性设置器中使用了ArgumentException然后,在保存按钮单击时,会显示一个消息框,其中包含try / catch中的错误消息,但未保存错误的值。 / p>

是否有类似的方法来实现像try / catch一样的消息框但使用IDataErrorInfo

我不是在寻找复杂的解决方案,因为我是初学者,这是我第一次尝试 IDataErrorInfo

1 个答案:

答案 0 :(得分:2)

由于评论,我已将答案更新为更详细的答案。

首先, Newspeak是Ingsoc,Ingsoc是Newspeak XAML是MVVM,MVVM是XAML。

要编写与" Hello,world"不同的东西,您必须特别学习MVVM和命令。以下代码使用以下ICommand实现:

public sealed class RelayCommand : ICommand
{
    private readonly Action execute;
    private readonly Func<bool> canExecute;

    public RelayCommand(Action execute, Func<bool> canExecute)
    {
        this.execute = execute;
        this.canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public bool CanExecute(object parameter)
    {
        return canExecute();
    }

    public void Execute(object parameter)
    {
        execute();
    }
}

在实际应用程序中,WPF验证使用了一些众所周知的验证框架,并且几乎从未在需要验证的每个视图模型中手动实现。

以下使用data annotations在基类中实现的IDataErrorInfo验证示例:

public abstract class ViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
    {
        // updating property-bound controls
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        // updating command-bound controls like buttons
        CommandManager.InvalidateRequerySuggested();
    }

    private readonly ObservableCollection<ViewModelError> validationErrors = new ObservableCollection<ViewModelError>();

    private void RemoveValidationErrors(string propertyName)
    {
        var propertyValidationErrors = validationErrors
            .Where(_ => _.PropertyName == propertyName)
            .ToArray();

        foreach (var error in propertyValidationErrors)
        {
            validationErrors.Remove(error);
        }
    }

    private string ValidateProperty(string propertyName)
    {
        // we need localized property name
        var property = GetType().GetProperty(propertyName);
        var displayAttribute = property.GetCustomAttribute<DisplayAttribute>();
        var propertyDisplayName = displayAttribute != null ? displayAttribute.GetName() : propertyName;

        // since validation engine run all validation rules for property,
        // we need to remove validation errors from the previous validation attempt
        RemoveValidationErrors(propertyDisplayName);

        // preparing validation engine
        var validationContext = new ValidationContext(this, null, null) { MemberName = propertyName };
        var validationResults = new List<ValidationResult>();

        // running validation
        if (!Validator.TryValidateProperty(property.GetValue(this), validationContext, validationResults))
        {
            // validation is failed;
            // since there could be several validation rules per property, 
            // validation results can contain more than single error
            foreach (var result in validationResults)
            {
                validationErrors.Add(new ViewModelError(propertyDisplayName, result.ErrorMessage));
            }

            // to indicate validation error, it's enough to return first validation message
            return validationResults[0].ErrorMessage;
        }

        return null;
    }

    public IEnumerable<ViewModelError> ValidationErrors
    {
        get { return validationErrors; }
    }

    public string this[string columnName]
    {
        get { return ValidateProperty(columnName); }
    }

    public string Error
    {
        get { return null; }
    }
}

ViewModelError只是一个容器:

public sealed class ViewModelError
{
    public ViewModelError(string propertyName, string errorMessage)
    {
        PropertyName = propertyName;
        ErrorMessage = errorMessage;
    }

    public string PropertyName { get; private set; }
    public string ErrorMessage { get; private set; }
}

让我们看一下模型,它代表一些人物数据,相应的视图模型和视图:

a)模型

public class Person
{
    public Guid Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

b)查看模型

public class PersonViewModel : ViewModel
{
    private void HandleSave()
    {
        var person = new Person
        {
            Id = Guid.NewGuid(),
            Name = Name,
            Age = Age
        };

        // save person using database, web service, file...
    }

    private bool CanSave()
    {
        return !ValidationErrors.Any();
    }

    public PersonViewModel()
    {
        SaveCommand = new RelayCommand(HandleSave, CanSave);
    }

    [Display(Name = "Full name of person")]
    [Required(AllowEmptyStrings = false)]
    [MaxLength(50)]
    public string Name
    {
        get { return name; }
        set
        {
            if (name != value)
            {
                name = value;
                OnPropertyChanged();
            }
        }
    }
    private string name;

    [Display(Name = "Age of person")]
    [Range(18, 65)]
    public int Age
    {
        get { return age; }
        set
        {
            if (age != value)
            {
                age = value;
                OnPropertyChanged();
            }
        }
    }
    private int age;

    public ICommand SaveCommand { get; private set; }
}

c)视图(WPF窗口)

<Window x:Class="Wpf_IDataErrorInfoSample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Wpf_IDataErrorInfoSample"
        mc:Ignorable="d"
        Title="MainWindow" Height="300" Width="400">

    <Window.DataContext>
        <local:PersonViewModel />
    </Window.DataContext>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>

        <!-- Validation summary -->
        <ItemsControl ItemsSource="{Binding ValidationErrors}">
            <ItemsControl.ItemTemplate>
                <DataTemplate DataType="{x:Type local:ViewModelError}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding PropertyName}"/>
                        <TextBlock Text=" : "/>
                        <TextBlock Text="{Binding ErrorMessage}"/>
                    </StackPanel>
                </DataTemplate>
            </ItemsControl.ItemTemplate>
        </ItemsControl>

        <!-- Editable fields -->
        <StackPanel Grid.Row="1" Margin="4, 10, 4, 4">
            <!-- Name -->
            <TextBlock Text="Name:"/>
            <TextBox Text="{Binding Name, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}"/>

            <!-- Age -->
            <TextBlock Text="Age:"/>
            <Slider Minimum="0" Maximum="120" TickPlacement="BottomRight" Value="{Binding Age, ValidatesOnDataErrors=True}"/>
        </StackPanel>

        <Button Grid.Row="2" Content="Save" Command="{Binding SaveCommand}"/>
    </Grid>
</Window>

如果您运行此代码,初始图片将如下所示:

enter image description here

如您所见,如果存在验证错误,则会禁用按钮,并且您无法保存无效数据。如果您修复错误,按钮将变为启用状态,您可以单击它以保存数据:

enter image description here