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) 全部刷新?这不是某某方式能不能做到的问题,开发嘛,再乱再差代码,有时候也能做到用例要求。

以上是真实项目中我见过的修改过程,这做法当然被推翻,原因如下:

  1. Model 层为了 View 层的显示,添加了代码
    Model 类,为显示方式而改动(添加了一个不做映射的只读属性),是设计上的错误。图片选择的代码也在 Model 内(见 StatusImage 属性 get 内的 switch),也是设计错误。
  2. 图片与 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的更多相关文章

  1. C# WPF MVVM 实战 – 5- 用绑定,通过 VM 设置 View 的控件焦点

    本文介绍在 MVVM 中,如何用 ViewModel 控制焦点. 这焦点设置个东西嘛,有些争论.就是到底要不要用 ViewModel 来控制视图的键盘输入焦点.这里不讨论,假设你就是要通过 VM,设置 ...

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

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

  3. WPF MVVM+EF增删改查 简单示例(二) 1对1 映射

    WPF MVVM+EF增删改查 简单示例(一)实现了对学生信息的管理. 现在需求发生变更,在录入学生资料的时候同时需要录入学生的图片信息,并且一名学生只能有一张图片资料.并可对学生的图片资料进行更新. ...

  4. WPF MVVM 验证

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

  5. WPF MVVM初体验

    首先MVVM设计模式的结构, Views: 由Window/Page/UserControl等构成,通过DataBinding与ViewModels建立关联: ViewModels:由一组命令,可以绑 ...

  6. WPF MVVM实现TreeView

    今天有点时间,做个小例子WPF MVVM 实现TreeView 只是一个思路大家可以自由扩展 文章最后给出了源码下载地址 图1   图2     模版加上了一个checkbox,选中父类的checkb ...

  7. WPF/MVVM 快速开始指南(译)(转)

    WPF/MVVM 快速开始指南(译) 本篇文章是Barry Lapthorn创作的,感觉写得很好,翻译一下,做个纪念.由于英文水平实在太烂,所以翻译有错或者译得不好的地方请多指正.另外由于原文是针对W ...

  8. A WPF/MVVM Countdown Timer

    Introduction This article describes the construction of a countdown timer application written in C# ...

  9. 使用Prism提供的类实现WPF MVVM点餐Demo

    使用Prism提供的类实现WPF MVVM点餐Demo 由于公司开发的技术需求,近期在学习MVVM模式开发WPF应用程序.进过一段时间的学习,感受到:学习MVVM模式,最好的方法就是用MVVM做几个D ...

随机推荐

  1. servlet、genericservlet、httpservlet之间的区别(转)

    当编写一个servlet时,必须直接或间接实现servlet接口,最可能实现的方法就是扩展javax.servlet.genericservlet或javax.servlet.http.httpser ...

  2. where 子句和having子句中的区别

    1.where 不能放在GROUP BY 后面 2.HAVING 是跟GROUP BY 连在一起用的,放在GROUP BY 后面,此时的作用相当于WHERE 3.WHERE 后面的条件中不能有聚集函数 ...

  3. 【转】Web UI自动化测试原理

    目前市面上有很多Web UI自动化测试框架,比如WatiN, Selinimu,WebDriver,还有VS2010中的Coded UI等等.  这些框架都可以操作Web中的控件,模拟用户输入,点击等 ...

  4. 鸟哥的linux私房菜学习记录之开机流程、模块管理与Loader

  5. 使用 TC 对LInux中vpn 上传下载进行限速(转)

    TC 无需安装,Linux 内核自带 例:将vpn IP地址段192.168.1.0/24 上传下载限速为 5M 将以下内容添加到/etc/ppp/ip-up文件exit 0上面. down=5Mbi ...

  6. OBD 14230 Slow, Addr激活

    const u8 LinkCmd14230[6] = { 0xC2, 0x33, 0xF1, 0x01, 0x00, 0xE7 }; u8 ISO14230ADDR_Check(){          ...

  7. Apache服务器访问过慢分析及解决

    起因:线上的一台服务器,最近总是出现 访问 很慢的情况发生,点击一个链接要2秒钟以上才能打开,按照我们对于访问人数的估计,服务器应该不至于响应这么慢,从而需要针对这个问题进行分析,来解决网站访问过慢. ...

  8. Linux系统查看系统是32位还是64位方法总结【转】

    转自:http://www.cnblogs.com/kerrycode/p/3785768.html 这篇博客是总结.归纳查看Linux系统是32位还是64位的一些方法,很多内容来自网上网友的博客.本 ...

  9. [转]Jexus的常用操作和基本配置

    转自http://www.cnblogs.com/xiaodiejinghong/archive/2013/04/05/3000404.html 3.Jexus的操作 经过两个章节关于Jexus的介绍 ...

  10. Hibernate,Session清理缓存时间点

    当应用程序调用org.hibernate.Transaction的commit()的时候,commit()方法先清理缓存,然后再向数据库提交事务. 当应用程序显示调用Session.flush()方法 ...