在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.HasError
是readonly属性,因此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