學習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層,主要是命令的綁定,及數據的綁定。
- 在DataGridTextColumn中通過Binding="{Binding Id}"的形式綁定要展示的列屬性名。
- 在Button按鈕上通過Command="{Binding AddCommand}"的形式綁定要響應的命令。
- 在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ǐ)