WPF MVVM 验证
WPF MVVM(Caliburn.Micro) 数据验证#
前文中仅是WPF验证中的一种,我们暂且称之为View端的验证(因为其验证规是写在Xaml文件中的)。
还有一种我们称之为Model端验证,Model通过继承IDataErrorInfo接口来实现,这个还没研究透,后面补上。
今天的主要内容是MVVM下的数据验证,主要使用View端验证,需求如下:
- 1.对姓名的非空验证,验证错误控件后边应该有感叹号提示,感叹号的ToolTip应该有具体错误的信息
- 2.对姓名的非空验证不通过的话,确定 按钮应该禁用
对于1,控件本身验证不通过会有一个红色的边框,后面的感叹号我们用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);
}
}
.pro_name a{color: #4183c4;}
.osc_git_title{background-color: #d8e5f1;}
.osc_git_box{background-color: #fafafa;}
.osc_git_box{border-color: #ddd;}
.osc_git_info{color: #666;}
.osc_git_main a{color: #4183c4;}
当然,如有错误,请大家斧正。
WPF MVVM 验证的更多相关文章
- WPF mvvm 验证,耗时两天的解决方案
常用类 类名 介绍 ValidationRule 所有自定义验证规则的基类.提供了让用户定义验证规则的入口. ExceptionValidation 表示一个规则,该规则检查在绑定源属性更新过程中引发 ...
- WPF MVVM从入门到精通8:数据验证
原文:WPF MVVM从入门到精通8:数据验证 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM从入门到精通3:数据绑定 WPF M ...
- WPF MVVM(Caliburn.Micro) 数据验证
书接前文 前文中仅是WPF验证中的一种,我们暂且称之为View端的验证(因为其验证规是写在Xaml文件中的). 还有一种我们称之为Model端验证,Model通过继承IDataErrorInfo接口来 ...
- WPF MVVM使用prism4.1搭建
WPF MVVM使用prism4.1搭建 MVVM即Model-View-ViewModel,MVVM模式与MVP(Model-View-Presenter)模式相似,主要目的是分离视图(View)和 ...
- ViewModel从未如此清爽 - 轻量级WPF MVVM框架Stylet
Stylet是我最近发现的一个WPF MVVM框架, 在博客园上搜了一下, 相关的文章基本没有, 所以写了这个入门的文章推荐给大家. Stylet是受Caliburn Micro项目的启发, 所以借鉴 ...
- WPF MVVM 架构 Step By Step(6)(把actions从view model解耦)
到现在为止,我们创建了一个简单的MVVM的例子,包含了实现了的属性和命令.我们现在有这样一个包含了例如textbox类似的输入元素的视图,textbox用绑定来和view model联系,像点击but ...
- 转载:WPF MVVM之INotifyPropertyChanged接口的几种实现方式
原文地址:http://www.cnblogs.com/xiwang/ 序言 借助WPF/Sliverlight强大的数据绑定功能,可以比实现比MFC,WinForm更加优雅轻松的数据绑定.但是在使用 ...
- WPF MVVM从入门到精通6:RadioButton等一对多控件的绑定
原文:WPF MVVM从入门到精通6:RadioButton等一对多控件的绑定 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM ...
- WPF MVVM从入门到精通7:关闭窗口和打开新窗口
原文:WPF MVVM从入门到精通7:关闭窗口和打开新窗口 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM从入门到精通3:数据绑定 ...
随机推荐
- JQuery实现一个简单的鼠标跟随提示效果
效果体验:http://hovertree.com/texiao/jsstudy/2/ 实现思路 1 鼠标移入标题(这里是<a>标签) 创建一个div,div的内容为鼠标位置的文本 将创建 ...
- Day Tips:alertstemplates.xml
1.修改Alert邮件模板,首先要注意的时候最好复制一份alertstemplates.xml,然后改一下名字,我改的是 custom_alertstemplates.xml,修改自己需要的代码部分. ...
- 熟悉HTML CSS布局模型
HTML最难的地方来了!这个我反复了很多遍, 包括现在写博客, 也对我自己算是一种温习, 我这块怕是没办法写的很好懂, 因为我自己还不能把我学到的准确通俗易懂的表达出来, 给自己记个笔记, 以后再来一 ...
- iOS 最新版 CocoaPods 的安装流程
iOS 最新版 CocoaPods 的安装流程1.移除现有Ruby默认源$gem sources --remove https://rubygems.org/2.使用新的源$gem sources - ...
- 【转】JavaScript中的原型和继承
请在此暂时忘记之前学到的面向对象的一切知识.这里只需要考虑赛车的情况.是的,就是赛车. 最近我正在观看 24 Hours of Le Mans ,这是法国流行的一项赛事.最快的车被称为 Le Mans ...
- [Java编程思想-学习笔记]第3章 操作符
3.1 更简单的打印语句 学习编程语言的通许遇到的第一个程序无非打印"Hello, world"了,然而在Java中要写成 System.out.println("He ...
- #研发解决方案介绍#IdCenter(内部统一认证系统)
郑昀 基于朱传志的设计文档 最后更新于2014/11/13 关键词:LDAP.认证.权限分配.IdCenter. 本文档适用人员:研发 曾经一个IT内部系统配一套帐号体系和授权 线上生产环境里 ...
- 虚拟机VMWARE上ORACLE License 的计算
Oracle License的计算有两种方式:按照用户数和CPU个数. 其中按CPU计算方式如下: License Number = The Number of CPU Cores * Core ...
- ORACLE应用调优:请避免SQL做大量循环逻辑处理
前阵子遇到一个案例:一个同事说以前一个运行很正常的包,突然间比以前慢了很多,执行时间非常长,晚上的作业调用这个包跑了几个小时也没有跑出数据.于是我在跟踪.优化过程中定位到包中一个存储过程的一段SQL, ...
- SQL Server:触发器详解
1. 概述 触发器是一种特殊的存储过程,它不能被显式地调用,而是在往表中插入记录﹑更新记录或者删除记录时被自动地激活. 所以触发器可以用来实现对表实施复杂的完整性约束. 2. 触发器的分类 SQL S ...