书接前文

前文中仅是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);
}
}

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

WPF MVVM(Caliburn.Micro) 数据验证的更多相关文章

  1. WPF +MVVM(Caliburn.Micro)项目框架

    最近做了一个软件,这个软件不是网站,但是与HTML,AJAX等技术密切相关,也不是只有单纯的数据库增删改查,还涉及到线程协调,比较复杂的文本处理…… 这样的软件,用OA,ERP的框架显然是不合适的,因 ...

  2. MVVM Caliburn.Micro学习记录

    wpf中MVVM一直用的自己写的框架,最近试了试Caliburn.Micro. Caliburn.Micro可以通过x:name来进行属性和事件绑定. 比如 <Button x:Name=&qu ...

  3. WPF 使用Caliburn.Micro 多线程打开窗口

    我们都知道在WPF里面用多线程打开一个窗口很简单.如下 public void ClickMe(object sender) { Thread newWindowThread = new Thread ...

  4. [WPF] 让第一个数据验证出错(Validation.HasError)的控件自动获得焦点

    1. 需求 在上一篇文章 <在 ViewModel 中让数据验证出错(Validation.HasError)的控件获得焦点>中介绍了如何让 Validation.HasError 的控件 ...

  5. WPF MVVM 验证

    WPF MVVM(Caliburn.Micro) 数据验证 书接前文 前文中仅是WPF验证中的一种,我们暂且称之为View端的验证(因为其验证规是写在Xaml文件中的). 还有一种我们称之为Model ...

  6. WPF数据验证方式

    WPF有两种数据验证的方式: 1 在数据对象上进行验证:普通属性验证或者实现IDataErrorInfo接口 2 可以再绑定规则上进行验证:ExceptionValidationRule异常验证规则 ...

  7. 从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器

    从0到1:使用Caliburn.Micro(WPF和MVVM)开发简单的计算器 之前时间一直在使用Caliburn.Micro这种应用了MVVM模式的WPF框架做开发,是时候总结一下了. Calibu ...

  8. WPF MVVM从入门到精通8:数据验证

    原文:WPF MVVM从入门到精通8:数据验证 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM从入门到精通3:数据绑定 WPF M ...

  9. 【WPF】数据验证

    原文:[WPF]数据验证 引言      数据验证在任何用户界面程序中都是不可缺少的一部分.在WPF中,数据验证更是和绑定紧紧联系在一起,下面简单介绍MVVM模式下常用的几种验证方式. 错误信息显示 ...

随机推荐

  1. JAVA学习笔记 -- 读写XML

    XML是一种可扩展标记语言 以下是一个完整的XML文件(也是下文介绍读写XML的样本): <? xml version="1.0" encoding="UTF-8& ...

  2. PyPy与VirtualEnv的安装问题

    PyPy与VirtualEnv的安装问题 说明:本博客由bitpeach原创撰写,请勿商用.转载免费,请注明出处,谢谢. (零)背景 VirtualEnv工具的详细内容是什么,请自行百度.这里大概简介 ...

  3. Intellij idea 快键键

    1.快速进入实现类: Command + Alt+B 2.全局查找: Shift + Shift 3.类继承关系: Ctrl + H 4.在继承层次上跳转则用 Command + B / Comman ...

  4. Hibernate 入门示例

    版权声明:本文为博主原创文章,如需转载请标注转载地址 博客地址:http://www.cnblogs.com/caoyc/p/5593406.html  环境: myelipse2015+Hibern ...

  5. linux route命令使用

    说明:route命令是打印和操作ip路由表描述:route操作基于内核ip路由表,它的主要作用是创建一个静态路由让指定一个主 机或者一个网络通过一个网络接口,如eth0.当使用"add&qu ...

  6. 解决Jquery Ajax提交 服务器端接收中文乱码问题

    看到有朋友说到用post提交方式解决,我指定了methord="post",仍然解决不了, 说一下解决办法,客户端进行编码,服务器端解码, 客户端:var where = esca ...

  7. 电子商务(电销)平台中用户模块(User)数据库设计明细(转载)

    电子商务(电销)平台中用户模块(User)数据库设计明细 以下是自己在电子商务系统设计中的订单模块的数据库设计经验总结,而今发表出来一起分享,如有不当,欢迎跟帖讨论~ 用户基础表(user_base) ...

  8. ASP.NET MVC 简单的分页思想与实现

    首先我们通过VS创建一个空的基于Razor视图引擎的ASP.NET MVC3 Web应用程序,命名为JohnConnor.Web 对创建过程或Razor不太了解的看官,请移步 ASP.NET MVC ...

  9. redis安装和配置(一)

    Redis 的官方下载站是http://redis.io/download 怎么安装 Redis 数据库呢?下面将介绍Linux 版本的安装方法 步骤一: 下载Redis 下载安装包:wget htt ...

  10. NOPcommerce研究

    http://www.cnblogs.com/gusixing/archive/2012/04/07/2435873.html