WPF快速入门系列(8)——MVVM快速入门

一、引言

  在前面介绍了WPF一些核心的内容,其中包括WPF布局、依赖属性、路由事件、绑定、命令、资源样式和模板。然而,在WPF还衍生出了一种很好的编程框架,即WVVM,在Web端开发有MVC,在WPF客户端开发中有MVVM,其中VM就相当于MVC中C(Control)。在Web端,微软开发了Asp.net MVC这样的MVC框架,同样在WPF领域,微软也开发了Prism这样的MVVM框架。Prism项目地址是:http://compositewpf.codeplex.com/SourceControl/latest。大家有兴趣的可以下载源码研究下。

  本文所有源码下载:FristMVVMProject.zip

二、MVVM模式是什么?

  既然讲到MVVM模式,自然第一个问题就是MVVM的含义。MVVM是Model-View-ViewModel的缩写形式,它通常被用于WPF或Silverlight开发。这三者之间的关系如下图所示:

  下面我们分别来介绍下这三部分。

模型(Model)

  Model——可以理解为带有字段,属性的类。

视图(View)

  View——可以理解为我们所看到的UI。

视图模型(View Model)

View Model在View和Model之间,起到连接的作用,并且使得View和Model层分离。View Model不仅仅是Model的包装,它还包含了程序逻辑,以及Model扩展,例如,如果Model中有一个公开属性不需要在UI上显示,此时我们可以不再View Model中去定义它。

在MVVM模式下,WPF程序的运行流程如下图所示:

  在MVVM中,VM的地位可以说是举足轻重。使用MVVM模式具有以下几个特点:

  • 视图的cs文件包括极少的代码,其核心逻辑都被放在View Model类中,从而使得程序逻辑与视图耦合度降低。
  • ViewModel类作为View的DataContext。
  • 在MVVM下,所有的事件和动作都被当成命令,如按钮的点击操作,此时不是触发点击事件,而是绑定到一个点击命令,再由命令去执行对应的逻辑。

三、使用MVVM模式来实现WPF程序

  前面介绍了MVVM一些基础知识,下面通过一个实例来说明下如何在WPF程序中应用MVVM模式。在之前实现WPF程序时,我们可能会把所有的后台逻辑都放在视图的后台文件中,这样的实现方式的好处更直观,方便,对于一些小的应用程序这样做当然没什么问题,但是对于复杂的应用程序这样写的话,可能会导致后台代码显得非常臃肿,到最好变得难以维护。此时想到的解决方案就是职责分离,使后台的逻辑分离到其他类中,MVVM其实我理解就是达到这个目的。下面我们按照MVVM的组成部分来实现下这个MVVM程序。

  第一步:自然是数据部分了,即Model层的实现。在这里定义了一个Person类,其中包含了2个基本的属性。

public class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

  为了进行测试,下面创建一个静态方法来获得测试数据。

public class PersonDataHelper
    {
        public static ObservableCollection<Person> GetPersons()
        {
            ObservableCollection<Person> samplePersons = new ObservableCollection<Person>();
            samplePersons.Add(new Person() {Name = "张三", Age = 33});
            samplePersons.Add(new Person() { Name ="王五", Age= 22 });
            samplePersons.Add(new Person() { Name = "李四", Age = 35 });
            samplePersons.Add(new Person() { Name = "LearningHard", Age = 27 });
            return samplePersons;
        }
    }

  第二步:实现ViewModel层,实现数据和界面之间的逻辑。在视图模型类中,包含了属性和命令,因为在MVVM中,事件都当成命令来进行处理,其中命令只能与具有Command属性的控件进行绑定。既然要包含命令,首先就需要实现一个命令,这里自定义的命令需要实现ICommand接口。这里我们定义了一个QueryCommand。具体的实现代码如下所示:

