C# WPF MVVM 实战 – 4_.NET_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > .NET > C# WPF MVVM 实战 – 4

C# WPF MVVM 实战 – 4

 2013/8/30 17:09:18  善用 IValueConverter  博客园  我要评论(0)
  • 摘要:IValueConverter,做WPF的都应该接触过,把值换成Visibility、Margin等等是最常见的例子,也有很多很好的博文解释过用法。本文只是解释一下,MVVM中一些情景。我遇到过一个案例,做些简单的数据可视化。要求把enum换成图片。MVVM模式下,是通过ViewModel把业务类Model暴露给View,用绑定完成ViewModel和View的连接。这样的话,Model内的enum直接绑到View时候,视图显示了enum值ToString()的结果。用个真实例子说明
  • 标签:C#

IValueConverter,做 WPF 的都应该接触过,把值换成 Visibility 、Margin 等等是最常见的例子,也有很多很好的博文解释过用法。本文只是解释一下,MVVM 中一些情景。

我遇到过一个案例,做些简单的数据可视化。要求把 enum 换成图片。

MVVM 模式下,是通过 ViewModel 把业务类 Model 暴露给 View,用绑定完成 ViewModel 和 View 的连接。这样的话,Model 内的 enum 直接绑到 View 时候,视图显示了 enum 值 ToString() 的结果。

用个真实例子说明。为代码更简单,以下没有用任何其他第三方控件或类库。

看看 Model 是怎样的:

break-word;overflow: auto;">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 = 0,
        Cancel = 1,
        Delete = 2
    }
}

这个业务类,将使用 ORM, NHibernate 做 persistence,这个样子加上 XXX.hbm.xml 映射能直接调用 session.Save() 来储存的了。enum 会自动储存为 int。数据层不写出来了。使用其他 ORM 也大同小异。

但直接绑定后,这样子不太 User-Friendly,也不够华丽。

attach1

这用例是做可视化,把状态换成图片。

要绑定图片,绑定的话,最直观的想法,改 Model。Model 内有图片Source 属性,好像就什么都解决了。

using System.ComponentModel;
using System.Windows.Media;
using System;
using System.Windows.Media.Imaging;

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>
        /// 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>

效果:

attach2

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 也是不正确。破坏了设计,自然享受不了良好设计的优势。

发表评论
用户名: 匿名