使用IDataErrorInfo在更新数据源之前对WPF表单进行验证

6
我正在尝试在C# / WPF / Entity Framework 4.0应用程序中实现优雅的表单验证。我有一个经典的表单(一些文本框和复选框)和一个保存按钮。
我只想在用户按下保存时更新源(和数据库),并且我也只想在用户按下保存时运行表单验证。
我使用参数UpdateSourceTrigger=Explicit设置了所有绑定。我还将所有绑定放置在BindingGroup中。
当用户按下保存时,我触发绑定组的UpdateSources方法-这会触发每个绑定上的UpdateSource。在此时点(在源更新完成之前),我希望进行表单验证,并在GUI中突出显示错误。如果没有输入错误,则可以自由地进行更新。
我认为我可以通过在绑定到字段的EntityObject上实现IDataErrorInfo并在所有绑定上设置参数ValidatesOnDataErrors=True来实现这一点。
不幸的是,这样做不起作用,因为正如这里所解释的那样: MSDN数据绑定概述-数据验证 在标题“验证过程”下:

5)绑定引擎设置源属性。

6)......这是设置了ValidatesOnDataErrors为true的绑定进行检查的时刻。

这对我来说似乎非常愚蠢-为什么要在数据已经“提交”到对象后再验证数据?我已经搜索了几个小时,想找到一个方法来获得我想要的行为...有人以前做过类似的事情吗?
因此,主要问题是: 如何在更新源之前验证输入并在验证失败时取消更新?
1个回答

2

值必须提交到对象中,因为IDataErrorInfo仅使用propertyName检索特定属性的错误。无法将建议的值(应该进行验证)传递给它,因此只能使用已提交的属性值。

我认为这是一个很好的方法,因为视图模型和视图始终保持同步,即使属性具有无效值,也会在视图模型中保留无效值状态,因此可以在视图模型中包含基于该信息的其他逻辑,而不是视图。

如果您想将建议的值验证传播到视图模型,则必须使用自己的自定义接口和验证规则来完成。

以下是我完成的方式:

IProposedValueErrorInfo.cs

using System.Globalization;

namespace WpfApplication
{
    public interface IProposedValueErrorInfo
    {
        object GetError(string propertyName, object value, CultureInfo cultureInfo);
    }
}

ProposedValueErrorValidationRule.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;

namespace WpfApplication
{
    internal sealed class ProposedValueErrorValidationRule : ValidationRule
    {
        private readonly DependencyObject targetObject;
        private readonly DependencyProperty targetProperty;

        public ProposedValueErrorValidationRule(DependencyObject targetObject, DependencyProperty targetProperty)
            : base(ValidationStep.RawProposedValue, true)
        {
            if (targetObject == null)
                throw new ArgumentNullException("targetObject");
            if (targetProperty == null)
                throw new ArgumentNullException("targetProperty");

            this.targetObject = targetObject;
            this.targetProperty = targetProperty;
        }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            var expression = BindingOperations.GetBindingExpression(this.targetObject, this.targetProperty);
            if (expression != null)
            {
                var sourceItem = expression.DataItem as IProposedValueErrorInfo;
                if (sourceItem != null)
                {
                    var propertyName = expression.ParentBinding.Path != null ? expression.ParentBinding.Path.Path : null;
                    if (propertyName != null)
                    {
                        var error = sourceItem.GetError(propertyName, value, cultureInfo);
                        if (error != null)
                            return new ValidationResult(false, error);
                    }
                }
            }
            return ValidationResult.ValidResult;
        }
    }
}

ProposedValueValidationBindingExtension.cs

using System;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;

namespace WpfApplication
{
    public sealed class ProposedValueValidationBindingExtension : MarkupExtension
    {
        private readonly Binding binding;

        public ProposedValueValidationBindingExtension(Binding binding)
        {
            if (binding == null)
                throw new ArgumentNullException("binding");

            this.binding = binding;
        }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            var provideValueTarget = serviceProvider != null ? serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget : null;
            if (provideValueTarget != null)
                this.binding.ValidationRules.Add(new ProposedValueErrorValidationRule(provideValueTarget.TargetObject as DependencyObject, provideValueTarget.TargetProperty as DependencyProperty));

            return this.binding.ProvideValue(serviceProvider);
        }
    }
}

Person.cs

using System.Globalization;

namespace WpfApplication
{
    public class Person : IProposedValueErrorInfo
    {
        public int Age { get; set; }
        public string Surname { get; set; }

        #region IProposedValueErrorInfo Members

        object IProposedValueErrorInfo.GetError(string propertyName, object value, CultureInfo cultureInfo)
        {
            switch (propertyName)
            {
                case "Age":
                    int dummy;
                    return value is int || int.TryParse(value as string, NumberStyles.Integer, cultureInfo, out dummy) ? null : "Age must be a number.";
            }

            return null;
        }

        #endregion
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:WpfApplication"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:Person Age="16"/>
    </Window.DataContext>
    <StackPanel>
        <TextBox Text="{local:ProposedValueValidationBinding {Binding Age}}" ToolTip="{Binding Path='(Validation.Errors)/ErrorContent', RelativeSource={RelativeSource Self}}"/>
        <TextBox Text="{local:ProposedValueValidationBinding {Binding Age}}" ToolTip="{Binding Path='(Validation.Errors)/ErrorContent', RelativeSource={RelativeSource Self}}"/>
    </StackPanel>
</Window>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接