在MVVM中绑定Validation.HasError属性

我目前正在实现ValidationRule以检查TextBox中是否存在某些无效字符。 我很高兴设置我已实现的类inheritance我的TextBox上的ValidationRule ,当找到这些字符时将其设置为红色,但我还想使用Validation.HasError属性或Validation.Errors属性来弹出消息框告诉用户在页面中的各种文本框中有错误。

有没有办法将我的ViewModel的属性绑定到Validation.HasError和/或Validation.Errors属性,以便我可以在我的ViewModel中访问它们?

这是TextBox的错误样式:

              

以下是我在我的XAML中声明我的TextBox(OneTextBox封装常规WPF TextBox)的方法:

          

Validation.HasErrorreadonly属性,因此Binding不适用于此属性。 这可以在ILSpy看到:

 public virtual bool HasError { get { return this._validationError != null; } } 

作为替代方案,您应该看到一篇很棒的article ,它以使用附加依赖项属性的forms提供解决方案,您将看到该示例的详细说明。

下面是本文的完整示例,我只是在C#下翻译,原始语言是VB.NET

XAML

                           

Code-behind

 public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } } #region Model public class TestData : INotifyPropertyChanged { private bool _hasError = false; public bool HasError { get { return _hasError; } set { _hasError = value; NotifyPropertyChanged("HasError"); } } private string _testText = "0"; public string TestText { get { return _testText; } set { _testText = value; NotifyPropertyChanged("TestText"); } } #region PropertyChanged public event PropertyChangedEventHandler PropertyChanged; protected void NotifyPropertyChanged(string sProp) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(sProp)); } } #endregion } #endregion #region ValidationRule public class OnlyNumbersValidationRule : ValidationRule { public override ValidationResult Validate(object value, CultureInfo cultureInfo) { var result = new ValidationResult(true, null); string NumberPattern = @"^[0-9-]+$"; Regex rgx = new Regex(NumberPattern); if (rgx.IsMatch(value.ToString()) == false) { result = new ValidationResult(false, "Must be only numbers"); } return result; } } #endregion public class ProtocolSettingsLayout { public static readonly DependencyProperty MVVMHasErrorProperty= DependencyProperty.RegisterAttached("MVVMHasError", typeof(bool), typeof(ProtocolSettingsLayout), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceMVVMHasError)); public static bool GetMVVMHasError(DependencyObject d) { return (bool)d.GetValue(MVVMHasErrorProperty); } public static void SetMVVMHasError(DependencyObject d, bool value) { d.SetValue(MVVMHasErrorProperty, value); } private static object CoerceMVVMHasError(DependencyObject d,Object baseValue) { bool ret = (bool)baseValue; if (BindingOperations.IsDataBound(d,MVVMHasErrorProperty)) { if (GetHasErrorDescriptor(d)==null) { DependencyPropertyDescriptor desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType()); desc.AddValueChanged(d,OnHasErrorChanged); SetHasErrorDescriptor(d, desc); ret = System.Windows.Controls.Validation.GetHasError(d); } } else { if (GetHasErrorDescriptor(d)!=null) { DependencyPropertyDescriptor desc= GetHasErrorDescriptor(d); desc.RemoveValueChanged(d, OnHasErrorChanged); SetHasErrorDescriptor(d, null); } } return ret; } private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor", typeof(DependencyPropertyDescriptor), typeof(ProtocolSettingsLayout)); private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d) { var ret = d.GetValue(HasErrorDescriptorProperty); return ret as DependencyPropertyDescriptor; } private static void OnHasErrorChanged(object sender, EventArgs e) { DependencyObject d = sender as DependencyObject; if (d != null) { d.SetValue(MVVMHasErrorProperty, d.GetValue(Validation.HasErrorProperty)); } } private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value) { var ret = d.GetValue(HasErrorDescriptorProperty); d.SetValue(HasErrorDescriptorProperty, value); } } 

