【转】WPF MVVM 循序渐进 (从基础到高级) 【已翻译100%】【1】

原文地址:http://www.oschina.net/translate/wpf-mvvm-step-by-step-basics-to-advance-level?lang=chs&page=1#

简介

简单的三层架构示例和 GLUE(胶水)代码问题

第一步:最简单的 MVVM 示例 - 把后台代码移到类中

第二步:添加绑定 - 消灭后台代码

第三步:添加执行动作和“INotifyPropertyChanged”接口

第四步:在 ViewModel 中解耦执行动作

第五步:利用 PRISM

WPF MVVM 的视频演示

简介

从我们还是儿童到学习成长为成年人,生命一直都在演变。 对于软件架构, 同样适用这个道理, 从一个基础的架构开始, 随着每个需求和情境在不断演化。

如果你问任何一个 .NET 开发者, 什么是最小的基础架构, 首先浮现的就是"三层架构"。 在这个框架中, 我们把项目分为三个逻辑层次: UI 层, 业务逻辑层和数据访问层, 每一层都负责各自对应的功能。

三层架构

UI 负责显示功能, 业务逻辑层负责校验, 数据访问层负责 SQL 语句。3层架构有如下的好处:

  •  包容变化: 每一层的变化不会重复跨越到其它层次。

  •  重用性: 增强可重用性, 因为每一层都是分离, 自包容的独立实体



MVVM 是三层架构的一个演化。我知道我的经历不够证明这点, 但是我个人对 MVVM 进行了演化和观察。 那我们先从三层基础架构开始, 去理解三层架构存在的问题, 看 MVVM 架构是如何解决这些问题, 然后升级到去创建一个自定义的 MVVM 框架代码。 下面是本文接下来的路线图。

Road map of MVVM

简单的三层架构示例和 GLUE(胶水) 代码问题

首先, 让我们来理解三层架构以及它存在的问题, 然后看 MVVM 如何解决这个问题。

直觉和现实是两种不同的事物。 当你看到三层架构的图, 你首先的直觉是每个功能可能都分布在各自层次。 但是当你实际编写代码时, 有些层次被强迫去做一些它们不应该做的额外的工作(破坏了SOLID 原则)。 如果你对 SOLID 原则还不熟悉可以参考这个视频: SOLID principle video(译者注: SOLID 指 Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion, 即单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)。

GLUE Code

这部分额外工作就在 UI 与Model之间, 以及 Model 与 Data access 之间。 我们把这类代码称为"GLUE"(胶水, 译者注:由于作者全用大写字母表示, 因此后续延用 GLUE)代码。"GLUE"代码主要有两种逻辑类型。



鄙人浅见薄识, 如果你有更多的"GLUE"类型实例, 请在留言中指出。

  • 映射逻辑(绑定逻辑): 每一层通过属性、方法和集合和其它层链接。例如, 一个在 UI 层中名为“txtCustomerName”的 Textbox 控件,将其映射到 customer 类的"CustomerName"属性。

txtCustomerName.text = custobj.CustomerName; // 映射代码

现在谁应该拥有上述绑定逻辑代码,UI 还是 Model?开发者往往把这个代码推到 UI 层次中。

  • 转换逻辑:每个层次使用的数据格式都是不同的。比如一个 Model 类"Person"有一个性别属性,可取值分别为 "F"(Female) 和 "M"(Male) 分别代表女性和男性。但是在 UI 层中,希望将这个值可视化为一个复选框控件,勾选则代表男性,不勾选则代表女性。下面是一个转换代码示例。

if (obj.Gender == “M”) // 转换代码 {chkMale.IsChecked = true;}
else
{chkMale.IsChecked = false;}

大多数开发者最终会将"GLUE"代码写到UI层中。通常可以在后台代码中定位到这类代码,例如 .cs 文件。如果UI 是 XAML,则对应的 XAML.cs 包含 GLUE代码;如果 UI 是 ASPX,则对应的 ASPX.cs 包含 GLUE 代码,以此类推。



那么问题来了:是UI负责这类GLUE代码吗?让我们看下WPF应用中的一个简单的三层结构例子,以及更详细的GLUE代码细节。

下面是一个简单的模型类"Customer",它有三个属性“CustomerName”,“Amount” 和“Married”。

但是,当这个模型显示到 UI 上时它又表现如下。所以,你可以看出来它包含了该模型的所有属性,以及一些额外的元素:颜色标签和 Married 复选框控件。