public class QueryCommand :ICommand
    {
        #region Fields
        private Action _execute;
        private Func<bool> _canExecute;
        #endregion 

        public QueryCommand(Action execute)
            : this(execute, null)
        {
        }
        public QueryCommand(Action execute, Func<bool> canExecute)
        {
            if (execute == null)
                throw new ArgumentNullException("execute");
            _execute = execute;
            _canExecute = canExecute;
        }

        #region ICommand Member

        public event EventHandler CanExecuteChanged
        {
            add
            {
                if (_canExecute != null)
                {
                    CommandManager.RequerySuggested += value;

                }
            }
            remove
            {
                if (_canExecute != null)
                {
                    CommandManager.RequerySuggested -= value;

                }
            }
        }

        public bool CanExecute(object parameter)
        {
            return _canExecute == null ? true : _canExecute();
        }

        public void Execute(object parameter)
        {
            _execute();
        }
        #endregion
    }

  接下来就是定义我们的ViewModel类了,具体的实现代码如下所示:

 1 public class PersonListViewModel : INotifyPropertyChanged
 2     {
 3         #region Fields
 4         private string _searchText;
 5         private ObservableCollection<Person> _resultList;
 6         #endregion
 7
 8         #region Properties
 9
10         public ObservableCollection<Person> PersonList { get; private set; }
11
12         // 查询关键字
13         public string SearchText
14         {
15             get { return _searchText; }
16             set
17             {
18                 _searchText = value;
19                 RaisePropertyChanged("SearchText");
20             }
21         }
22
23         // 查询结果
24         public ObservableCollection<Person> ResultList
25         {
26             get { return _resultList; }
27             set
28             {
29                 _resultList = value;
30                 RaisePropertyChanged("ResultList");
31             }
32         }
33
34         public ICommand QueryCommand
35         {
36             get { return new QueryCommand(Searching, CanSearching); }
37         }
38
39         #endregion
40
41         #region Construction
42         public PersonListViewModel()
43         {
44             PersonList = PersonDataHelper.GetPersons();
45             _resultList = PersonList;
46         }
47
48         #endregion
49
50         #region Command Handler
51         public void Searching()
52         {
53             ObservableCollection<Person> personList = null;
54             if (string.IsNullOrWhiteSpace(SearchText))
55             {
56                 ResultList = PersonList;
57             }
58             else
59             {
60                 personList = new ObservableCollection<Person>();
61                 foreach (Person p in PersonList)
62                 {
63                     if (p.Name.Contains(SearchText))
64                     {
65                         personList.Add(p);
66                     }
67                 }
68                 if (personList != null)
69                 {
70                     ResultList = personList;
71                 }
72             }
73         }
74
75         public bool CanSearching()
76         {
77             return true;
78         }
79
80         #endregion
81
82         #region INotifyPropertyChanged Members
83
84         public event PropertyChangedEventHandler PropertyChanged;
85
86         #endregion
87
88         #region Methods
89         private void RaisePropertyChanged(string propertyName)
90         {
91             // take a copy to prevent thread issues
92             PropertyChangedEventHandler handler = PropertyChanged;
93             if (handler != null)
94             {
95                 handler(this, new PropertyChangedEventArgs(propertyName));
96             }
97         }
98         #endregion
99     }

  第三步:实现View层,设计我们的视图,设置它的DataContext属性为ViewModel类。具体的XAML代码如下所示:

<Window x:Class="MVVMDemo.View.PersonsView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:MVVMDemo.ViewModel"
        Title="PersonsView" Height="350" Width="400">
    <!--设置DataContex是ViewModel类,当然你也可以使用后台代码设置-->
    <Window.DataContext>
        <local:PersonListViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="50"/>
            <RowDefinition Height="*"/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Name="searchtxt" Text="{Binding  Path=SearchText, Mode=TwoWay}" HorizontalAlignment="Left" Height="30" Width="280" Margin="10,0,0,0"></TextBox>
        <Button Grid.Row="0" Name="searchBtn" Content="Search" Command="{Binding Path=QueryCommand}" Width="80" Height="30" HorizontalAlignment="Right" Margin="0,0,10,0"></Button>
        <DataGrid Grid.Row="1" Name="datGrid"
                  HorizontalAlignment="Center"
                  VerticalAlignment="Top" ItemsSource="{Binding Path=ResultList}" Width="300"></DataGrid>

    </Grid>
</Window>

  到此,我们的MVVM的WPF程序就已经完成了,下面就是要看看程序是否达到我们预期的目的。具体的运行结果如下图所示:

四、总结

  到这里,本文的内容就分享完了,并且本文也是WPF系列的最后一篇了,希望这个系列可以使得初学者快速上手WPF编程。在接下来的时间里,我打算写一些具有实战性的内容,因为我之前都是分享一些初级的入门系列,接下来打算分享一些实际的项目实现,以及领域驱动设计方面的内容,希望得到大家的督促和支持。

时间: 2024-12-25 16:48:50

WPF快速入门系列(8)——MVVM快速入门的相关文章

转:[Silverlight入门系列]使用MVVM模式(9): 想在ViewModel中控制TreeView节点展开?

很多童鞋看了我的博客以后也去实践MVVM,但却发现Silverlight实践中的MVVM很难实现,比纯粹的CodeBehind难度大很多.首先是原来在xaml.cs的CodeBehind部分很容易控制界面逻辑,现在这部分逻辑移到ViewModel里面去了以后,就很难调用CodeBind的部分;其次是很多View和ViewModel.或者一个ViewModel多个View,他们之间如何通 很多童鞋看了我的博客以后也去实践MVVM,但却发现Silverlight实践中的MVVM很难实现,比纯粹的Co

