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); } }

固然,若有錯誤,請你們斧正。

相關文章
相關標籤/搜索