作为使用ValidationRule的替代方法,在MVVM样式中,您可以尝试实现IDataErrorInfo接口。 有关更多信息,请参阅:

Enforcing Complex Business Data Rules with WPF

回应Anatoliy对非工作项目示例的要求:

Generic.xaml

   

TextBoxCustomControl.cs

 using System.Windows; using System.Windows.Controls; namespace TestAttachedPropertyValidationError { public class TextBoxCustomControl : Control { static TextBoxCustomControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TextBoxCustomControl), new FrameworkPropertyMetadata(typeof(TextBoxCustomControl))); } public static readonly DependencyProperty NumericPropProperty = DependencyProperty.Register("NumericProp", typeof (int), typeof (TextBoxCustomControl), new PropertyMetadata(default(int))); public int NumericProp { get { return (int) GetValue(NumericPropProperty); } set { SetValue(NumericPropProperty, value); } } public static readonly DependencyProperty NumericPropHasErrorProperty = DependencyProperty.Register("NumericPropHasError", typeof (bool), typeof (TextBoxCustomControl), new PropertyMetadata(default(bool))); public bool NumericPropHasError { get { return (bool) GetValue(NumericPropHasErrorProperty); } set { SetValue(NumericPropHasErrorProperty, value); } } } } 

HasErrorUtility.cs

 using System; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace TestAttachedPropertyValidationError { class HasErrorUtility { public static readonly DependencyProperty HasErrorProperty = DependencyProperty.RegisterAttached("HasError", typeof(bool), typeof(HasErrorUtility), new FrameworkPropertyMetadata(false, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, null, CoerceHasError)); public static bool GetHasError(DependencyObject d) { return (bool)d.GetValue(HasErrorProperty); } public static void SetHasError(DependencyObject d, bool value) { d.SetValue(HasErrorProperty, value); } private static object CoerceHasError(DependencyObject d, Object baseValue) { var ret = (bool)baseValue; if (BindingOperations.IsDataBound(d, HasErrorProperty)) { if (GetHasErrorDescriptor(d) == null) { var desc = DependencyPropertyDescriptor.FromProperty(Validation.HasErrorProperty, d.GetType()); desc.AddValueChanged(d, OnHasErrorChanged); SetHasErrorDescriptor(d, desc); ret = Validation.GetHasError(d); } } else { if (GetHasErrorDescriptor(d) != null) { var desc = GetHasErrorDescriptor(d); desc.RemoveValueChanged(d, OnHasErrorChanged); SetHasErrorDescriptor(d, null); } } return ret; } private static readonly DependencyProperty HasErrorDescriptorProperty = DependencyProperty.RegisterAttached("HasErrorDescriptor", typeof(DependencyPropertyDescriptor), typeof(HasErrorUtility)); private static DependencyPropertyDescriptor GetHasErrorDescriptor(DependencyObject d) { var ret = d.GetValue(HasErrorDescriptorProperty); return ret as DependencyPropertyDescriptor; } private static void SetHasErrorDescriptor(DependencyObject d, DependencyPropertyDescriptor value) { d.SetValue(HasErrorDescriptorProperty, value); } private static void OnHasErrorChanged(object sender, EventArgs e) { var d = sender as DependencyObject; if (d != null) { d.SetValue(HasErrorProperty, d.GetValue(Validation.HasErrorProperty)); } } } } 

ViewModel.cs

 using System.ComponentModel; using System.Runtime.CompilerServices; namespace TestAttachedPropertyValidationError { public class ViewModel :INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; private int _vmNumericProp; private bool _vmNumericPropHasError; public int VmNumericProp { get { return _vmNumericProp; } set { _vmNumericProp = value; OnPropertyChanged(); } } public bool VmNumericPropHasError { get { return _vmNumericPropHasError; } set { _vmNumericPropHasError = value; OnPropertyChanged(); } } protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) { var handler = PropertyChanged; if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName)); } } } 

MainWindow.xaml