WPF MVVM(Caliburn.Micro) 数据验证

书接前文html

前文中仅是WPF验证中的一种,咱们暂且称之为View端的验证(由于其验证规是写在Xaml文件中的)。django

还有一种咱们称之为Model端验证,Model经过继承IDataErrorInfo接口来实现,这个还没研究透,后面补上。canvas

WPF MVVM Model端验证-待续swift

今天的主要内容是MVVM下的数据验证,主要使用View端验证,需求以下:缓存

  • 1.对姓名的非空验证,验证错误控件后边应该有感叹号提示,感叹号的ToolTip应该有具体错误的信息
  • 2.对姓名的非空验证不经过的话,肯定 按钮应该禁用

对于1,控件自己验证不经过会有一个红色的边框,后面的感叹号咱们用Adorner来实现,且看这篇ide

WPF Adorner+附加属性 实现控件友好提示ui

很差处理的是2,为何呢?在Mvvm中,咱们故意分离View和VM,View只负责显示,VM负责各类交互逻辑,VM应该感知不到View的存在,而各类验证(无论你是VIew端验证仍是Model端验证)产生的Validation.ErrorEvent冒泡事件只会沿着逻辑树上走,咱们就是须要监听这个事件,有了这个事件咱们的VM才能知道验证不经过,从而修改属性来达到禁用按钮的目的。也就是说View和VM之间除了传统的Binding和Command以外,还应该有一条通道,从View通知到VM的通道。this

这里补充一下Validation.ErrorEvent,被验证的控件以下spa

 <TextBox Grid.Row="0" Grid.Column="1" Height="25" VerticalContentAlignment="Center"> <TextBox.Text> <Binding Path="IdentityName" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True"> <Binding.ValidationRules> <validationRules:RequiredRule ValidatesOnTargetUpdated="True"></validationRules:RequiredRule> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox>

须要把NotifyOnValidationError设置为True才可以产生Validation.ErrorEvent事件,咱们通常在全部要验证控件的最外层来注册监听这个事件设计

这是基本思路,实现的方式就不少,就看谁的优雅。最直接的就是在View的后台代码中直接注册监听这个事件,而后验证事件触发的时候,将
这个View的DataContext转成咱们对应的ViewModel,就能够直接操做了。这样作也行,可是后面的项目基本会累死,由于这些都是体力活。
各类百度(百度基本没什么用),最后使用Bing搜索到一个老外写的代码
很是6,参考以后,决定改造一下。

先讲一下思路,继承WPF中的Behavior,取名ValidationExceptionBehavior,这个Behavior负责注册监听Validation.ErrorEvent事件,而且将验证结果通知到ViewModel
,要可以通用的话,必然ValidationExceptionBehavior不知道ViewModel的具体类型,因而咱们设计了一个接口IValidationExceptionHandler,须要接受到来自view的验证结果
的ViewModel就须要实现这个接口。因此对于View,咱们只须要在最外层容器加入下面一行代码

 <i:Interaction.Behaviors> <behaviors:ValidationExceptionBehavior></behaviors:ValidationExceptionBehavior> </i:Interaction.Behaviors>

对于ViewModel,咱们只须要实现接口

[Export] public class BaseInfoConfigViewModel : Screen, IValidationExceptionHandler { public bool IsValid { get { return _isValid; } set { if (value == _isValid) return; _isValid = value; NotifyOfPropertyChange(() => IsValid); } } }

该接口只有一个属性,就是IsValid,验证是否有效,经过这个属性,就能够在ViewModel中随心所欲了。好吧,讲这么多不如上代码,上Demo。

IValidationExceptionHandler.cs

/// <summary> /// 验证异常处理接口,由VM来继承实现 /// </summary> public interface IValidationExceptionHandler { /// <summary> /// 是否有效 /// </summary> bool IsValid { get; set; } }

ValidationExceptionBehavior.cs

