WPF中为什么使用MVVM设计模式不完全推导

WPF中为什么使用MVVM设计模式不完全推导

MVVM的定义和 "GLUE"代码

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

在这里插入图片描述

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

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

直觉和现实是两种不同的事物。 当你看到三层架构的图, 你首先的直觉是每个功能可能都分布在各自层次。 但是当你实际编写代码时, 有些层次被强迫去做一些它们不应该做的额外的工作(破坏了SOLID 原则)。 如果你对 SOLID 原则还不熟悉可以参考这个视频: SOLID principle video(译者注: SOLID 指 Single responsibility, Open-closed, Liskov substitution, Interface segregation and Dependency inversion, 即单一功能、开闭原则、里氏替换、接口隔离以及依赖反转)。
在这里插入图片描述
这部分额外工作就在 UI 与Model之间, 以及 Model 与 Data access 之间。 我们把这类代码称为"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,中间是谈过的映射和转换逻辑。
在这里插入图片描述

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

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 原则?`

https://www.oschina.net/translate/wpf-mvvm-step-by-step-basics-to-advance-level?cmp

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

MVVM模式中的基本内容绑定:
1、数据绑定: 依赖属性:数据Model必须继承DependencyObject 属性名称+Changed事件:固定写法 INotifyPropertyChanged接口:形成一种契约
2、基于Model的数据验证
Exception Required、StringLength、Range、RegularExpression、Custom

我想大部分开发者已经知道怎么解决这个问题。毫无疑问地把后台代码(GLUE 代码)移到一个类库中。这个类库代表了描述了 UI 的属性和行为。任何移入到这个类库的代码都可以编译成 DLL,然后被所有 .NET 项目(Windows,Web 等等)所引用。因此,在这一节我们将创建一个最简单的 MVVM 示例,然后在后续的章节中我们将基于这个示例创建更高级的 MVVM 示例。
在这里插入图片描述
我们创建一个“CustomerViewModel”类来包含 GLUE 代码。“CustomerViewModel”类代表了你的 UI,所以我们想保持它的属性和UI命名约定一致。你可以从下图看出来“CustomerViewModel”类的属性是如何从之前的 CustomerModel 类中映射过来: “TxtCustomerName”对应“CustomerName”,“TxtAmount”对应“Amount”等等。
在这里插入图片描述
下面是实际代码:

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 代码。
在这里插入图片描述
你不需要写后台的代码,我们可以选中 UI 元素,按 F4,如下图中选择指定绑定。这个步骤会把绑定代码插入到 XAML中。
在这里插入图片描述
选择“StaticResource”来指定映射,然后在 UI 元素和 ViewModel 对象之间指定绑定路径。
在这里插入图片描述
这时你查看 XAML.CS 文件,它已经没有任何 GLUE 代码,同样也没有转换和映射代码。唯一的代码就是标准的 WPF UI 初始化代码。

{       

public MVVMWithBindings()

  {InitializeComponent();}

}

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

应用程序不仅仅只是有 textboxs 和 labels, 同样还需要执行动作,比如按钮,鼠标事件等。 因此让我们添加一个按钮来看看如何把 MVVM 类应用起来。 我们在同样的 UI 上添加了一个‘Calculate tax’按钮,当用户按下按钮,它将根据“Sales Amount”值计算出税值并显示在界面上。
在这里插入图片描述
因此为了在 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”方法。
现在,我们想要在 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 类的引用)。
在这里插入图片描述
下面是简短的代码片段,有四点需要注意:

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

上面的 command 代码中,ViewModel 对象是通过构造函数传递进来。所以 ViewModel 类需要创建一个 command 对象来暴露这个对象的“ICommand”接口。这个“ICommand”接口将被 WPF XAML 使用并调用。

下面是一些关于“CustomerViewModel”类使用 command 类的要点:
  1. command 类是“CustomerViewModel”类的私有成员。
  2. 在“CustomerViewModel”类的构造函数中将当前对象的实例传递给 command 类。在之前解释 command
    类的一节中我们说了 command 类构造函数获取 ViewModel 类的实例。因此在这一节中我们正是将当前实例传递给 command
    类。
  3. command 对象是通过以“ICommand”接口的形式暴露出来,这样才可以被 XAML 所使用。
using System.ComponentModel;

public class CustomerViewModel 
{
…
…
private ButtonCommand objCommand; //  Point 1
        public CustomerViewModel()
        {
            objCommand = new ButtonCommand(this); // Point 2
        }
        public ICommand btnClick // Point 3
        {
            get
            {
                return objCommand;
            }
        }..
}

在你的 UI 中添加一个按钮,这样就可以把按钮的执行动作连接到暴露的“ICommand”接口。现在打开 button 的属性栏,选择 command 属性,右击创建一个数据绑定。
在这里插入图片描述
然后选择静态资源(Static Resource),并将“ButtonCommand”附加到button上。
在这里插入图片描述
当你点击了 Calculate Tax 按钮,它就执行了“CalculateTax”方法。并将税值结果存在“_tax”变量中。关于“CalculateTax”方法代码,可以阅读前面的小节“第三步:添加执行动作和“INotifyPropertyChanged”接口”。

换句话说,税值计算过程并不会自动通知给 UI。所以我们需要从对象发送某种通知给 UI,告诉它税值已经变化了,UI 需要重新载入绑定值。
在这里插入图片描述
因此,在 ViewModel 类中我们需要发送 INotify 事件给视图。

在这里插入图片描述
为了让你的 ViewModel 类能够实现通知,我们必须做三件事情。这三件事情都在下面的代码注释中指出,例如 Point1, Point2 和 Point3。

Point1: 如下面代码那样实现“INotifyPropertyChanged”接口。一旦你实现了该接口,它就创建了对象的“PropertyChangedEventHandler”事件。

Point2 和 3: 在“Calculate”方法中用“PropertyChanged”对象去触发事件,并在其中指定了某个属性的通知。在这里是“Tax”属性。安全起见,我们同样也要检查“PropertyChanged”是否不为空。

public class CustomerViewModel : INotifyPropertyChanged // Point 1
{..
        public void Calculate()
        {
            obj.CalculateTax();
            if (PropertyChanged != null) // Point 2
            {
                PropertyChanged(this,new PropertyChangedEventArgs("Tax"));
            // Point 3
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
}

如果你运行程序,你应该可以看见当点击按钮后“Tax”值被更新了。

1、什么是命令? 参照事件,但与事件不同 特定的指令、有明确的执行内容,带有一定的强制性
2、命令的用途 :
A第一个目的是将语义和调用命令的对象与执行命令的逻辑分开。 这允许多个和不同的源调用相同的命令逻辑,并允许针对不同的目标自定义命令逻辑。
B、命令的另一个用途是统一的指示操作是否可用。
WPF中的命令
1、预定义命令
MediaCommands(24个) Play、Stop、Pause…….
ApplicationCommands(23个) New、Open、Copy、Cut、Print………
NavigationCommands(16个) GoToPage、LastPage、、。。。。
ComponentCommands(27个)
EditingCommands(54个)
2、内置命令
Copy、Cut、Paste
3、自定义命令
RoutedCommand、RoutedUICommand
事件和命令都可以分离,都要以执到ViewModel或Model里面的代码,见下节

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

MVVM模式中的命令绑定
1、自定义命令ICommand接口 是否可执行的方法:CanExecute 主要执行逻辑:Execute
触发检查是否可执行:CanExecuteChanged 到目前为止,我们用 MVVM 框架创建了一个简单的界面。这个界面同时包含了属性和命令实现。我们拥有了一个视图,它的 UI 输入元素(例如 textbox)通过绑定和 ViewModel 连接起来,它的任何执行动作(例如按钮点击)通过命令和 ViewModel 连接起来。
2、命令参数传递
MVVM模式中的鼠标行为与命令绑定
1、鼠标输入的命令绑定
(InputBindings: )
单击鼠标左键 :LeftClick 双击鼠标左键:LeftDoubleClick
单击鼠标中键 :MiddleClick 双击鼠标中键:MiddleDoubleClick
单击鼠标右键:RightClick 双击鼠标右键:RightDoubleClick
不执行任何操作:None 旋转鼠标滚轮:WheelClick
2、事件命令绑定
利用:
System.Windows.Interactivity
MVVMLight-》 PassEventArgsToCommand=true

ViewModel 和内部的 Model 通讯,如下图:
在这里插入图片描述

但是在上面的结构中还有一个问题:command 类和 ViewModel 类存在着过度耦合的情况。如果你还记得 command 类代码(我在下面贴出来了)中的构造函数是传递了 ViewModel 对象,这意味着这个 command 类无法被其它的 ViewModel 类所复用。

public class ButtonCommand : ICommand
{
        private CustomerViewModel obj; // Point 1
        public ButtonCommand(CustomerViewModel _obj) // Point 2
        {
            obj = _obj;
        }
......
......
......

}

在这里插入图片描述
但是在考虑了所有情况之后,让我们逻辑地思考下“什么是一个动作?”。它是一个事件,可以由用户从鼠标点击(左键或右键),按钮点击,菜单点击,功能键按下等。所以应该有一种方式通用化这些动作,并且让各种 ViewModel 有一种更通用的方法去绑定它。

逻辑上讲,如果你认为任务动作是一些方法和函数的封装逻辑。那有什么是“方法”和“函数”的通用表达方式呢?…努力想想…再想想…“委托”,“委托”,没错,还是“委托”。

我们需要两个委托,一个给“CanExecute”,另一个给“Execute”。“CanExecute”返回一个布尔值用来验证以及根据验证来使能(Enable)或者禁用(Disable)用户界面。“Execute”委托则将在“CanExecute”委托返回 true 时执行。

public class ButtonCommand : ICommand
{
    public bool CanExecute(object parameter) // Validations
    {
    }
    public void Execute(object parameter) // Executions
   {
   }
  }

因此,换句话说,我们需要两个委托,一个返回布尔值,另一个执行动作并返回空。所以,创建一个“Func”和一个“Action”如何?“Func”和“Action”都可以用来创建委托。

如果你还不熟悉 Func 和 Action,可以看下下面这个视频。(译注:作者在这里提供了一个 YouTube 的视频链接,大概说的就是 C# 中 Func<> 和 Action<> 这两个委托的区别,前者 Func<> 模版参数包含返回值类型,而 Action<> 表示无返回值的泛型委托,参见这里)
通过使用委托的方法,我们试着创建一个通用的 command 类。我们对 command 类做了三个修改(代码参见下面),同时我也标注了三点 Point 1,2 和 3。

Point1: 我们在构造函数中移除了 ViewModel 对象,改为接受两个委托,一个是“Func”,另一个是“Action”。“Func”委托用作验证(例如验证何时动作将被执行),而“Action”委托用来执行动作。两个委托都是通过构造函数参数传递进来,并赋值给类内部的对应私有成员变量。

Point2 和 3: Func<> 委托(WhentoExecute)被“CanExecute”调用,执行动作的委托 Whattoexecute 则是在“Execute”中被调用。

public class ButtonCommand : ICommand
{
		private Action WhattoExecute;
		private Func<bool> WhentoExecute;
        public ButtonCommand(Action What , Func<bool> When) // Point 1
        {
            WhattoExecute = What;
            WhentoExecute = When;
        }
		public bool CanExecute(object parameter)
        {
            return WhentoExecute(); // Point 2
        }
		public void Execute(object parameter)
        {
            WhattoExecute(); // Point 3
        }
}

在 Model 类中我们已经知道要执行什么了(例如“CalculateTax”),我们也创建一个简单的函数“IsValid”来验证“Customer”类是否有效。

public class Customer
 {
		public void CalculateTax()
		{
		  if (_Amount > 2000)
		  {
		     _Tax = 20;
		  }
		else if (_Amount > 1000)
		 {
		    _Tax = 10;
		}
		else
		 {
		   _Tax = 5;
		  }
		 }
		
		public bool IsValid()
		{
		  if (_Amount == 0)
		{
		   return false;
		 }
		else
		 {
		   return true;
		 }
   }
}

在 ViewModel 类中我们同时传递函数和方法给 command 类的构造函数,一个给“Func”,一个给“Action”。

public class CustomerViewModel : INotifyPropertyChanged
{
	private Customer obj = new Customer();
	privateButtonCommandobjCommand;
	publicCustomerViewModel()
	{
		objCommand = new ButtonCommand(obj.CalculateTax,
		obj.IsValid);
    }
}

这样使得框架更好,更解耦, 使得这个 command 类可以以一个通用的方式被其它 ViewModel 引用。下面是改善后的架构, 需要注意 ViewModel 如何通过委托(Func和Action)和 command 类交互。

在这里插入图片描述

委托与命令
首先,WPF为每一个控件都提供了一个Command的依赖属性,因为任何实现了ICommand接口的类都可以通过绑定的方式和前台关联起来,我们这里对比下命令和路由事件的区别可以发现,路由事件必须写在Code-Behind代码中,而命令可以写在ViewModel里,所以直观上来讲命令更加自由灵活。下面我们以一个简单的例子来剖析这两者间的关系。

我们知道使用Command需要实现ICommand接口,所以实现起来是相对容易的,我们这里继续沿用MVVM light中的RelayCommand这个名字:

public class RelayCommand : ICommand
{
    private readonly Action<object> m_execute;
    private readonly Predicate<object> m_canExecute;

    public RelayCommand(Action<object> execute)
    {
        this.m_execute = execute;
    }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        this.m_execute = execute;
        this.m_canExecute = canExecute;
    }

    public bool CanExecute(object parameter)
    {
        if (m_canExecute == null)
            return true;

        return m_canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    public void Execute(object parameter)
    {
        this.m_execute(parameter);
    }
}

我们可以看到这里有两个重要的方法,Execute和CanExecute,前者是一个void类型的方法,后者是一个bool类型的方法。当我们需要判断控件是否应该执行某一个过程的时候,CanExecute这个方法就可以帮助我们完成判断,而Execute方法显然是执行某一个过程的方法,可以注意到通过委托我们让调用者更加自由和灵活地传入一个方法,这是我喜欢这种设计的一个地方,因为我的一位同事就对普通的路由事件表示无法理解。

这里需要说明的是CanExecuteChanged这个事件,这个和INotifyPropertyChanged接口中的PropertyChanged成员类似,是在当CanExecute发生变化的时候通知视图的,我对这里的理解是CanExecute本身就具备对某一个过程是否应该被执行的支持,可是遗憾的是在,在我参与的项目中,人们更喜欢声明大量的布尔类型变量来处理这里的相关逻辑,因此无论是对Property还是Command而言,在ViewModel里都是看起来非常丑陋的代码实现。

好了,现在对我们而言,这是一个非常愉快的旅程,因为在完成对RelayCommand的定义以后,我们绑定命令和定义命令的过程是非常简单的。除此以外,WPF提供了一个RoutedCommand类,该类实现了ICommand接口,我怀疑MVVM light中的EventToCommand正是通过这种思路实现了路由事件到命令的转换,因为只有RoutedCommand具备访问UI事件的能力,这里我们仅仅提出问题,进一步的思考和验证我们可以留到以后去做。下面我们来看看如何声明和绑定命令:

public RelayCommand ClickCommand
{
    get
    {
        return new RelayCommand((arg)=>
        {
            MessageBox.Show("Click");
        });
    }
}

显然这个ClickCommand将作为一个属性出现在ViewModel中,我选择了一个我最喜欢用的方法,或许这样看起来非常低端。可是在调试界面的过程中,它要比断点调试更为直接和直观。当我们的ViewModel中出现这样的只读属性的时候,直接在Get访问器中定义它的返回值似乎是最直接有效的方案,可问题是Get访问器应该是非常“轻”的,因为大量业务逻辑的渗透,现在连这里都不能保留其纯粹性了吗?这让我表示非常郁闷啊。

<Window x:Class="WPFLearning.Window1"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="Window" Height="300" Width="300">
    <Grid>
        <Button Content="Button" HorizontalAlignment="Center" 
            VerticalAlignment="Center" Command="{Binding ClickCommand }"/>  
    </Grid>
</Window>

现在你可以发现,委托和命令结合得非常好,当你发现这一切如此美妙的时候,回归本质或许是我们最喜欢的事情,就像纯粹的你我一样,在这个世界上,我们彼此装点着各自生命里美好的风景,执著而勇敢、温暖而明媚,那些周而复始的日子里,总能听到梦想开花的声音。

第五步:利用 PRISM
最后如果有一个框架能帮助实现我们的 MVVM 代码那就更好了。PRISM 就是其中一个可复用的框架。PRISM 的主要用途是为了提供模块化开发,但是它提供了一个很好的“DelegateCommand”类拿来代替我们自己创建的 command 类。

所以,第一件事情就是从这里下载 PRISM,编译这个解决方案,添加“Microsoft.Practices.Prism.Mvvm.dll”和“Microsoft.Practices.Prism.SharedInterfaces.dll”这两个 DLL 库的引用。

你可以去掉自定义的 command 类,导入“Microsoft.Practices.Prism.Commands”名称空间, 然后以下面代码的方式使用 DelegateCommand。

public class CustomerViewModel : INotifyPropertyChanged
{
private Customer obj = new Customer();
private DelegateCommand  objCommand;
public CustomerViewModel()
        {
objCommand = new DelegateCommand(obj.CalculateTax, obj.IsValid);
        }
…………
…………
…………
…………

}    
}

MVVM: Model、View、ViewModel的概念

我要着重介绍的是ViewModel, 从字面意思就能看出ViewModel是View层的Model,也就是说ViewModel就是View的逻辑层。 ViewModel就是为了存储View的相关状态、行为,而不仅仅是View绑定的一些字段。

举个例子:比如界面当前的编辑状态、选择状态等等状态都是ViewModel存储的。

再举个例子:当View收到用户的输入时,可以通过Command等来调用ViewModel的处理, ViewModel继续调用Model层的增删改或者通过Event通知别的ViewModel处理。

也可以认为ViewModel是Model的一种特殊例子。 好多Model层的数据并不能直接呈现给某个View,因此需要ViewModel做包装和转换。换句话说, 传统的DTO(Data Transfer Object)应该定义在ViewModel层来转换Model的某些数据。

总结下:View通过DataContext可以知道ViewModel, ViewModel不知道View!, ViewModel不应该对View有引用关系。当ViewModel对View的状态和行为都进行了存储时, 就能完全脱离View而存在。

和MVC的对比:

MVC一般都是Controller先接受到用户输入, 然后Controller来创建View, 而且Controller创建完View后就不关心View了。下次用户的输入又再次先进入Controller来处理。

MVVM是View先接受到用户输入, 然后View通知ViewModel;
MVVM的定义

网络上的解释是Model-View-ViewModel的缩写,可是对于一个初学者来说,这玩意真有点难以理解。

下面我们再用白话解释一次:

 - Model-就是模型(简言之就是数据库的表,或者要显示的元素结构)
 - View-就是UI,就是用户界面,就是XAML文件。最终显示给用户的效果。
 - ViewModel—就是控制View的对象,或者对应到代码中就是一个Class。
     该Class包含:Model或者Model的集合,包含View上的命令(Command)

幸运的是,MVVM相对MVP的确发生了些许改变,一个重要的特性是双向绑定,View的变化将自动反映在ViewModel中,而显然ViewModel是一个为View打造的Model,它可以容纳更多的普通的Model,因此从某种意义上来说,ViewModel依然作为连接View和Model的桥梁而出现,它是对View的一种抽象,而抽象有两层含义,即数据(Property)和行为(Command),一旦你明白了这一点,ViewModel无非是一个特殊而普通的类而已,特殊是因为它需要实现INotifyPropertyChanged接口,普通是因为它继承了面向对象编程(OOP)的基本思想。

————————————————
在这里插入图片描述
在这里插入图片描述

注意:一般来说对Model的操作(换言之对数据对象的操作),一般放在Command中,界面交互的也可以通过CommandParameter等方式进行交互,如果复杂的话,考虑使用在View中控制。

这样一个简单的MVVM就OK了

XAML:[View]

<Grid x:Name="LayoutRoot">

<TextBlock Text="{Binding StrName,Mode=OneWay}"></TextBlock>

</Grid>

Model[cs]:

public class XXClassModel:NotificationObject//【这个引用可以直接使用using Microsoft.Practices.Prism.ViewModel;】

public class XXClassModel:NotificationObject
{
	private string strName;
	
	public string StrName	
	{
	
		get{return strName;}
		
		set{		
			strName=value;			
			RaisePropertyChanged("StrName");//通知界面,注意大小写			
			//这里还可以做更多的操作,也就是对目标属性赋值的时候执行的操作		
		}	
	}
}

ViewModel[cs]:

public class XXClassViewModel:NotificationObject
{
		public void XXClassViewModel		
		{		
			item=new XXClassModel (){StrName="OK"}		
		}		
		private XXClassModel item;		
		public XXClassModel Item		
		{		
			get{return item;}			
			set{			
				strName=value;				
				RaisePropertyChanged("Item");//通知界面				
				//这里还可以做更多的操作,也就是对目标属性赋值的时候执行的操作		
		}		
	}

}

再到View.cs的构造函数中写

this.LayeroutRoot.DataContext=new XXClassViewModel();

至此,全部完成!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

是刘彦宏吖

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值