zoukankan      html  css  js  c++  java
  • 走进WPF之MVVM完整案例

    学习WPF如果不学MVVM,仿佛缺少了灵魂。那什么是MVVM呢?为什么要学MVVM呢,本以一个简单的增删改查的小例子,简述MVVM的基本知识及如何通过进行MVVM架构的程序开发,仅供学习分享使用,如有不足之处,还请指正。

    什么是MVVM?

    MVVM是Model-View-ViewModel的简写。它本质上就是MVC (Model-View- Controller)的改进版。即模型-视图-视图模型。分别定义如下:

    • 【模型】指的是后端传递的数据。
    • 【视图】指的是所看到的页面。
    • 【视图模型】mvvm模式的核心,它是连接view和model的桥梁。它有两个方向:
      • 一是将【模型】转化成【视图】,即将后端传递的数据转化成所看到的页面。实现的方式是:数据绑定。
      • 二是将【视图】转化成【模型】,即将所看到的页面转化成后端的数据。实现的方式是:DOM 事件监听。这两个方向都实现的,我们称之为数据的双向绑定。

    MVVM示意图如下所示:

    安装MvvmLight插件

    项目名称右键-->管理NuGet程序包-->搜索MvvmLight-->安装。如下所示:

     弹出接受许可证窗口,点击【接受】如下所示:

     MvvmLight安装成功后,自动引用需要的第三方库,并默认生成示例内容,有些不需要的需要删除,如下所示:

    MVVM示例截图

    主要通过MVVM实现数据的CRUD【增删改查】基础操作,如下所示:

     MVVM开发步骤

    1. 创建Model层

    本例主要是对学生信息的增删改查,所以创建Student模型类,如下所示:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 
     7 namespace WpfApp3.Models
     8 {
     9     /// <summary>
    10     /// 学生类
    11     /// </summary>
    12     public class Student
    13     {
    14         /// <summary>
    15         /// 唯一标识
    16         /// </summary>
    17         public int Id { get; set; }
    18 
    19         /// <summary>
    20         /// 学生姓名
    21         /// </summary>
    22         public string Name { get; set; }
    23 
    24         /// <summary>
    25         /// 年龄
    26         /// </summary>
    27         public int Age { get; set; }
    28 
    29         /// <summary>
    30         /// 班级
    31         /// </summary>
    32         public string Classes { get; set; }
    33     }
    34 }

    2. 创建DAL层

    为了简化示例,模拟数据库操作,构建基础数据,如下所示:

     1 using System;
     2 using System.Collections.Generic;
     3 using System.Linq;
     4 using System.Text;
     5 using System.Threading.Tasks;
     6 using WpfApp3.Models;
     7 
     8 namespace WpfApp3.DAL
     9 {
    10     public class LocalDb
    11     {
    12         private List<Student> students;
    13 
    14         public LocalDb() {
    15             init();
    16         }
    17 
    18         /// <summary>
    19         /// 初始化数据
    20         /// </summary>
    21         private void init() {
    22             students = new List<Student>();
    23             for (int i = 0; i < 30; i++)
    24             {
    25                 students.Add(new Student()
    26                 {
    27                     Id=i,
    28                     Name=string.Format("学生{0}",i),
    29                     Age=new Random(i).Next(0,100),
    30                     Classes=i%2==0?"一班":"二班"
    31                 });
    32             }
    33         }
    34 
    35         /// <summary>
    36         /// 查询数据
    37         /// </summary>
    38         /// <returns></returns>
    39         public List<Student> Query()
    40         {
    41             return students;
    42         }
    43 
    44         /// <summary>
    45         /// 按名字查询
    46         /// </summary>
    47         /// <param name="name"></param>
    48         /// <returns></returns>
    49         public List<Student> QueryByName(string name)
    50         {
    51             return students.Where((t) => t.Name.Contains(name)).ToList();//FindAll((t) => t.Name.Contains(name));
    52         }
    53 
    54         public Student QueryById(int Id)
    55         {
    56             var student = students.FirstOrDefault((t) => t.Id == Id);
    57             if (student != null)
    58             {
    59                 return new Student() { 
    60                     Id=student.Id,
    61                     Name=student.Name,
    62                     Age=student.Age,
    63                     Classes=student.Classes
    64                 };
    65             }
    66             return null;
    67         }
    68 
    69 
    70         /// <summary>
    71         /// 新增学生
    72         /// </summary>
    73         /// <param name="student"></param>
    74         public void AddStudent(Student student)
    75         {
    76             if (student != null)
    77             {
    78                 students.Add(student);
    79             }
    80         }
    81 
    82         /// <summary>
    83         /// 删除学生
    84         /// </summary>
    85         /// <param name="Id"></param>
    86         public void DelStudent(int Id)
    87         {
    88             var student = students.FirstOrDefault((t) => t.Id == Id); //students.Find((t) => t.Id == Id);
    89             if (student != null)
    90             {
    91                 students.Remove(student);
    92             }
    93             
    94         }
    95     }
    96 
    97 
    98 }

    3. 创建View层

    View层与用户进行交互,用户数据的展示,及事件的响应。在本例中,View层主要有数据查询展示,新增及编辑页面。

    在View层,主要是命令的绑定,及数据的绑定。

    1. 在DataGridTextColumn中通过Binding="{Binding Id}"的形式绑定要展示的列属性名。
    2. 在Button按钮上通过Command="{Binding AddCommand}"的形式绑定要响应的命令。
    3. 在TextBox文本框中通过Text="{Binding Search}"的形式绑定查询条件属性。

    数据展示窗口,如下所示:

     1 <Window x:Class="WpfApp3.MainWindow"
     2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     6         xmlns:local="clr-namespace:WpfApp3"
     7         mc:Ignorable="d"
     8         Title="MainWindow" Height="450" Width="800">
     9     <Grid>
    10         <Grid.RowDefinitions>
    11             <RowDefinition Height="80"></RowDefinition>
    12             <RowDefinition Height="*"></RowDefinition>
    13         </Grid.RowDefinitions>
    14         <StackPanel Orientation="Horizontal" Grid.Row="0" Margin="5" VerticalAlignment="Center">
    15             <TextBlock Text="姓名:" Margin="10" Padding="5"></TextBlock>
    16             <TextBox x:Name="sname" Text="{Binding Search}" Width="120" Margin="10" Padding="5"></TextBox>
    17             <Button x:Name="btnQuery" Content="查询" Margin="10" Padding="5" Width="80" Command="{Binding QueryCommand}"></Button>
    18             <Button x:Name="btnReset" Content="重置" Margin="10" Padding="5" Width="80" Command="{Binding ResetCommand}"></Button>
    19             <Button x:Name="btnAdd" Content="创建" Margin="10" Padding="5" Width="80"  Command="{Binding AddCommand}"></Button>
    20         </StackPanel>
    21         <DataGrid x:Name="dgInfo" Grid.Row="1" AutoGenerateColumns="False" CanUserAddRows="False" CanUserSortColumns="False" Margin="10" ItemsSource="{Binding GridModelList}">
    22             <DataGrid.Columns>
    23                 <DataGridTextColumn Header="Id" Width="100"  Binding="{Binding Id}"></DataGridTextColumn>
    24                 <DataGridTextColumn Header="姓名" Width="100" Binding="{Binding Name}"></DataGridTextColumn>
    25                 <DataGridTextColumn Header="年龄" Width="100" Binding="{Binding Age}"></DataGridTextColumn>
    26                 <DataGridTextColumn Header="班级" Width="100" Binding="{Binding Classes}"></DataGridTextColumn>
    27                 <DataGridTemplateColumn Header="操作" Width="*">
    28                     <DataGridTemplateColumn.CellTemplate>
    29                         <DataTemplate>
    30                             <StackPanel Orientation="Horizontal" VerticalAlignment="Center" HorizontalAlignment="Center">
    31                                 <Button x:Name="edit" Content="编辑" Width="60" Margin="3" Height="25" CommandParameter="{Binding Id}" Command="{Binding DataContext.EditCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"></Button>
    32                                 <Button x:Name="delete" Content="删除" Width="60" Margin="3" Height="25"  CommandParameter="{Binding Id}" Command="{Binding DataContext.DeleteCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=DataGrid}}"></Button>
    33                             </StackPanel>
    34                         </DataTemplate>
    35                     </DataGridTemplateColumn.CellTemplate>
    36                 </DataGridTemplateColumn>
    37             </DataGrid.Columns>
    38         </DataGrid>
    39     </Grid>
    40 </Window>

    新增及编辑页面,如下所示:

     1 <Window x:Class="WpfApp3.Views.StudentWindow"
     2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
     3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     6         xmlns:local="clr-namespace:WpfApp3.Views"
     7         mc:Ignorable="d"
     8         Title="StudentWindow" Height="440" Width="500" AllowsTransparency="False" WindowStartupLocation="CenterScreen" WindowStyle="None">
     9     <Grid>
    10         <Grid.RowDefinitions>
    11             <RowDefinition Height="60"></RowDefinition>
    12             <RowDefinition></RowDefinition>
    13             <RowDefinition Height="60"></RowDefinition>
    14         </Grid.RowDefinitions>
    15         <TextBlock FontSize="30" Margin="10">修改学生信息</TextBlock>
    16         <StackPanel Grid.Row="1" Orientation="Vertical">
    17             <TextBlock FontSize="20" Margin="10" Padding="5">姓名</TextBlock>
    18             <TextBox x:Name="txtName" FontSize="20"  Padding="5" Text="{Binding Model.Name}"></TextBox>
    19             <TextBlock FontSize="20" Margin="10"  Padding="5">年龄</TextBlock>
    20             <TextBox x:Name="txtAge" FontSize="20"  Padding="5" Text="{Binding Model.Age}"></TextBox>
    21             <TextBlock FontSize="20" Margin="10"  Padding="5">班级</TextBlock>
    22             <TextBox x:Name="txtClasses" FontSize="20"  Padding="5" Text="{Binding Model.Classes}"></TextBox>
    23         </StackPanel>
    24         <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right">
    25             <Button x:Name="btnSave" Content="保存" Margin="10" FontSize="20" Width="100" Click="btnSave_Click" ></Button>
    26             <Button x:Name="btnCancel" Content="取消" Margin="10" FontSize="20" Width="100" Click="btnCancel_Click" ></Button>
    27         </StackPanel>
    28     </Grid>
    29 </Window>

    3. 创建ViewModel层

    ViewModel层是MVVM的核心所在,起到承上启下的作用。ViewModel需要继承GalaSoft.MvvmLight.ViewModelBase基类。

    ViewModel中属性实现数据的绑定,命令实现用户交互的响应。如下所示:

      1 using GalaSoft.MvvmLight;
      2 using GalaSoft.MvvmLight.Command;
      3 using System.Collections.Generic;
      4 using System.Collections.ObjectModel;
      5 using System.Linq;
      6 using System.Windows;
      7 using WpfApp3.DAL;
      8 using WpfApp3.Models;
      9 using WpfApp3.Views;
     10 
     11 namespace WpfApp3.ViewModel
     12 {
     13     /// <summary>
     14     /// 
     15     /// </summary>
     16     public class MainViewModel : ViewModelBase
     17     {
     18         #region 属性及构造函数
     19 
     20         private LocalDb localDb;
     21 
     22         private ObservableCollection<Student> gridModelList;
     23 
     24         public ObservableCollection<Student> GridModelList
     25         {
     26             get { return gridModelList; }
     27             set
     28             {
     29                 gridModelList = value;
     30                 RaisePropertyChanged();
     31             }
     32         }
     33 
     34         /// <summary>
     35         /// 查询条件
     36         /// </summary>
     37         private string search;
     38 
     39         public string Search
     40         {
     41             get { return search; }
     42             set
     43             {
     44                 search = value;
     45                 RaisePropertyChanged();
     46             }
     47         }
     48 
     49 
     50         /// <summary>
     51         /// 
     52         /// </summary>
     53         public MainViewModel()
     54         {
     55             localDb = new LocalDb();
     56             QueryCommand = new RelayCommand(this.Query);
     57             ResetCommand = new RelayCommand(this.Reset);
     58             EditCommand = new RelayCommand<int>(this.Edit);
     59             DeleteCommand = new RelayCommand<int>(this.Delete);
     60             AddCommand = new RelayCommand(this.Add);
     61         }
     62 
     63         #endregion
     64 
     65         #region command
     66 
     67         /// <summary>
     68         /// 查询命令
     69         /// </summary>
     70         public RelayCommand QueryCommand { get; set; }
     71 
     72         /// <summary>
     73         /// 重置命令
     74         /// </summary>
     75         public RelayCommand ResetCommand { get; set; }
     76 
     77         /// <summary>
     78         /// 编辑
     79         /// </summary>
     80         public RelayCommand<int> EditCommand { get; set; }
     81 
     82         /// <summary>
     83         /// 删除
     84         /// </summary>
     85         public RelayCommand<int> DeleteCommand { get; set; }
     86 
     87         /// <summary>
     88         /// 新增
     89         /// </summary>
     90         public RelayCommand AddCommand { get; set; }
     91 
     92         #endregion
     93 
     94         public void Query()
     95         {
     96             List<Student> students;
     97             if (string.IsNullOrEmpty(search))
     98             {
     99                 students = localDb.Query();
    100             }
    101             else
    102             {
    103                 students = localDb.QueryByName(search);
    104             }
    105 
    106             GridModelList = new ObservableCollection<Student>();
    107             if (students != null)
    108             {
    109                 students.ForEach((t) =>
    110                 {
    111                     GridModelList.Add(t);
    112                 });
    113             }
    114         }
    115 
    116         /// <summary>
    117         /// 重置
    118         /// </summary>
    119         public void Reset()
    120         {
    121             this.Search = string.Empty;
    122             this.Query();
    123         }
    124 
    125         /// <summary>
    126         /// 编辑
    127         /// </summary>
    128         /// <param name="Id"></param>
    129         public void Edit(int Id)
    130         {
    131             var model = localDb.QueryById(Id);
    132             if (model != null)
    133             {
    134                 StudentWindow view = new StudentWindow(model);
    135                 var r = view.ShowDialog();
    136                 if (r.Value)
    137                 {
    138                     var newModel = GridModelList.FirstOrDefault(t => t.Id == model.Id);
    139                     if (newModel != null)
    140                     {
    141                         newModel.Name = model.Name;
    142                         newModel.Age = model.Age;
    143                         newModel.Classes = model.Classes;
    144                     }
    145                     this.Query();
    146                 }
    147             }
    148         }
    149 
    150         /// <summary>
    151         /// 删除
    152         /// </summary>
    153         /// <param name="Id"></param>
    154         public void Delete(int Id)
    155         {
    156             var model = localDb.QueryById(Id);
    157             if (model != null)
    158             {
    159                 var r = MessageBox.Show($"确定要删除吗【{model.Name}】?","提示",MessageBoxButton.YesNo);
    160                 if (r == MessageBoxResult.Yes)
    161                 {
    162                     localDb.DelStudent(Id);
    163                     this.Query();
    164                 }
    165             }
    166         }
    167 
    168         /// <summary>
    169         /// 新增
    170         /// </summary>
    171         public void Add()
    172         {
    173             Student model = new Student();
    174             StudentWindow view = new StudentWindow(model);
    175             var r = view.ShowDialog();
    176             if (r.Value)
    177             {
    178                 model.Id = GridModelList.Max(t => t.Id) + 1;
    179                 localDb.AddStudent(model);
    180                 this.Query();
    181             }
    182         }
    183     }
    184 }

    4. 数据上下文

    当各个层分别创建好后,那如何关联起来呢?答案就是DataContext【数据上下文】。

    查询页面上下文,如下所示:

     1 namespace WpfApp3
     2 {
     3     /// <summary>
     4     /// MainWindow.xaml 的交互逻辑
     5     /// </summary>
     6     public partial class MainWindow : Window
     7     {
     8         public MainWindow()
     9         {
    10             InitializeComponent();
    11             MainViewModel viewModel = new MainViewModel();
    12             viewModel.Query();
    13             this.DataContext = viewModel;
    14         }
    15     }
    16 }

    新增页面上下文,如下所示:

     1 namespace WpfApp3.Views
     2 {
     3     /// <summary>
     4     /// StudentWindow.xaml 的交互逻辑
     5     /// </summary>
     6     public partial class StudentWindow : Window
     7     {
     8         public StudentWindow(Student student)
     9         {
    10             InitializeComponent();
    11             this.DataContext = new
    12             {
    13                 Model = student
    14             };
    15         }
    16 
    17         private void btnSave_Click(object sender, RoutedEventArgs e)
    18         {
    19             this.DialogResult = true;
    20         }
    21 
    22         private void btnCancel_Click(object sender, RoutedEventArgs e)
    23         {
    24             this.DialogResult = false;
    25         }
    26     }
    27 }

    总结

    MVVM具有低耦合,可重用,可测试,独立开发的优点,核心要素就两个:

    • 属性发生变化时的通知,即可达到数据的实时更新。
    • 命令是实现用户与程序之间数据和算法的桥梁。

    备注

    本文作为MVVM的简单入门示例,旨在抛砖引玉,一起学习,共同进步。如果对WPF的其他入门知识,不是很了解,可以参考其他博文。

    玉楼春·别后不知君远近

    欧阳修 〔宋代〕

    别后不知君远近,触目凄凉多少闷。渐行渐远渐无书,水阔鱼沉何处问。
    夜深风竹敲秋韵,万叶千声皆是恨。故攲单枕梦中寻,梦又不成灯又烬。注:攲(yǐ)


    作者:小六公子
    出处:http://www.cnblogs.com/hsiang/
    本文版权归作者和博客园共有,写文不易,支持原创,欢迎转载【点赞】,转载请保留此段声明,且在文章页面明显位置给出原文连接,谢谢。
    关注个人公众号,定时同步更新技术及职场文章

  • 相关阅读:
    Codeforces 543E. Listening to Music
    UOJ #138. 【UER #3】开学前的涂鸦
    bzoj 3569: DZY Loves Chinese II
    bzoj 2428: [HAOI2006]均分数据
    bzoj 4589: Hard Nim
    UOJ #119. 【UR #8】决战圆锥曲线
    spoj5973
    codeforces555E
    poj1275
    bzoj4152
  • 原文地址:https://www.cnblogs.com/hsiang/p/15579839.html
Copyright © 2011-2022 走看看