WPF学习笔记(25)MVVM框架与项目实例
MVVM框架与项目实例
- 一、MVVM框架
- 1. 概述
- 2. 核心组件与优势
- 一、MVVM项目
- 1.普通项目
- 2. MVVM架构
- 3. MVVM项目实例
- 1. 项目准备
- 2. LoginViewModel与Login
- 2. MainWindowViewModel
- 4. MVVM项目优化
- 1. BaseViewModel
- 2. RealyCommand
- 3. 效果展示
- 总结
一、MVVM框架
1. 概述
官方文档:https://learn.microsoft.com/zh-cn/dotnet/architecture/maui/mvvm
2. 核心组件与优势
一、MVVM项目
1.普通项目
2. MVVM架构
3. MVVM项目实例
1. 项目准备
以一个简单的图书管理系统为例,包含登录和图书删除两个功能。
Model创建Book、DAL、User,内容如下:
namespace Model
{//书籍类public class Book{public Book(string name, string isbn, string author){Name = name;Isbn = isbn;Author = author;}public string Name { get; set; }public string Isbn { get; set; }public string Author { get; set; }}
}
namespace Model
{//数据访问层,操作数据库public class DAL{//模拟查询数据库书籍表数据public ObservableCollection<Book> GetBookList(){ObservableCollection<Book> list = new ObservableCollection<Book>();list.Add(new Book("Java高级编程", "111", "李老师"));list.Add(new Book("C++高级编程", "222", "黄老师"));list.Add(new Book("C#高级编程", "333", "马老师"));return list;}//模拟查询数据库用户表数据public List<User> GetUserList(){List<User> list = new List<User>();list.Add(new User("admin", "123", "1111111"));list.Add(new User("张三", "123", "2222222"));return list;}//通过用户名查找用户public User FindUserByName(String name){List<User> users = GetUserList();foreach (User u in users){if (u.Name == name)return u;}return null;}}
namespace Model
{//用户类public class User{public User(string name, string pwd, string phone){Name = name;Pwd = pwd;Phone = phone;}public string Name { get; set; }public string Pwd { get; set; }public string Phone { get; set; }}
}
View创建Login,内容如下:
<Grid><Label x:Name="label" Content="用户名" HorizontalAlignment="Left" Height="30" Margin="245,115,0,0" VerticalAlignment="Top" Width="100"/><TextBox x:Name="textBoxName" Text="" HorizontalAlignment="Left" Height="30" Margin="360,115,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="195"/><Label x:Name="label2" Content="密码" HorizontalAlignment="Left" Height="30" Margin="245,175,0,0" VerticalAlignment="Top" Width="100" RenderTransformOrigin="0.402,2.66"/><TextBox x:Name="textBoxPwd" Text="" HorizontalAlignment="Left" Height="30" Margin="360,175,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="195"/><Button x:Name="buttonLogin" Content="登录" HorizontalAlignment="Left" Height="40" Margin="340,310,0,0" VerticalAlignment="Top" Width="140" /><Label x:Name="labelError" Content="" HorizontalAlignment="Left" Height="33" Margin="225,235,0,0" VerticalAlignment="Top" Width="390" Foreground="#FFF30303"/> </Grid>
2. LoginViewModel与Login
ViewModel创建LoginViewModel,为界面上的用户名和密码输入框设计属性,并继承InotifyPertyChanged接口,实现数据双向更新
接来对窗口使用DataContext进行上下文数据绑定。
将View绑定到ViewModel上
创建LoginCommand类,继承接口ICommand并实现。
其对LoginCommand类的Execute函数改造,实现命令处理逻辑,以及检查登录信息是否正确。
using Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;namespace ViewModel
{//自定义命令类class LoginCommand : ICommand{public event EventHandler CanExecuteChanged;private LoginViewModel vm;public LoginCommand(LoginViewModel vm){this.vm = vm;}public bool CanExecute(object parameter){return true;}public void Execute(object parameter){//Execute方法中实现命令处理逻辑string Name = vm.Name;string Pwd = vm.Pwd;if(string.IsNullOrEmpty(vm.Name) || string.IsNullOrEmpty(vm.Pwd)){vm.Error = "用户名密码不能为空!";return;}//检查登录信息User u = vm.dal.FindUserByName(vm.Name);if (u != null && u.Pwd == vm.Pwd){Window w = parameter as Window;w.DialogResult = true;//关闭窗口}elsevm.Error = "用户名密码错误!";}}internal class LoginViewModel : INotifyPropertyChanged{//属性对应,界面上用户名TextBox控件private string name = "admin";public string Name{get { return name; }set{name = value;OnPropertyChanged("Name");}}//属性对应,界面上密码TextBox控件private string pwd;public string Pwd{get { return pwd; }set{pwd = value;OnPropertyChanged("Pwd");}}//属性对应,界面上报错Label控件 private string error = "暂无信息";public string Error{get { return error; }set{error = value;OnPropertyChanged("Error");}}//命令属性,对应界面上的登录事件public LoginCommand LoginCommand { get; set; }//数据访问层public DAL dal = new DAL();public event PropertyChangedEventHandler PropertyChanged;//通知属性更改public void OnPropertyChanged(string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}//构造函数public LoginViewModel(){LoginCommand = new LoginCommand(this);}}
}
<Window x:Class="View.Login"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:View"mc:Ignorable="d"Title="Login" Height="450" Width="800" FontSize="18" Closing="Window_Closing"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="3*"/><ColumnDefinition Width="5*"/></Grid.ColumnDefinitions><Label x:Name="label" Content="用户名" HorizontalAlignment="Left" Height="30" Margin="245,115,0,0" VerticalAlignment="Top" Width="100" Grid.ColumnSpan="2"/><TextBox x:Name="textBoxName" Text="{Binding Name}" HorizontalAlignment="Left" Height="30" Margin="60,115,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="195" Grid.Column="1"/><Label x:Name="label2" Content="密码" HorizontalAlignment="Left" Height="30" Margin="245,175,0,0" VerticalAlignment="Top" Width="100" RenderTransformOrigin="0.402,2.66" Grid.ColumnSpan="2"/><TextBox x:Name="textBoxPwd" Text="{Binding Pwd}" HorizontalAlignment="Left" Height="30" Margin="60,175,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="195" Grid.Column="1"/><Button x:Name="buttonLogin" Content="登录" Command="{Binding LoginCommand}"CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window} } }"HorizontalAlignment="Left" Height="40" Margin="40,310,0,0" VerticalAlignment="Top" Width="140" Grid.Column="1" /><Label x:Name="labelError" Content="{Binding Error}" HorizontalAlignment="Left" Height="33" Margin="225,235,0,0" VerticalAlignment="Top" Width="390" Foreground="#FFF30303" Grid.ColumnSpan="2"/></Grid>
</Window>
2. MainWindowViewModel
在ViewModel创建图书管理的MainWindowViewModel,实现图书删除。
接来对窗口使用DataContext进行上下文数据绑定。
将View绑定到ViewModel上
创建DelCommand类,继承接口ICommand并实现。
其对DelCommand类的Execute函数改造,实现命令处理逻辑,删除书籍。
using Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;namespace ViewModel
{class DelCommand : ICommand{public event EventHandler CanExecuteChanged;private MainWindowViewModel vm;public DelCommand(MainWindowViewModel vm){this.vm = vm;}public bool CanExecute(object parameter){return true;}//传入的参数是命令的绑定的参数Book类的对象public void Execute(object parameter){Book b = parameter as Book;//删除执行的内容vm.Books.Remove(b);}}internal class MainWindowViewModel : INotifyPropertyChanged{//数据访问层public DAL dal = new DAL();public MainWindowViewModel(){ Books = dal.GetBookList() ;DelCommand = new DelCommand(this);}//对应界面上的 ListView显示的数据//ObservableCollection 可以通知界面更新的private ObservableCollection<Book> books; public ObservableCollection<Book> Books{get { return books; }set { books = value;OnPropertyChanged("Books");}}//删除命令 ,对应界面的删除按钮的点击操作public DelCommand DelCommand { get; set; }//通知属性已经更改public event PropertyChangedEventHandler PropertyChanged;public void OnPropertyChanged(string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
}
<Window x:Class="View.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.microsoft.com/expression/blend/2008"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"xmlns:local="clr-namespace:View"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800" FontSize="18" ><Grid><ListView x:Name="listView" ItemsSource="{Binding Books}"HorizontalAlignment="Center" Height="275" VerticalAlignment="Center" Width="556" d:ItemsSource="{d:SampleData ItemCount=5}"><ListView.View><GridView><GridViewColumn Header="书名" Width="200" DisplayMemberBinding="{Binding Name}" /><GridViewColumn Header="ISBN" Width="100" DisplayMemberBinding="{Binding Isbn}" /><GridViewColumn Header="作者" Width="Auto" DisplayMemberBinding="{Binding Author }" /><GridViewColumn Header="操作" Width="Auto" ><GridViewColumn.CellTemplate><DataTemplate><Button Width="60" Content="删除" Command="{Binding RelativeSource={RelativeSource FindAncestor,AncestorType={x:Type Window}},Path=DataContext.DelCommand}" CommandParameter="{Binding }" /></DataTemplate></GridViewColumn.CellTemplate> </GridViewColumn></GridView></ListView.View></ListView></Grid>
</Window>
4. MVVM项目优化
1. BaseViewModel
2. RealyCommand
3. 效果展示
修改LoginCommand
修改DelCommand
下面为优化后的实际代码
创建BaseViewModel
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace ViewModel
{internal class BaseViewMode : INotifyPropertyChanged{//通知属性已经更改public event PropertyChangedEventHandler PropertyChanged;public void OnPropertyChanged(string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
}
创建RelayCommand
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;namespace ViewModel
{internal class RelayCommand : ICommand{//命令执行的函数,参数为object,无返回值的private Action<object> executeAction;//判断命名能否执行的函数,参数为object,返回值为 boolprivate Func<object, bool> canExecuteFunc;public event EventHandler CanExecuteChanged;//构造函数传入参数,以后不用为每个命令单独写一个类了,每个命令都可以使用这个类//只不过执行的内容不同public RelayCommand(Action<object> execute, Func<object, bool> canExecute = null){executeAction = execute;canExecuteFunc = canExecute;}public bool CanExecute(object parameter){if (canExecuteFunc != null){ return canExecuteFunc(parameter);}return true;}public void Execute(object parameter) { if (executeAction != null){ executeAction(parameter);} }}
}
优化LoginViewModel
using Model;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;namespace ViewModel
{ internal class LoginViewModel : BaseViewMode{//构造函数public LoginViewModel(){LoginCommand = new RelayCommand(parameter =>{//Execute方法中实现命令处理逻辑if (string.IsNullOrEmpty(Name) || string.IsNullOrEmpty(Pwd)){Error = "用户名密码不能为空!";return;}//检查登录信息User u = dal.FindUserByName(Name);if (u != null && u.Pwd == Pwd){Window w = parameter as Window;w.DialogResult = true;//关闭窗口}elseError = "用户名密码错误!";});}//属性对应,界面上用户名TextBox控件private string name = "admin";public string Name{get { return name; }set{name = value;OnPropertyChanged("Name");}}//属性对应,界面上密码TextBox控件private string pwd;public string Pwd{get { return pwd; }set{pwd = value;OnPropertyChanged("Pwd");}}//属性对应,界面上报错Label控件 private string error = "暂无信息";public string Error{get { return error; }set{error = value;OnPropertyChanged("Error");}}//命令属性,对应界面上的登录事件public ICommand LoginCommand { get; set; }//数据访问层public DAL dal = new DAL();}
}
优化MainWindowViewModel
using Model;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;namespace ViewModel
{class DelCommand : ICommand{public event EventHandler CanExecuteChanged;private MainWindowViewModel vm;public DelCommand(MainWindowViewModel vm){this.vm = vm;}public bool CanExecute(object parameter){return true;}//传入的参数是命令的绑定的参数Book类的对象public void Execute(object parameter){Book b = parameter as Book;//删除执行的内容vm.Books.Remove(b);}}internal class MainWindowViewModel : INotifyPropertyChanged{//数据访问层public DAL dal = new DAL();public MainWindowViewModel(){ Books = dal.GetBookList() ;DelCommand = new DelCommand(this);}//对应界面上的 ListView显示的数据//ObservableCollection 可以通知界面更新的private ObservableCollection<Book> books; public ObservableCollection<Book> Books{get { return books; }set { books = value;OnPropertyChanged("Books");}}//删除命令 ,对应界面的删除按钮的点击操作public DelCommand DelCommand { get; set; }//通知属性已经更改public event PropertyChangedEventHandler PropertyChanged;public void OnPropertyChanged(string propertyName = null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
}
总结
- MVVM有效的帮助界面与实现的分离,易维护,可重用,易测试,可并行开发
- ViewModel继承InotifyPertyChanged接口,实现数据双向更新
- 继承接口ICommand实现自定义命令替代事件,对Execute函数改造,实现命令处理逻辑。
- ObservableCollection类表示一个动态数据集合,它实现了INotifyPropertyChanged 接囗的数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知。