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

.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 验证的更多相关文章

  1. WPF mvvm 验证,耗时两天的解决方案

    常用类 类名 介绍 ValidationRule 所有自定义验证规则的基类.提供了让用户定义验证规则的入口. ExceptionValidation 表示一个规则,该规则检查在绑定源属性更新过程中引发 ...

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

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

  3. WPF MVVM(Caliburn.Micro) 数据验证

    书接前文 前文中仅是WPF验证中的一种,我们暂且称之为View端的验证(因为其验证规是写在Xaml文件中的). 还有一种我们称之为Model端验证,Model通过继承IDataErrorInfo接口来 ...

  4. WPF MVVM使用prism4.1搭建

    WPF MVVM使用prism4.1搭建 MVVM即Model-View-ViewModel,MVVM模式与MVP(Model-View-Presenter)模式相似,主要目的是分离视图(View)和 ...

  5. ViewModel从未如此清爽 - 轻量级WPF MVVM框架Stylet

    Stylet是我最近发现的一个WPF MVVM框架, 在博客园上搜了一下, 相关的文章基本没有, 所以写了这个入门的文章推荐给大家. Stylet是受Caliburn Micro项目的启发, 所以借鉴 ...

  6. WPF MVVM 架构 Step By Step(6)(把actions从view model解耦)

    到现在为止,我们创建了一个简单的MVVM的例子,包含了实现了的属性和命令.我们现在有这样一个包含了例如textbox类似的输入元素的视图,textbox用绑定来和view model联系,像点击but ...

  7. 转载:WPF MVVM之INotifyPropertyChanged接口的几种实现方式

    原文地址:http://www.cnblogs.com/xiwang/ 序言 借助WPF/Sliverlight强大的数据绑定功能,可以比实现比MFC,WinForm更加优雅轻松的数据绑定.但是在使用 ...

  8. WPF MVVM从入门到精通6:RadioButton等一对多控件的绑定

    原文:WPF MVVM从入门到精通6:RadioButton等一对多控件的绑定   WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM ...

  9. WPF MVVM从入门到精通7:关闭窗口和打开新窗口

    原文:WPF MVVM从入门到精通7:关闭窗口和打开新窗口 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM从入门到精通3:数据绑定 ...

随机推荐

  1. SPF邮件伪造漏洞测试脚本

    测试脚本: # -*- coding: utf-8 -*- import socket,select,base64,os,re,time,datetime class mail: def __init ...

  2. hyperv 创建虚拟以太网交换机失败

    问题: hyperv 创建虚拟以太网交换机失败 解决办法: 取消无线共享,重新创建虚拟以太网交换机即可

  3. iOS 疑难杂症 — — UITableView 添加 tableFooterView 旋转屏幕后收不到点击事件!!!

    声明 欢迎转载,但请保留文章原始出处:) 博客园:http://www.cnblogs.com 农民伯伯: http://over140.cnblogs.com 正文 新手的烦恼你不懂 - - ## ...

  4. Masonry使用注意事项

    1 理解自身内容尺寸约束与抗压抗拉 自身内容尺寸约束:一般来说,要确定一个视图的精确位置,至少需要4个布局约束(以确定水平位置x.垂直位置y.宽度w和高度h).但是,某些用来展现内容的用户控件,例如文 ...

  5. React Native 红屏之Could not connect to development server.

    React Native 是目前最火的开发框架,其他不说了,上Bug. 按照  React Native iOS环境搭建 高级版 在mac上  搭建 React Native  环境,运行 项目 若出 ...

  6. git之四

    1.管理修改 Git与其他版本控制系统相比,Git跟踪并管理的是修改,而非文件. 为什么说Git管理的是修改,而不是文件.接下来做测试,我们对readme.txt修改,追加一行内容: echo &qu ...

  7. 使用 Async 和 Await 的异步编程(C# 和 Visual Basic)[msdn.microsoft.com]

    看到Microsoft官方一篇关于异步编程的文章,感觉挺好,不敢独享,分享给大家. 原文地址:https://msdn.microsoft.com/zh-cn/library/hh191443.asp ...

  8. MySQL学习笔记之视图

    视图是对磁盘上保存的表数据的抽象,即抽取一个表或多个表的部分行或列的数据,展示给使用者. 首先列举下MySQL中最简单的对视图操作的语法: 1.创建视图: create view 视图名 as sel ...

  9. ORACLE 10g 数据库体系结构图

    ORACLE 10g 的数据库体系结构图(ORACLE 10g(Release 2)ARCHITECTURE),非常的全面.系统.高屋建瓴的整体介绍了ORACLE 10g 的数据库体系结构.如果能全面 ...

  10. Oracle常用语句集合

    oracle常用经典SQL查询 常用SQL查询: .查看表空间的名称及大小 )),) ts_size from dba_tablespaces t, dba_data_files d where t. ...