「数据挖掘入门系列」Python快速入门

Python环境搭建 本次入门系列将使用Python作为开发语言.要使用Python语言,我们先来搭建Python开发平台.我们将基于Python 2.7版本.以及Python的开发发行版本Anaconda版本来开发. Anaconda指的是一个开源的Python发行版本,其包含了conda.Python等180多个科学包及其依赖项. 下载地址: https://www.anaconda.com/distribution/,注意要下载2.7版本 下载好Anaconda安装包后,即可安装,安装好后

Java入门系列之集合LinkedList入门(八)

前言 前面两节内容我们详细介绍了ArrayList,一是手写实现ArrayList数据结构,而是通过分析ArrayList源码看看内置实现,关于集合内容一如既往,本节课我们继续学习集合LinkedList,我们首先入门LinkedList数据结构,然后再去看看LinkedList源码是如何实现的,我们开始吧. LinkedList入门 LinkedList内置是通过双链表数据结构来存储数据,和ArrayList不同的是,ArrayList属于真正意义物理意义上的线性结构,而LinkedList也

UIPath入门系列九之Citrix入门

接着讲解Citrix Automation 一.虚拟机的录制方法 UIPath录制虚拟机,不能使用普通的录制功能呢,需要使用如图像识别或者OCR技术.常见的虚拟机环境有Citrix, RDP,Microsoft Azure等. 二.Citrix识别方法 1. Click Image:选择Citrix中的任意一个对象 2. Click Text:选择Citrix中的文本框 3. Type into:在Citrix文本框中输入字符 4. Select & Copy: 5. Scrape Relati

快速入门系列--WebAPI--03框架你值得拥有

接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI,其核心概念就是构建REST风格的Web服务,把一起数据视为资源,无论是服务请求或者是数据操作,与以前的SOAP和XML-RPC架构风格有很大不同.说道这,很多读者可能想到WCF中不是早都有了REST风格的服务么,为什么还需要这个WebAPI?确实如此,不过WCF中的该类型服务显得比较复杂,因为其通

快速入门系列--MVC--01概述

虽然使用MVC已经不少年,相关技术的学习进行了多次,但是很多技术思路的理解其实都不够深入.其实就在MVC框架中有很多设计模式和设计思路的体现,例如DependencyResolver类就包含我们常见的DI依赖注入概念和注册表模式(GetService)等内容,ExceptionFilter等过滤器就体现AOP的概念,整个MVC内置了一个IOC容器,基本上所有的框架类的对象都是通过这种方式来创建的.此外,一直觉得很j2ee的spring很棒,其实如果大家很熟悉EHAB(微软企业开发库)的话,就会发

快速入门系列--WebAPI--01基础

ASP.NET MVC和WebAPI已经是.NET Web部分的主流,刚开始时两个公用同一个管道,之后为了更加的轻量化(WebAPI是对WCF Restful的轻量化),WebAPI使用了新的管道,因此两者相关类的命名空间有细微差异,在使用时需要注意. WebAPI学习系列目录如下,欢迎您的阅读! 快速入门系列--WebAPI--01基础 快速入门系列--WebAPI--02进阶 快速入门系列--WebAPI--03框架你值得拥有 快速入门系列--WebAPI--04在老版本MVC4下的调整 W

快速学习系列--目录

为了便于查看,特此建立一个系列目录,感谢大家的自己,加油. ASP.NET WebAPI 快速入门系列--WebAPI--01基础 快速入门系列--WebAPI--02进阶 快速入门系列--WebAPI--03框架你值得拥有 .NET CLR 快速入门系列--CLR--01基本概念 快速入门系列--CLR--02多线程 快速入门系列--CLR--03泛型集合 ASP.NET MVC 快速入门系列--MVC--01概述 快速入门系列--MVC--02路由 快速入门系列--MVC--03控制器和IO

爬虫入门系列(三):用 requests 构建知乎 API

爬虫入门系列目录: 爬虫入门系列(一):快速理解HTTP协议 爬虫入门系列(二):优雅的HTTP库requests 爬虫入门系列(三):用 requests 构建知乎 API 在爬虫系列文章 优雅的HTTP库requests 中介绍了 requests 的使用方式,这一次我们用 requests 构建一个知乎 API,功能包括:私信发送.文章点赞.用户关注等,因为任何涉及用户操作的功能都需要登录后才操作,所以在阅读这篇文章前建议先了解Python模拟知乎登录.现在假设你已经知道如何用 reque