WPF学习笔记(25)MVVM框架与项目实例

MVVM框架与项目实例

  • 一、MVVM框架
    • [1. 概述](#1. 概述)
    • [2. 核心组件与优势](#2. 核心组件与优势)
  • 一、MVVM项目
    • 1.普通项目
    • [2. MVVM架构](#2. MVVM架构)
    • [3. MVVM项目实例](#3. MVVM项目实例)
      • [1. 项目准备](#1. 项目准备)
      • [2. LoginViewModel与Login](#2. LoginViewModel与Login)
      • [2. MainWindowViewModel](#2. MainWindowViewModel)
    • [4. MVVM项目优化](#4. MVVM项目优化)
      • [1. BaseViewModel](#1. BaseViewModel)
      • [2. RealyCommand](#2. RealyCommand)
      • [3. 效果展示](#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,内容如下:

csharp 复制代码
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; }

    }
}
csharp 复制代码
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;
        }

    }
csharp 复制代码
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,内容如下:

xml 复制代码
    <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函数改造,实现命令处理逻辑,以及检查登录信息是否正确。

csharp 复制代码
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;//关闭窗口
            }
            else
                vm.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);
        }
    }
}
xml 复制代码
<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函数改造,实现命令处理逻辑,删除书籍。

csharp 复制代码
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));
        }
    }
}
xml 复制代码
<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

csharp 复制代码
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

csharp 复制代码
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,返回值为 bool
        private 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

csharp 复制代码
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;//关闭窗口
                }
                else
                    Error = "用户名密码错误!";
            });
        }

        //属性对应,界面上用户名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

csharp 复制代码
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 接囗的数据集合,在添加项、移除项或刷新整个列表时,此集合将提供通知。
相关推荐
qq_392397125 小时前
Redis常用操作
数据库·redis·wpf
厦门德仔15 小时前
【WPF】WPF(样式)
android·java·wpf
✎ ﹏梦醒͜ღ҉繁华落℘2 天前
WPF学习(四)
学习·wpf
zzyzxb2 天前
WPF中依赖属性和附加属性
wpf
✎ ﹏梦醒͜ღ҉繁华落℘2 天前
WPF学习(动画)
学习·wpf
weixin_447103582 天前
Wpf布局之Canvas面板!
wpf
葬歌倾城2 天前
waferMap图像渲染
c#·wpf
甄天2 天前
WPF路由事件:冒泡、隧道与直接全解析
c#·wpf·visual studio
三千道应用题2 天前
WPF学习笔记(12)下拉框控件ComboBox与数据模板
wpf