/// <summary> /// 验证行为类,能够得到附加到的对象 /// </summary> public class ValidationExceptionBehavior : Behavior<FrameworkElement> { #region 字段 /// <summary> /// 错误计数器 /// </summary> private int _validationExceptionCount = 0; private Dictionary<UIElement, NotifyAdorner> _adornerCache; #endregion #region 方法 #region 重写方法 /// <summary> /// 附加对象时 /// </summary> protected override void OnAttached() { _adornerCache = new Dictionary<UIElement, NotifyAdorner>(); //附加对象时,给对象增长一个监听验证错误事件的能力,注意该事件是冒泡的 this.AssociatedObject.AddHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(this.OnValidationError)); } #endregion #region 私有方法 #region 获取实现接口的对象 /// <summary> /// 获取对象 /// </summary> /// <returns></returns> private IValidationExceptionHandler GetValidationExceptionHandler() { if (this.AssociatedObject.DataContext is IValidationExceptionHandler) { var handler = this.AssociatedObject.DataContext as IValidationExceptionHandler; return handler; } return null; } #endregion #region 显示Adorner /// <summary> /// 显示Adorner /// </summary> /// <param name="element"></param> /// <param name="errorMessage"></param> private void ShowAdorner(UIElement element, string errorMessage) { NotifyAdorner adorner = null; //先去缓存找 if (_adornerCache.ContainsKey(element)) { adorner = _adornerCache[element]; //找到了,修改提示信息 adorner.ChangeToolTip(errorMessage); } //没有找到,那就New一个,加入到缓存 else { adorner = new NotifyAdorner(element, errorMessage); _adornerCache.Add(element, adorner); } //将Adorner加入到 if (adorner != null) { var adornerLayer = AdornerLayer.GetAdornerLayer(element); adornerLayer.Add(adorner); } } #endregion #region 移除Adorner /// <summary> /// 移除Adorner /// </summary> /// <param name="element"></param> private void HideAdorner(UIElement element) { //移除Adorner if (_adornerCache.ContainsKey(element)) { var adorner = _adornerCache[element]; var adornerLayer = AdornerLayer.GetAdornerLayer(element); adornerLayer.Remove(adorner); } } #endregion #region 验证事件方法 /// <summary> /// 验证事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void OnValidationError(object sender, ValidationErrorEventArgs e) { try { var handler = GetValidationExceptionHandler(); var element = e.OriginalSource as UIElement; if (handler == null || element == null) return; if (e.Action == ValidationErrorEventAction.Added) { _validationExceptionCount++; ShowAdorner(element, e.Error.ErrorContent.ToString()); } else if (e.Action == ValidationErrorEventAction.Removed) { _validationExceptionCount--; HideAdorner(element); } handler.IsValid = _validationExceptionCount == 0; } catch (Exception ex) { throw ex; } } #endregion #endregion #endregion }

NotifyAdorner.cs

/// <summary> /// 提示Adorner /// </summary> public class NotifyAdorner : Adorner { private VisualCollection _visuals; private Canvas _canvas; private Image _image; private TextBlock _toolTip; /// <summary> /// 构造 /// </summary> /// <param name="adornedElement"></param> /// <param name="errorMessage"></param> public NotifyAdorner(UIElement adornedElement, string errorMessage) : base(adornedElement) { _visuals = new VisualCollection(this); BuildNotifyStyle(errorMessage); _canvas = new Canvas(); _canvas.Children.Add(_image); _visuals.Add(_canvas); } private void BuildNotifyStyle(string errorMessage) { _image = new Image() { Width = 20, Height = 20, Source = new BitmapImage(new Uri("你的图片路径", UriKind.Absolute)) }; _toolTip = new TextBlock() { FontSize = 14, Text = errorMessage }; _image.ToolTip = _toolTip; } protected override int VisualChildrenCount { get { return _visuals.Count; } } protected override Visual GetVisualChild(int index) { return _visuals[index]; } public void ChangeToolTip(string errorMessage) { _toolTip.Text = errorMessage; } protected override Size MeasureOverride(Size constraint) { return base.MeasureOverride(constraint); } protected override Size ArrangeOverride(Size finalSize) { _canvas.Arrange(new Rect(finalSize)); _image.Margin = new Thickness(finalSize.Width + 2, 0, 0, 0); return base.ArrangeOverride(finalSize); } }

固然,若有错误,请你们斧正。

相关文章
相关标签/搜索