【问题标题】:IDataErrorInfo based validation not working基于 IDataErrorInfo 的验证不起作用
【发布时间】:2012-07-25 11:47:47
【问题描述】:

编辑:这是对本文原始版本的简化更新。

在 WPF 中,我实现了一个 UserControl(称为“NumericTextBox”),它使用与 Text 属性保持同步的 *DependencyProperty 'Value' >文本框(xaml):

<TextBox.Text>
  <Binding Path="Value" 
           Mode="TwoWay"
           ValidatesOnDataErrors="True"
           NotifyOnValidationError="True"
           UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>

出于验证目的,我使用 IDataErrorInfo 接口 (xaml.cs):

public partial class NumericTextbox : Textbox, IDataErrorInfo {
    public double Value {
        get { return (double)GetValue(ValueProperty); }
        set { SetValue(ValueProperty, value); }
    }

    public static readonly DependencyProperty ValueProperty =
        DependencyProperty.Register("Value", typeof(double),  
                                    typeof(NumericTextBox), 
                                    new PropertyMetadata(default(double)));

    public string this[string columnName]
    {
        // Never gets called!
        get { /* Some validation rules here */ }
    }
}

如源代码中所述,get 属性实际上永远不会被调用,因此不会发生验证。您看到问题的原因了吗?

Edit2:根据伦理逻辑的回答,我重新构建了我的代码。 NumericTextBox 现在使用底层视图模型类,该类提供绑定到 TextBoxText 属性的依赖属性 Value em> 由 NumericTextBox 声明。此外,NumericTextBox 使用视图模型作为其数据上下文。 viewmodel 现在负责检查 Value 属性的变化。由于 NumericTextBox 的值限制是可自定义的(例如可以调整最小值),它会将这些设置转发给 viewmodel 对象。

【问题讨论】:

  • 你的问题到底是什么?
  • 抱歉,刚刚更新了帖子。
  • 所以你想检查用户是否只写数字?我能理解你的问题吗?
  • 还有一些附加条件...
  • 嗨,为什么您希望该属性仅是依赖属性。为什么不能将验证应用于绑定到 TextBox 的 Text 属性的 ViewModel 属性

标签: c# wpf validation xaml idataerrorinfo


【解决方案1】:

这样做而不是创建任何依赖属性。验证应用于 ViewModel 而不是 Control 或 View 。试试这样,希望对您有所帮助。

public class MyViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    public MyViewModel()
    {
        Value = 30;
    }
    private double _value;

    [Range(1, 80, ErrorMessage = "out of range")]
    public double Value
    {
        get
        {
            return _value;
        }
        set
        {
            _value = value;
            ValidationMessageSetter("Value", value);
        }
    }

    private void ValidationMessageSetter(string propertyName, object value)
    {
        Notify(propertyName);
        string validationresult = ValidateProperty(propertyName, value);
        if (!string.IsNullOrEmpty(validationresult) && !_dataErrors.ContainsKey(propertyName))
            _dataErrors.Add(propertyName, validationresult);
        else if (_dataErrors.ContainsKey(propertyName))
                _dataErrors.Remove(propertyName);

    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    private void Notify(string str)
    { 
        if(PropertyChanged!=null)
            PropertyChanged(this,new PropertyChangedEventArgs(str));
    }

    private string ValidateProperty(string propertyName,object value)
    {
        var results = new List<ValidationResult>(2);
        string error = string.Empty;

        bool result = Validator.TryValidateProperty(
            value,
            new ValidationContext(this, null, null)
            {
                MemberName = propertyName
            },
            results);

        if (!result && (value == null || ((value is int || value is long) && (int)value == 0) || (value is decimal && (decimal)value == 0)))
            return null;

        if (!result)
        {
            ValidationResult validationResult = results.First();
            error = validationResult.ErrorMessage;
        }

        return error;    
    }

    #region IDataErrorInfo Members

    private Dictionary<string, string> _dataErrors = new Dictionary<string, string>();

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

    public string this[string columnName]
    {
        get
        {
            if (_dataErrors.ContainsKey(columnName))
                return _dataErrors[columnName];
            else
                return null;
        }
    }

    #endregion
}

<TextBox Text="{Binding Value, Mode=TwoWay, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>

我希望这会有所帮助。

【讨论】:

  • 我只是简化了代码。现在我使用一种受您的帖子启发的方法,并且在许多教程中都有描述(但在我的程序中仍然失败)。还有什么提示吗?
  • 您好,我已经更新了上面的答案。这很好,我希望这能帮助你给出一个想法。
  • 感谢您采用这种方法。它帮助我开发了一个可行的解决方案(参见第一篇文章)。实际上仍然可以使用依赖属性。我这样做有两个原因: 1. 使用 T4 模板可以大大减少代码量。 2. 如this article 所述,它应该更高效。
【解决方案2】:

IDataErrorInfo 接口应该在被绑定的对象上实现,而不是在具有DependencyProperty 的对象上实现。

在您的示例中,如果您想使用此机制进行验证,那么您的视图模型需要为 Value 属性执行以下操作:

public class ViewModel : IDataErrorInfo
{
    public string this[string columnName]
    {
        // Never gets called!
        get
        { 
            if (columnName == "Value")
                return GetValidationMessageForValueField();

            return null;
        }
    }
}

我猜你真正想要做的是验证某人何时在TextBox..中输入了一个非数字值?如果是这种情况,您可能希望采用与使用 IDataErrorInfo 不同的方法

【讨论】:

  • 我刚刚做了,其他教程也做了(除了我的属性实际上是一个依赖属性)。例如。看看codeblitz.wordpress.com/2009/05/08/…
  • 不同之处在于,在该教程中,Customer 类是视图模型。在您的示例中,您正在创建一个 Control(因为您继承自 TextBox
  • 好的,因为我完全不知道视图模型实际上是什么以及如何使用它,我将尝试使用另一种方法:codeproject.com/Articles/18678/…
  • 好的。出于兴趣,您实际上想要实现什么行为?
  • 我想要一个用于数字输入的文本框。所以我只想接受数字作为输入。因此,如果输入不是有效数字、超出某些边界( 特定阈值)或不是某个预定义值的倍数(例如因此限制小数位数),则应警告用户。然后这个文本框将与一个滑块组合在一起。
猜你喜欢
  • 1970-01-01
  • 2013-05-29
  • 1970-01-01
  • 2012-09-06
  • 2018-01-01
  • 2020-03-13
  • 1970-01-01
  • 2021-08-22
  • 2011-04-12
相关资源
最近更新 更多