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

IDataErrorInfo error messagebox on save button click

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

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

过去,我在 属性 setter 中使用了 ArgumentException,然后在单击保存按钮时,会显示一个消息框,其中包含 try/catch 中的错误消息,而我的没有保存错误的值。

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

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

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

首先,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 验证会使用一些众所周知的验证框架,并且几乎从未在您需要验证的每个视图模型中手动实现。

这是 IDataErrorInfo 验证的示例,在基础 class 中实现,使用 data annotations:

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)

<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>

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

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