将WPF ValidationRule的状态传递给MVVM中的View Model

Jat*_*tin 14 wpf prism mvvm

我陷入了一个看似普遍的要求.我有一个WPF Prism(用于MVVM)应用程序.我的模型实现了IDataErrorInfo以进行验证.该IDataErrorInfo的非数值属性的伟大工程.但是,对于数字属性,如果用户输入无效字符(非数字),则数据甚至不会到达模型,因为wpf无法将其转换为数字类型.

所以,我不得不使用WPF ValidationRule为用户提供一些有意义的无效数字条目消息.视图中的所有按钮都绑定到棱镜的DelegateCommand(在视图模型中),按钮的启用/禁用在View Model本身中完成.

现在,如果某个TextBox的wpf ValidationRule失败,如何将此信息传递给View Model,以便它可以适当地禁用视图中的按钮?

Pau*_*ler 10

对于MVVM,我更喜欢将附加属性用于此类事物,因为它们是可重用的并且它使视图模型保持干净.

为了将Validation.HasError属性绑定到视图模型,您必须创建一个附加属性,该属性具有CoerceValueCallback,该属性将附加属性的值与您正在验证用户输入的控件上的Validation.HasError属性同步.

文章解释了如何使用这种技术来解决,通知WPF有效性规则的错误的视图模型的问题.代码在VB中,所以如果你不是VB的话,我把它移植到C#.

附属物

public static class ValidationBehavior
{
    #region Attached Properties

    public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached(
        "HasError",
        typeof(bool),
        typeof(ValidationBehavior),
        new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceHasError));

    private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached(
        "HasErrorDescriptor",
        typeof(DependencyPropertyDescriptor),
        typeof(ValidationBehavior));

    #endregion

    private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d)
    {
        return (DependencyPropertyDescriptor)d.GetValue(HasErrorDescriptorProperty);
    }

    private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value)
    {
        d.SetValue(HasErrorDescriptorProperty, value);
    }

    #region Attached Property Getters and setters

    public static bool GetHasError(DependencyObject d)
    {
        return (bool)d.GetValue(HasErrorProperty);
    }

    public static void SetHasError(DependencyObject d, bool value)
    {
        d.SetValue(HasErrorProperty, value);
    }

    #endregion

    #region CallBacks

    private static object CoerceHasError(DependencyObject d, object baseValue)
    {
        var result = (bool)baseValue;
        if (BindingOperations.IsDataBound(d, HasErrorProperty))
        {
            if (GetHasErrorDescriptor(d) == null)
            {
                var desc = DependencyPropertyDescriptor.FromProperty(System.Windows.Controls.Validation.HasErrorProperty, d.GetType());
                desc.AddValueChanged(d, OnHasErrorChanged);
                SetHasErrorDescriptor(d, desc);
                result = System.Windows.Controls.Validation.GetHasError(d);
            }
        }
        else
        {
            if (GetHasErrorDescriptor(d) != null)
            {
                var desc = GetHasErrorDescriptor(d);
                desc.RemoveValueChanged(d, OnHasErrorChanged);
                SetHasErrorDescriptor(d, null);
            }
        }
        return result;
    }
    private static void OnHasErrorChanged(object sender, EventArgs e)
    {
        var d = sender as DependencyObject;
        if (d != null)
        {
            d.SetValue(HasErrorProperty, d.GetValue(System.Windows.Controls.Validation.HasErrorProperty));
        }
    }

    #endregion
}
Run Code Online (Sandbox Code Playgroud)

在XAML中使用附加属性

<Window
  x:Class="MySolution.MyProject.MainWindow"
  xmlns:v="clr-namespace:MyNamespace;assembly=MyAssembly">  
    <TextBox
      v:ValidationBehavior.HasError="{Binding MyPropertyOnMyViewModel}">
      <TextBox.Text>
        <Binding
          Path="ValidationText"
          UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <v:SomeValidationRuleInMyNamespace/>
          </Binding.ValidationRules>
        </Binding>
     </TextBox.Text>
  </TextBox>
</ Window >
Run Code Online (Sandbox Code Playgroud)

现在,视图模型上的属性将与文本框上的Validation.HasError同步.


Max*_*nce 7

从 .NET 4.5 开始,ValidationRule 重载了 Validate 方法:

public ValidationResult Validate(object value, CultureInfo cultureInfo,
    BindingExpressionBase owner)
Run Code Online (Sandbox Code Playgroud)

您可以覆盖它并通过以下方式获取视图模型:

public override ValidationResult Validate(object value, 
    CultureInfo cultureInfo, BindingExpressionBase owner)
{
    ValidationResult result = base.Validate(value, cultureInfo, owner);
    var vm = (YourViewModel)((BindingExpression)owner).DataItem;
    // ...
    return result;
}
Run Code Online (Sandbox Code Playgroud)

  • 也许我错过了一些东西,但是如何从视图中调用它?据我所知,设置绑定的验证规则默认使用抽象的 Validate 方法。 (2认同)
  • @Maxence 此链接不再可用。我和克里斯有同样的问题。潜力巨大,因为 ValidationRule 可以让 VM 使用 INotifyDataErrorInfo 执行验证。请更详细地解释如何强制重载 Validate() 运行?使用 ValidationRule 而不是仅仅绑定的好处是可以保留 VM 属性类型(例如 Integer),并将 ValidationRule 应用于视图控件类型(例如字符串)。可以在绑定引擎吞下异常之前执行验证,以便更新 VM 上的 HasError 属性。 (2认同)

小智 6

NIRVAN

解决此特定问题的最简单方法是使用数字文本框,以防止用户输入无效值(您可以通过第三方供应商执行此操作,或查找开源解决方案,例如从Textbox派生的类这会抑制非数字输入).

在没有执行上述操作的情况下在MVVM中处理此问题的第二种方法是在ViewModel中定义另一个字段,该字段是字符串,并将该字段绑定到文本框.然后,在字符串字段的setter中,您可以设置Integer,并为数字字段赋值:

这是一个粗略的例子:(注意我没有测试它,但它应该给你的想法)

// original field
private int _age;
int Age 
{
   get { return _age; }
   set { 
     _age = value; 
     RaisePropertyChanged("Age");
   }
}


private string _ageStr;
string AgeStr
{
   get { return _ageStr; }
   set { 
     _ageStr = value; 
     RaisePropertyChanged("AgeStr");
     if (!String.IsNullOrEmpty(AgeStr) && IsNumeric(AgeStr) )
         Age = intVal;
    }
} 

private bool IsNumeric(string numStr)
{
   int intVal;
   return int.TryParse(AgeStr, out intVal);
}

#region IDataErrorInfo Members

    public string this[string columnName]
    {
        get
        {

            if (columnName == "AgeStr" && !IsNumeric(AgeStr)
               return "Age must be numeric";
        }
    }

    #endregion
Run Code Online (Sandbox Code Playgroud)