下面有一张简单的表,左边是 Model,右边是 UI,中间是谈过的映射和转换逻辑。

你可以看到前两行没有转换逻辑,只有映射逻辑,另外两行则同时包含转换逻辑和映射逻辑。

ModelGLUE CODEUI
                Customer Name                No conversion needed only Mapping                Customer Name
                Amount                No conversion needed only Mapping                Amount
                Amount                Mapping + Conversion logic.                > 1500 = BLUE
< 1500 = RED
                Married                Mapping + Conversion logic.                True – Married
False - UnMarried

这些转换和映射逻辑代码通常会在“xaml.cs”文件中。下面是上图对应的后台代码,你可以看到映射代码和颜色判定、性别格式转换代码。我在代码中用注释标注出来,这样你可以看到哪些是映射代码,哪些是转换代码。

lblName.Content = o.CustomerName; // mapping code
lblAmount.Content = o.Amount; // mapping code

if (o.Amount > 2000// transformation code
{
lblBuyingHabits.Background = new SolidColorBrush(Colors.Blue);
}
else if (o.Amount > 1500// transformation code
{
lblBuyingHabits.Background = new SolidColorBrush(Colors.Red);
}
if (obj.Married == "Married"// transformation code
{
chkMarried.IsChecked = true;
}
else
{
chkMarried.IsChecked = false;
}


现在这些 GLUE 代码存在的问题:

  • 单一责任原则被破坏(SRPViolation): 是 UI 负责这些 GLUE 代码吗?这种情况下改变了 Amount 数量,同时也需要修改 UI 代码。现在,数据的改变为什么会让我去修改 UI 的代码?这里可以闻到坏代码的味道。UI 应该只在我修改样式,颜色和布局的时候才改变。

  • 重用性: 如果我想把同样的颜色逻辑和性别格式转换用到下面的编辑界面,我该怎么做?拷贝粘帖重复的代码?

如果我想走得更远一点,把这个 GLUE 代码用在不同的 UI 技术体系上,比如 MVC、Windows Form 或者 Mobile 应用上。

但是这里跨 UI 技术平台的重用实际上是不可能的,因为每个平台 UI 背后都和各自的 UI 技术体系耦合得很紧密。

比如,下面的后台代码是继承自“Windows”类,而“Windows”类是集成在 WPF UI 体系中。如果我们想在 Web 应用或者 MVC 中应用这些逻辑,却又无法去创建一个这样的类对象来使用。

public partial class MainWindow : Window
{
// Behind code is here
}

那么我们要怎么重用后台代码?怎么遵循 SRP 原则?



第一步:最简单的 MVVM 示例 - 把后台代码移到类中

我想大部分开发者已经知道怎么解决这个问题。毫无疑问地把后台代码(GLUE 代码)移到一个类库中。这个类库代表了描述了 UI 的属性和行为。任何移入到这个类库的代码都可以编译成 DLL,然后被所有 .NET 项目(Windows,Web 等等)所引用。因此,在这一节我们将创建一个最简单的 MVVM 示例,然后在后续的章节中我们将基于这个示例创建更高级的 MVVM 示例。

Simplest MVVM

我们创建一个“CustomerViewModel”类来包含 GLUE 代码。“CustomerViewModel”类代表了你的 UI,所以我们想保持它的属性和UI命名约定一致。你可以从下图看出来“CustomerViewModel”类的属性是如何从之前的 CustomerModel 类中映射过来: “TxtCustomerName”对应“CustomerName”,“TxtAmount”对应“Amount”等等。

ViewModel

下面是实际代码:

public class CustomerViewModel 
    {
        private Customer obj = new Customer();

        public string TxtCustomerName
        {
            get { return obj.CustomerName; }
            set { obj.CustomerName = value; }
        }        

        public string TxtAmount
        {
            get { return Convert.ToString(obj.Amount) ; }
            set { obj.Amount = Convert.ToDouble(value); }
        }


        public string LblAmountColor
        {
            get 
            {
                if (obj.Amount > 2000)
                {
                    return "Blue";
                }
                else if (obj.Amount > 1500)
                {
                    return "Red";
                }
                return "Yellow";
            }
        }

        public bool IsMarried
        {
            get
            {
                if (obj.Married == "Married")
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }

        }}


关于“CustomerViewModel”这个类有以下几点注意:

  •  类属性都以 UI 的命名方式来约定,这样看上去会更形象一些;

  •  这个类负责了类型转换的代码,使得 UI 看上去更轻量级。例如代码中的“TxtAmount”属性。在 Model 类中的“Amount”属性是数字,而转换的过程是在 ViewModel 类中完成。换句话说这个类负责了 UI 显示的所有职责(译者注:逻辑上的业务职责)让 UI 后台代码看上去更简洁;

  •  所有转换逻辑的代码都在这个类中,例如“LblAmountColor”属性和“IsMarried”属性;

  •  所有的属性数据都保持了简单的字符类型,这样可以在大多 UI 技术平台上适用。例如,“LblAmountColor”属性把颜色值用字符串来传递,这样可以在任何 UI 类型中重用,同时我们也保持了最小的数据共性。

现在“CustomerViewModel”类包含了所有的后台代码逻辑,我们可以创建这个类的对象并绑定到 UI 元素上。你可以在下面代码看到我们只剩下了映射逻辑的代码部分,而转换逻辑的"GLUE"代码已经没有了。

private void DisplayUi(CustomerViewModel o)
{
lblName.Content = o.TxtCustomerName;
lblAmount.Content = o.TxtAmount;
BrushConverter brushconv = new BrushConverter();
lblBuyingHabits.Background = brushconv.ConvertFromString(o.LblAmountColor) as SolidColorBrush;
chkMarried.IsChecked = o.IsMarried;
}


第二步:添加绑定 - 消灭后台代码

第一步的方法很好,但是我们知道后台代码仍然还有问题,在 WPF 中消灭所有后台代码是完全可能的。接下来 WPF 绑定和命令登场了。

WPF 以其绑定(Binding)、命令(Commands)和声明式编程(Declarative programming)而著称。声明式编程意味着你可以使用 XMAL 来表达你的 C# 代码,而不用编写完整的C#代码。绑定功能帮助一个 WPF 对象连接到其它的 WPF 对象,从而他们可以发送和接收数据。

当前的映射 C# 代码有三个步骤:

  • 导入:我们要做的第一件事情是导入“CustomerViewModel”名称空间。

  • 创建对象:下一步要创建“CustomerViewModel”类的对象。

  • 绑定代码:最后将 WPF UI 绑定到这个 ViewModel 对象。

下面表格展示了 C# 代码和与其对应相同的 WPF XAML 代码。


C# codeXAML code
                Import                using CustomerViewModel;                xmlns:custns="clr-
namespace:CustomerViewModel;assembly=Custo
merViewModel"
                Create
object
                CustomerViewModelobj = new
CustomerViewModel();
obj.CustomerName = "Shiv";
obj.Amount = 2000;
obj.Married = "Married";
<Window.Resources>
<custns:CustomerViewModel 
x:Key="custviewobj" 
TxtCustomerName="Shiv" TxtAmount="1000" IsMarried=”true”/>
                Bind                lblName.Content = o.CustomerName;
<Label x:Name="lblName"  Content="{Binding 
TxtCustomerName, 
Source={StaticResourcecustviewobj}}"/>


你不需要写后台的代码,我们可以选中 UI 元素,按 F4,如下图中选择指定绑定。这个步骤会把绑定代码插入到 XAML中。

选择“StaticResource”来指定映射,然后在 UI 元素和 ViewModel 对象之间指定绑定路径。

这时你查看 XAML.CS 文件,它已经没有任何 GLUE 代码,同样也没有转换和映射代码。唯一的代码就是标准的 WPF UI 初始化代码。

{        

public MVVMWithBindings()

       {InitializeComponent();} 

}



第三步:添加执行动作和“INotifyPropertyChanged”接口

应用程序不仅仅只是有 textboxs 和 labels, 同样还需要执行动作,比如按钮,鼠标事件等。 因此让我们添加一个按钮来看看如何把 MVVM 类应用起来。 我们在同样的 UI 上添加了一个‘Calculate tax’按钮,当用户按下按钮,它将根据“Sales Amount”值计算出税值并显示在界面上。

Add Action

因此为了在 Model 类实现上面的功能,我们添加一个“CalculateTax()”方法。当这个方法被执行,它根据薪水范围计算出税值,并将值保存在“Tax”属性值中。

public class Customer
{ 
....
....
....
....
private double _Tax;
public double Tax
{
get { return _Tax; }
}
        public void CalculateTax()
        {
    if (_Amount > 2000)
            {
                _Tax = 20;
            }
            else if (_Amount > 1000)
            {
                _Tax = 10;
            }
            else
            {
                _Tax = 5;
            }
        }
}

由于 ViewModel 类是 Model 类的一个封装,因此我们需要在 ViewModel 类中创建一个方法来调用 Model 的“CalculateTax”方法。

public class CustomerViewModel 
{
        private Customer obj = new Customer();
....
....
....
....
        public void Calculate()
        {
            obj.CalculateTax();
        }
}

现在,我们想要在 XAML 的视图中调用这个“Calculate”方法,而不是在后台编写。不过你不能直接通过 XAML 调用“Calculate”方法,你需要用 WPF 的 command 类。

我们通过使用绑定属性将数据发送给 ViewModel 类,而发送执行动作给 ViewModel 类则需要使用命令。

所有从视图元素产生的动作都发送给 command 类,所以第一步是创建一个 command 类。为了创建自定义的 command 类,我们需要实现"ICommand"接口(如下图)。



"ICommand"接口有两个必须要重载的方法:“CanExecute”和“Execute”。在“Execute”中我们放的是希望动作发生时实际执行的逻辑代码(比如按钮按下,右键按下等)。在“CanExecute”中我们放的是验证逻辑来决定“Execute”代码是否应该执行。

public class ButtonCommand : ICommand
{
        public bool CanExecute(object parameter)
        {
      // When to execute
      // Validation logic goes here
        }

        public event EventHandler CanExecuteChanged;

        public void Execute(object parameter)
        {
// What to Execute
      // Execution logic goes here
    }
}

现在所有的动作调用都发送到 command 类,然后被路由到 ViewModel 类。换句话说,command 类需要组合ViewModel 类(译注:command 类需要一个 ViewModel 类的引用)。

Route

下面是简短的代码片段,有四点需要注意:

  1.  ViewModel 对象是作为一个私有的成员对象。

  2. 该 ViewModel 对象将通过构造函数参数的方式传递进来。

  3.  目前为止,我们没有在“CanExecute”中添加验证逻辑,它始终返回 true。

  4.  在“Execute”方法中我们调用了 ViewModel 类的“Calculate”方法。

public class ButtonCommand : ICommand
    {
        private CustomerViewModel obj; // Point 1
        public ButtonCommand(CustomerViewModel _obj) // Point 2
        {
            obj = _obj;
        }
        public bool CanExecute(object parameter)
        {
            return true// Point 3
        }
        public void Execute(object parameter)
        {
            obj.Calculate(); // Point 4
        }
    }

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C# WPF MVVM 是一种常用的框架,用于开发 Windows 桌面应用程序。进销存是一种常见的管理系统,用于管理企业的进货、销售、库存等业务。下面是一个简单的 C# WPF MVVM 进销存的实现示例: 1. 数据库设计 首先需要设计数据库,包括商品表、进货表、销售表、库存表等。可以使用 MSSqlserver 数据库。 2. MVVM 模式 使用 MVVM 模式可以将业务逻辑与界面分离,提高代码的可维护性和可测试性。MVVM 模式包括 Model、View 和 ViewModel 三个部分。 3. Model Model 层负责数据的读取和存储,可以使用 Entity Framework 或者 NHibernate 等 ORM 框架。例如,可以定义一个 Product 类表示商品,包括商品编号、商品名称、商品单价等属性。 4. View View 层负责界面的显示和用户交互,可以使用 XAML 语言定义界面。例如,可以定义一个商品列表界面,包括商品编号、商品名称、商品单价等列。 5. ViewModel ViewModel 层负责将 Model 层的数据绑定到 View 层的界面上,并处理用户交互事件。例如,可以定义一个 ProductViewModel 类表示商品列表界面的 ViewModel,包括商品列表、添加商品、删除商品等方法。 6. 插件式开发 使用插件式开发可以将业务模块分离,提高多人协作开发效率。可以使用 MEF(Managed Extensibility Framework)框架实现插件式开发。 7. 使用 DevExpress 插件 DevExpress 是一个常用的 UI 控件库,可以提高开发效率和用户体验。可以使用 DevExpress 的 Grid 控件实现商品列表界面,包括排序、筛选、分页等功能。 8. 使用 NLog 记录日志 NLog 是一个常用的日志记录框架,可以记录应用程序的运行日志,方便排查问题。 9. 相关问题:

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值