【问题标题】:Disable save button when validation fails验证失败时禁用保存按钮
【发布时间】:2012-11-14 20:23:13
【问题描述】:

正如您可能从标题中看到的那样,我要问一个以前被问过很多次的问题。但是,在阅读了所有这些其他问题之后,我仍然找不到解决问题的好方法。

我有一个带有基本验证的模型类:

partial class Player : IDataErrorInfo
{
    public bool CanSave { get; set; }

    public string this[string columnName]
    {
        get 
        { 
            string result = null;
            if (columnName == "Firstname")
            {
                if (String.IsNullOrWhiteSpace(Firstname))
                {
                    result = "Geef een voornaam in";
                }
            }
            if (columnName == "Lastname")
            {
                if (String.IsNullOrWhiteSpace(Lastname))
                {
                    result = "Geef een familienaam in";
                }
            }
            if (columnName == "Email")
            {
                try
                {
                    MailAddress email = new MailAddress(Email);
                }
                catch (FormatException)
                {
                    result = "Geef een geldig e-mailadres in";
                }
            }
            if (columnName == "Birthdate")
            {
                if (Birthdate.Value.Date >= DateTime.Now.Date)
                {
                    result = "Geef een geldige geboortedatum in";
                }
            }

            CanSave = true; // this line is wrong
            return result;
        }
    }

    public string Error { get { throw new NotImplementedException();} }
}

每次属性更改时都会执行此验证(因此每次用户在文本框中键入字符时):

<TextBox Text="{Binding CurrentPlayer.Firstname, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Width="137" IsEnabled="{Binding Editing}" Grid.Row="1"/>

这很完美。验证发生(PropertyChanged 绑定代码在 VM 中的 CurrentPlayer 属性上完成,该属性是 Player 的一个对象)。

我现在想做的是在验证失败时禁用保存按钮。

首先,最简单的解决方案似乎可以在这个帖子中找到:
Enable Disable save button during Validation using IDataErrorInfo

  1. 如果我想遵循公认的解决方案,我必须写我的 验证代码两次,因为我不能简单地使用索引器。写作 双重代码绝对不是我想要的,所以这不是解决方案 我的问题。
  2. 该线程上的第二个答案听起来很有希望, 但问题是我有多个字段必须是 验证。这样,一切都依赖于最后检查的属性 (因此,如果该字段填写正确,CanSave 将是真的,即使 虽然还有其他字段仍然无效)。

我发现的另一个解决方案是使用ErrorCount 属性。但是,由于我正在验证每个属性更改(以及每个键入的字符),这也是不可能的 - 我怎么知道何时增加/减少 ErrorCount

解决这个问题的最佳方法是什么?

谢谢

【问题讨论】:

  • 避免像这样编写所有的验证类,这很受欢迎。值得注意的是列名如何映射到带有错误结果的 if 块,因此可以为它们使用委托函数的映射;产生所有不必要的ifs。但是让我们假设您要使用它们,您在 if (A) 中执行 if (B),因此您可以改为使用 if (A &amp;&amp; B)。如果只执行if 中的一条语句,则不必键入{} 字符。通过使用地图,这个示例可以在 ~20 行而不是 ~50 行上;将您的开发速度提高一倍。

标签: c# wpf mvvm


【解决方案1】:

本文http://www.asp.net/mvc/tutorials/older-versions/models-%28data%29/validating-with-the-idataerrorinfo-interface-cs 将单个验证移到属性中:

public partial class Player : IDataErrorInfo
{
    Dictionary<string, string> _errorInfo;

    public Player()
    {
        _errorInfo = new Dictionary<string, string>();
    }

    public bool CanSave { get { return _errorInfo.Count == 0; }

    public string this[string columnName]
    {
        get 
        { 
            return _errorInfo.ContainsKey(columnName) ? _errorInfo[columnName] : null;
        }
    }

    public string FirstName
    {
        get { return _firstName;}
        set
        {
            if (String.IsNullOrWhiteSpace(value))
                _errorInfo.AddOrUpdate("FirstName", "Geef een voornaam in");
            else
            {
                _errorInfo.Remove("FirstName");
                _firstName = value;
            }
        }
    }
}

(您必须处理 Dictionary AddOrUpdate 扩展方法)。这类似于您的错误计数想法。

【讨论】:

  • 已编辑:_firstName 应在 else 子句中设置,否则您可以将对象设置为无效状态。这也消除了FirstName 的出现,这使得创建额外属性变得更加方便,因为需要替换的代码部分更少。
  • 谢谢,这似乎是一个不错的解决方案。它的问题是我的属性是在另一个部分类(生成的代码)中定义的,这些是自动属性。可以以某种方式应用此解决方案吗?
【解决方案2】:

我已经实现了my comment above 中显示的映射方法,在 C# 中这称为 Dictionary,我在其中使用 anonymous methods 进行验证:

partial class Player : IDataErrorInfo
{
    private delegate string Validation(string value);
    private Dictionary<string, Validation> columnValidations;
    public List<string> Errors;

    public Player()
    {
        columnValidations = new Dictionary<string, Validation>();
        columnValidations["Firstname"] = delegate (string value) {
            return String.IsNullOrWhiteSpace(Firstname) ? "Geef een voornaam in" : null;
        }; // Add the others...

        errors = new List<string>();
    }

    public bool CanSave { get { return Errors.Count == 0; } }

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

        set
        { 
            var error = columnValidations[columnName](value);

            if (String.IsNullOrWhiteSpace(error))
                errors.Add(error);
            else
                this.SetProperty(columnName, value);
        }
    }
}

【讨论】:

  • 谢谢,但我遇到了与下面的解决方案相同的问题......构造函数位于类的另一部分,它是生成的,所以我无法编写自己的构造函数.. .
  • 延迟加载setter中的列验证 public string this[string columnName] { get { return this[columnName]; } 设置 { if ( columnValidations == null ) { LoadColumnValidations(); } var 错误 = columnValidations[columnName](value); if (String.IsNullOrWhiteSpace(error)) errors.Add(error);否则 this[columnName] = value; } }
  • 嗯,谢谢,这是一个选择。但老实说,我真的不明白这个例子。索引器的 get 部分究竟返回了什么?那么什么会返回 this[columnName];真的回来了吗?
  • 但是 this[columnName] 到底指的是什么?
  • @Will:打算在那里使用getPropertysetProperty绕过运营商。
【解决方案3】:

此方法适用于数据注释。您还可以将“IsValid”属性绑定到保存按钮以启用/禁用。

public abstract class ObservableBase : INotifyPropertyChanged, IDataErrorInfo
{
    #region Members
    private readonly Dictionary<string, string> errors = new Dictionary<string, string>();
    #endregion

    #region Events

    /// <summary>
    /// Property Changed Event
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    #region Protected Methods

    /// <summary>
    /// Get the string name for the property
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression"></param>
    /// <returns></returns>
    protected string GetPropertyName<T>(Expression<Func<T>> expression)
    {
        var memberExpression = (MemberExpression) expression.Body;
        return memberExpression.Member.Name;
    }

    /// <summary>
    /// Notify Property Changed (Shorted method name)
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression"></param>
    protected virtual void Notify<T>(Expression<Func<T>> expression)
    {
        string propertyName = this.GetPropertyName(expression);
        PropertyChangedEventHandler handler = this.PropertyChanged;
        handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    /// <summary>
    /// Called when [property changed].
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="expression">The expression.</param>
    protected virtual void OnPropertyChanged<T>(Expression<Func<T>> expression)
    {
        string propertyName = this.GetPropertyName(expression);
        PropertyChangedEventHandler handler = this.PropertyChanged;

        handler?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    #endregion

    #region Properties

    /// <summary>
    /// Gets an error message indicating what is wrong with this object.
    /// </summary>
    public string Error => null;

    /// <summary>
    /// Returns true if ... is valid.
    /// </summary>
    /// <value>
    ///   <c>true</c> if this instance is valid; otherwise, <c>false</c>.
    /// </value>
    public bool IsValid => this.errors.Count == 0;

    #endregion

    #region Indexer

    /// <summary>
    /// Gets the <see cref="System.String"/> with the specified column name.
    /// </summary>
    /// <value>
    /// The <see cref="System.String"/>.
    /// </value>
    /// <param name="columnName">Name of the column.</param>
    /// <returns></returns>
    public string this[string columnName]
    {
        get
        {
            var validationResults = new List<ValidationResult>();
            string error = null;

            if (Validator.TryValidateProperty(GetType().GetProperty(columnName).GetValue(this), new ValidationContext(this) { MemberName = columnName }, validationResults))
            {
                this.errors.Remove(columnName);
            }
            else
            {
                error = validationResults.First().ErrorMessage;

                if (this.errors.ContainsKey(columnName))
                {
                    this.errors[columnName] = error;
                }
                else
                {
                    this.errors.Add(columnName, error);
                }
            }

            this.OnPropertyChanged(() => this.IsValid);
            return error;
        }
    }

    #endregion  
}

【讨论】:

    猜你喜欢
    • 2016-08-15
    • 2010-10-19
    • 2013-06-09
    • 2016-01-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-23
    相关资源
    最近更新 更多