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

    当然,如有错误,请大家斧正。

  • 相关阅读:
    在linux系统安装tomcat后,bin文件下startup.sh启动不
    利用教育邮箱注册JetBrains产品(pycharm、idea等)的方法
    linux 查看当前系统版本号
    windows 重装系统
    linux rpm方式安装mysql
    官网下载MySQL最新版本的安装包
    Redis随笔
    八大排序算法的java实现
    XML的两种解析方式
    Quartz快速入门
  • 原文地址:https://www.cnblogs.com/sjqq/p/8456911.html
Copyright © 2020-2023  润新知