WPF MVVM 驗證

WPF MVVM(Caliburn.Micro) 數據驗證

書接前文html

前文中僅是WPF驗證中的一種,咱們暫且稱之爲View端的驗證(由於其驗證規是寫在Xaml文件中的)。git

還有一種咱們稱之爲Model端驗證,Model經過繼承IDataErrorInfo接口來實現,這個還沒研究透,後面補上。canvas

WPF MVVM Model端驗證-待續緩存

今天的主要內容是MVVM下的數據驗證,主要使用View端驗證,需求以下:mvvm

  • 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,被驗證的控件以下設計

<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事件,咱們通常在全部要驗證控件的最外層來註冊監聽這個事件code

這是基本思路,實現的方式就不少,就看誰的優雅。最直接的就是在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);
    }
}

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

相關文章
相關標籤/搜索