WPF MVVM 驗證


WPF MVVM(Caliburn.Micro) 數據驗證#

書接前文

前文中僅是WPF驗證中的一種,我們暫且稱之為View端的驗證(因為其驗證規是寫在Xaml文件中的)。

還有一種我們稱之為Model端驗證,Model通過繼承IDataErrorInfo接口來實現,這個還沒研究透,后面補上。

WPF MVVM Model端驗證-待續

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

  • 1.對姓名的非空驗證,驗證錯誤控件后邊應該有感嘆號提示,感嘆號的ToolTip應該有具體錯誤的信息
  • 2.對姓名的非空驗證不通過的話,確定 按鈕應該禁用

對於1,控件本身驗證不通過會有一個紅色的邊框,后面的感嘆號我們用Adorner來實現,且看這篇

WPF Adorner+附加屬性 實現控件友好提示

不好處理的是2,為什么呢?在Mvvm中,我們故意分離View和VM,View只負責顯示,VM負責各種交互邏輯,VM應該感知不到View的存在,而各種驗證(不管你是VIew端驗證還是Model端驗證)產生的Validation.ErrorEvent冒泡事件只會沿着邏輯樹上走,我們就是需要監聽這個事件,有了這個事件我們的VM才能知道驗證不通過,從而修改屬性來達到禁用按鈕的目的。也就是說View和VM之間除了傳統的Binding和Command之外,還應該有一條通道,從View通知到VM的通道。

這里補充一下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事件,我們一般在所有要驗證控件的最外層來注冊監聽這個事件

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

當然,如有錯誤,請大家斧正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM