C# WPF MVVM 实战 – 4 - 善用 IValueConverter
IValueConverter,做 WPF 的都应该接触过,把值换成 Visibility 、Margin 等等是最常见的例子,也有很多很好的博文解释过用法。本文只是解释一下,MVVM 中一些情景。
我遇到过一个用例,做些简单的数据可视化。要求把 enum 换成图片。
MVVM 模式下,是通过 ViewModel 把业务类 Model 暴露给 View,用绑定完成 ViewModel 和 View 的连接。这样的话,Model 内的 enum 直接绑到 View 时候,视图显示了 enum 值 ToString() 的结果。
用个真实例子说明。为代码更简单,以下没有用任何其他第三方控件或类库。
看看 Model 是怎样的:
using System.ComponentModel;
namespace Lepton_Practical_MVVM_4 {
public class MyModel : INotifyPropertyChanged {
#region Domain Properties private long _ID;
///<summary>/// DB Key
///</summary>public virtual long ID {
get {
return _ID;
}
set {
_ID = value;
if (PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs("ID"));
}
}
private MyStatusEnum _Status;
///<summary>/// Document Status
///</summary>public virtual MyStatusEnum Status {
get {
return _Status;
}
set {
_Status = value;
if (PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs("Status"));
}
}
private string _DocNo;
///<summary>/// Document number
///</summary>public virtual string DocNo {
get {
return _DocNo;
}
set {
_DocNo = value;
if (PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs("DocNo"));
}
}
#endregion
#region INotifyPropertyChanged Members
public virtual event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
MyStatusEnum:
namespace Lepton_Practical_MVVM_4 {
public enum MyStatusEnum {
OK =,
Cancel =,
Delete =
}
}
这个业务类,将使用 ORM, NHibernate 做 persistence,这个样子加上 XXX.hbm.xml 映射能直接调用 session.Save() 来储存的了。enum 会自动储存为 int。数据层不写出来了。使用其他 ORM 也大同小异。
但直接绑定后,这样子不太 User-Friendly,也不够华丽。

这用例是做可视化,把状态换成图片。
要绑定图片,绑定的话,最直观的想法,改 Model。Model 内有图片Source 属性,好像就什么都解决了。
using System.ComponentModel;
using System.Windows.Media;
using System;
using System.Windows.Media.Imaging; namespace Lepton_Practical_MVVM_4 { publicclass MyModel : INotifyPropertyChanged { #region Domain Properties private long _ID;
///<summary>/// DB Key
///</summary> public virtual long ID {
get {
return _ID;
}
set {
_ID = value;
if (PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs("ID"));
}
} private MyStatusEnum _Status;
///<summary>
/// Status
///</summary>
public virtual MyStatusEnum Status {
get {
return _Status;
}
set {
_Status = value;
if (PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs("Status"));
}
}
private string _DocNo;
///<summary>
/// Document Number
///</summary>
public virtual string DocNo {
get {
return _DocNo;
}
set {
_DocNo = value;
if (PropertyChanged !=null)
PropertyChanged(this, new PropertyChangedEventArgs("DocNo"));
}
}
#endregion
public virtual ImageSource StatusImage {
get {
switch (Status) {
case MyStatusEnum.OK:
return new BitmapImage(new Uri("pack://application:,,,/Img/Check.png"));
case MyStatusEnum.Cancel:
return new BitmapImage(new Uri("pack://application:,,,/Img/Cancel.png"));
case MyStatusEnum.Delete:
return new BitmapImage(new Uri("pack://application:,,,/Img/Delete.png"));
default:
throw new InvalidEnumArgumentException("MyStatusEnum out of expected range");
}
}
}
#region INotifyPropertyChanged Members
public virtual event PropertyChangedEventHandler PropertyChanged;
#endregion
}
}
XAML:
<Window x:Class="Lepton_Practical_MVVM_4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Lepton Practical MVVM 4 IConverter"
Height="350" Width="525"><Grid><ListView ItemsSource="{Binding Path=MyItems}"><ListView.View><GridView><GridViewColumn DisplayMemberBinding="{Binding Path=Status}" Header="状态" Width="50"/><GridViewColumn DisplayMemberBinding="{Binding Path=DocNo}" Header="单号" Width="100"/><GridViewColumn Header="图片"><GridViewColumn.CellTemplate><DataTemplate><Image Source="{Binding Path=StatusImage}" Width="20" Height="20"/></DataTemplate></GridViewColumn.CellTemplate></GridViewColumn></GridView></ListView.View></ListView></Grid></Window>
效果:

StatusImage 这个字段,不需要映射不用保存到数据库,貌似没问题。
但一旦运行时 Status 值变化,问题就出来了。
运行时,MyStatusEnum 值如果动态改,你会发现,状态栏的值会变,图片却不变。问题原因很明显,值变化发生在 Status,由它发出 PropertyChanged 通知,通知 Status 变了,不是 StatusImage变。点击这里下载这版本的代码。
然后又想办法,或许在 Status 变动时同时发Status 和 StatusImage 两个变化通知?又或许 PropertyChanged(null) 全部刷新?这不是某某方式能不能做到的问题,开发嘛,再乱再差代码,有时候也能做到用例要求。
以上是真实项目中我见过的修改过程,这做法当然被推翻,原因如下:
- Model 层为了 View 层的显示,添加了代码
Model 类,为显示方式而改动(添加了一个不做映射的只读属性),是设计上的错误。图片选择的代码也在 Model 内(见 StatusImage 属性 get 内的 switch),也是设计错误。 - 图片与 Model 捆绑了在一起
如果改图片,需要改 Model 属性的 switch 内的 URI。万一,我要保留这图片这显示方式在这视图,但在另一个界面显示另一张图,比如我有另外一个视图需要把 Delete 和 Cancel 显示为同一张图,判断就不是那么简单。可能需要用到视图的引用,或者格外加参数、属性等等来判断显示哪个。
正确做法,是用 IValueConverter 做转换,IValueConverter 是显示层(View)的。需要不同逻辑的话,每个界面用自己的 Converter,逻辑独立,图片只跟 View 捆绑,ViewModel 和 Model 不需理会。
IValueConverter 写法很简单,在其他博文已经多次介绍。这里不重复了。用它的时候,绑定的是业务数据的字段,当它变化,图片自然会更新了。点击这里下载使用了 IValueConverter 的完整代码。
使用 MVVM 模式,一旦牵涉到改 Model 或 ViewModel,请三思。只为显示的话,几乎可以马上肯定,不该做在 Model 和 ViewModel。反过来,业务逻辑出现在 View 也是不正确。破坏了设计,自然享受不了良好设计的优势。
我在这群里,欢迎加入交流:
开发板玩家群 578649319
硬件创客 (10105555)
C# WPF MVVM 实战 – 4 - 善用 IValueConverter的更多相关文章
- C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点
本文介绍在 MVVM 中,如何用 ViewModel 控制焦点. 这焦点设置个东西嘛,有些争论.就是到底要不要用 ViewModel 来控制视图的键盘输入焦点.这里不讨论,假设你就是要通过 VM,设置 ...
- WPF MVVM从入门到精通6:RadioButton等一对多控件的绑定
原文:WPF MVVM从入门到精通6:RadioButton等一对多控件的绑定 WPF MVVM从入门到精通1:MVVM模式简介 WPF MVVM从入门到精通2:实现一个登录窗口 WPF MVVM ...
- WPF MVVM+EF增删改查 简单示例(二) 1对1 映射
WPF MVVM+EF增删改查 简单示例(一)实现了对学生信息的管理. 现在需求发生变更,在录入学生资料的时候同时需要录入学生的图片信息,并且一名学生只能有一张图片资料.并可对学生的图片资料进行更新. ...
- WPF MVVM 验证
WPF MVVM(Caliburn.Micro) 数据验证 书接前文 前文中仅是WPF验证中的一种,我们暂且称之为View端的验证(因为其验证规是写在Xaml文件中的). 还有一种我们称之为Model ...
- WPF MVVM初体验
首先MVVM设计模式的结构, Views: 由Window/Page/UserControl等构成,通过DataBinding与ViewModels建立关联: ViewModels:由一组命令,可以绑 ...
- WPF MVVM实现TreeView
今天有点时间,做个小例子WPF MVVM 实现TreeView 只是一个思路大家可以自由扩展 文章最后给出了源码下载地址 图1 图2 模版加上了一个checkbox,选中父类的checkb ...
- WPF/MVVM 快速开始指南(译)(转)
WPF/MVVM 快速开始指南(译) 本篇文章是Barry Lapthorn创作的,感觉写得很好,翻译一下,做个纪念.由于英文水平实在太烂,所以翻译有错或者译得不好的地方请多指正.另外由于原文是针对W ...
- A WPF/MVVM Countdown Timer
Introduction This article describes the construction of a countdown timer application written in C# ...
- 使用Prism提供的类实现WPF MVVM点餐Demo
使用Prism提供的类实现WPF MVVM点餐Demo 由于公司开发的技术需求,近期在学习MVVM模式开发WPF应用程序.进过一段时间的学习,感受到:学习MVVM模式,最好的方法就是用MVVM做几个D ...
随机推荐
- OpenStack collectd的从零安装客户端
1.查看是否需要增加yum 源 1 2 3 4 5 6 7 8 9 10 11 12 13 14 [root@node-12 ~]# yum search collectd Loaded plugin ...
- 作为一个web开发人员,哪些技术细节是在发布站点前你需要考虑到的
前日在cnblogs上看到一遍文章<每个程序员都必读的12篇文章>,其中大多数是E文的. 先译其中一篇web相关的”每个程序员必知之WEB开发”. 原文: http://programme ...
- Ubuntu1404: 将VIM打造为一个实用的PythonIDE
参考: http://www.tuicool.com/articles/ZRv6Rv 说明: 内容非原创, 主要是做了整合和梳理. 在 ubuntu14.04 & debian 8 下测试通 ...
- 160921、React入门教程第一课--从零开始构建项目
工欲善其事必先利其器,现在的node环境下,有太多好用的工具能够帮助我们更好的开发和维护管理项目. 我本人不建议什么功能都自己写,我比较喜欢代码复用.只要能找到npm包来实现的功能,坚决不自己敲代码. ...
- [Android新手区] SQLite 操作详解--SQL语法
该文章完全摘自转自:北大青鸟[Android新手区] SQLite 操作详解--SQL语法 :http://home.bdqn.cn/thread-49363-1-1.html SQLite库可以解 ...
- 在CentOS之上搭建VMware Player 7
1.下载VMware-Player-7.1.2安装包 百度网盘下载地址: 链接:http://pan.baidu.com/s/1nudfo6H 密码:oemc 直接下载地址: https://down ...
- Quartz集群原理及配置应用
1.Quartz任务调度的基本实现原理 Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现.作为一个优秀的开源调度框架,Quartz具有以下特点: (1) ...
- JavaEE基础(二)
1.Java语言基础(常量的概述和使用) A:什么是常量 在程序执行的过程中其值不可以发生改变 B:Java中常量的分类 字面值常量 自定义常量(面向对象部分讲) C:字面值常量的分类 字符串常量 用 ...
- Oracle字符集设置
客户端与服务端字符集不一致会造成乱码问题. 在服务端: sql>SELECT * FROM NLS_DATABASE_PARAMETERS; 在查询结果中关注如下参数: nls_language ...
- HDU 3746:Cyclic Nacklace
Cyclic Nacklace Time Limit: 2000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) ...