C#WPF之快速理解MVVM模式
MVVM是一种设计模式,特别适用于WPF等XAML-based的应用程序开发。MVVM模式主要包含三个部分:Model(模型)、View(视图)和ViewModel(视图模型)。
- Model(模型):模型代表的是业务逻辑和数据。它包含了应用程序中用于处理的核心数据对象。模型通常包含业务规则、数据访问和存储逻辑。
- View(视图):视图是用户看到和与之交互的界面。在WPF中,视图通常由XAML定义,并且包含各种用户界面元素,如按钮、文本框、列表等。
- ViewModel(视图模型):视图模型是视图的抽象,它包含视图所需的所有数据和命令。视图模型通过实现
INotifyPropertyChanged
接口和使用ICommand
对象,将视图的状态和行为抽象化,从而实现了视图和模型的解耦。
MVVM模式的主要优点是分离了视图和模型,使得视图和业务逻辑之间的依赖性降低,提高了代码的可维护性和可测试性。此外,通过数据绑定和命令绑定,MVVM模式可以减少大量的样板代码,使得代码更加简洁和易于理解。
不使用MVVM的例子
在Winform中我们使用了事件驱动编程,同样在WPF中我们也可以使用事件驱动编程。
事件驱动编程是一种编程范式,程序的执行由外部事件决定。当一个事件发生时,会触发与之关联的事件处理器(EVent Handler)。事件处理器是一个函数或方法,用于响应特定事件。
流程图如下:
这里通过WPF实现一个事件驱动编程的例子:
首先是UI界面的xaml代码
<Window x:Class="demo_11_2.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:demo_11_2"mc:Ignorable="d"Title="MainWindow" Height="450" Width="800"Loaded="Window_Loaded"><StackPanel><ToolBar><Label Content="姓名:"></Label><TextBox x:Name="nameTextBox" Width="50"></TextBox><Label Content="邮箱:"></Label><TextBox x:Name="emailTextBox" Width="100"></TextBox><Button Content="添加"Click="AddUser"></Button></ToolBar><StackPanel><DataGrid x:Name="dataGrid1"></DataGrid></StackPanel></StackPanel>
</Window>
UI界面如是
这里使用了两个事件,窗体加载时间和按钮点击事件:
Loaded="Window_Loaded"><Button Content="添加"Click="AddUser"></Button>
添加两个类:用户类和用户管理类
public class User{public string? Name { get; set; }public string? Email { get; set; }}
public class UserManager{public static ObservableCollection<User> DataBaseUsers = new ObservableCollection<User>(){new User() { Name = "tom", Email = "123@qq.com" },new User() { Name = "jerry", Email = "456@qq.com" },new User() { Name = "speicher", Email = "789@qq.com" }};public static ObservableCollection<User> GetUsers(){return DataBaseUsers;}public static void AddUser(User user){DataBaseUsers.Add(user);}}
窗体加载事件处理程序:
private void Window_Loaded(object sender, RoutedEventArgs e){dataGrid1.ItemsSource = UserManager.GetUsers();}
按钮点击事件处理程序:
private void AddUser(object sender, RoutedEventArgs e)
{User user = new User();user.Name = nameTextBox.Text;user.Email = emailTextBox.Text;UserManager.AddUser(user);MessageBox.Show("成功添加用户!");
}
如此便可成功添加
使用MVVM的例子
上面使用的是事件驱动编程,我们在winform开发中经常这样干。对于一些小项目这样做很方便,但是如果业务逻辑很多,这样做就难以维护,因为UI与业务逻辑严重耦合。
使用MVVM,首先新建一个Commands文件夹,新建一个RelayComand类
public class RelayCommand:ICommand{public event EventHandler? CanExecuteChanged;private Action<object> _Excute { get; set; }private Predicate<object> _CanExcute { get; set; }public RelayCommand(Action<object> ExcuteMethod, Predicate<object> CanExcuteMeth){_Excute = ExcuteMethod;_CanExcute = CanExcuteMeth;}public bool CanExecute(object? parameter){return _CanExcute(parameter);}public void Execute(object? parameter){_Excute(parameter);}}
RelayCommand实现了ICommand接口。
先来介绍一下ICommand
接口:在WPF中,ICommand
是一个接口,它定义了一种机制,用于在用户界面(UI)中处理事件,这种机制与用户界面的具体行为进行了解耦。这是实现MVVM设计模式的关键部分。
ICommand
接口包含两个方法和一个事件:
Execute(object parameter)
:当调用此命令时,应执行的操作。CanExecute(object parameter)
:如果可以执行Execute
方法,则返回true
;否则返回false
。这可以用于启用或禁用控件,例如按钮。CanExecuteChanged
事件:当CanExecute
的返回值可能发生更改时,应引发此事件。
ICommand结构图如下:
ICommand反编译代码:
public interface ICommand{event EventHandler? CanExecuteChanged;bool CanExecute(object? parameter);void Execute(object? parameter);}
继续说RelayCommand
RelayCommand
是一种常用于WPF和MVVM模式的设计模式,它是一种特殊的命令类型。在MVVM模式中,RelayCommand
允许将命令的处理逻辑从视图模型中分离出来,使得视图模型不需要知道命令的具体执行逻辑,从而实现了视图模型和命令处理逻辑的解耦。
RelayCommand
通常包含两个主要部分:CanExecute
和Execute
。CanExecute
是一个返回布尔值的函数,用于确定命令是否可以执行。Execute
是一个执行命令的函数,当CanExecute
返回true
时,Execute
将被调用。
这种设计模式使得你可以在不改变视图模型的情况下,更改命令的处理逻辑,提高了代码的可维护性和可重用性。
简单来说就是RelayCommand
是ICommand
接口的一个常见实现,它允许你将Execute
和CanExecute
的逻辑定义为委托,从而实现对命令的灵活处理。
RelayCommand类中定义两个委托:
private Action<object> _Excute { get; set; }private Predicate<object> _CanExcute { get; set; }
Action<object>
是一个委托,它封装了一个接受单个参数并且没有返回值的方法。这个参数的类型是object
。
对应于这一部分:
public void Execute(object? parameter){_Excute(parameter);}
Predicate<object>
是一个委托,它封装了一个接受单个参数并返回一个bool
值的方法。这个参数的类型是object
。
public bool CanExecute(object? parameter)
{return _CanExcute(parameter);
}
其RelayCommand构造函数:
public RelayCommand(Action<object> ExcuteMethod, Predicate<object> CanExcuteMeth){_Excute = ExcuteMethod;_CanExcute = CanExcuteMeth;}
然后说一下ViewModel
ViewModel是一个抽象,它代表了View的状态和行为。ViewModel包含了View所需的数据,并提供了命令以响应View上的用户操作。ViewModel不知道View的具体实现,它只知道如何提供View所需的状态和行为。
ViewModel的主要职责包括:
- 数据绑定:ViewModel提供了View所需的数据。这些数据通常是以属性的形式提供的,当这些属性的值改变时,ViewModel会通过实现
INotifyPropertyChanged
接口来通知View。 - 命令绑定:ViewModel提供了命令以响应View上的用户操作。这些命令通常是以
ICommand
接口的实现的形式提供的。 - 视图逻辑:ViewModel包含了View的逻辑,例如,决定何时显示或隐藏某个元素,何时启用或禁用某个按钮等。
新建一个ViewModel文件夹,在该文件夹中新建一个MainViewModel类:
public class MainViewModel
{public ObservableCollection<User> Users { get; set; }public ICommand AddUserCommand { get; set; }public string? Name { get; set; }public string? Email { get; set; }public MainViewModel(){Users = UserManager.GetUsers();AddUserCommand = new RelayCommand(AddUser, CanAddUser);}private bool CanAddUser(object obj){return true;}private void AddUser(object obj){User user = new User();user.Name = Name;user.Email = Email;UserManager.AddUser(user);}
}
View与ViewModel之间的关系:
VVM关系图
首先最重要的就是数据绑定,现在View的xaml如下:
<Windowx:Class="demo11_1.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:local="clr-namespace:demo11_1"xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"Title="MainWindow"Width="800"Height="450"mc:Ignorable="d"><StackPanel><ToolBar><Label Content="name:" /><TextBox Width="60" Text="{Binding Name}" /><Label Content="email:" /><TextBox Width="120" Text="{Binding Email}" /><ButtonWidth="150"Command="{Binding AddUserCommand}"Content="Add" /></ToolBar><StackPanel><DataGrid ItemsSource="{Binding Users}" /></StackPanel></StackPanel>
</Window>
cs如下:
public partial class MainWindow : Window{public MainWindow(){InitializeComponent();MainViewModel mainViewModel = new MainViewModel();this.DataContext = mainViewModel;}}
下图这几处为数据绑定,对应VVM关系图的DataBindings流程。
下图为命令绑定,对应于关系图的Commands,
如此便实现了与事件驱动一样的添加效果。
但是VVM关系图中的Send Notifications还没有体现,Send Notifications表示ViewModel中的更改会通知View。
现在我们来以一个例子说明一下Send Notifications是如何实现的。
在MainViewModel中添加一个测试命令: public ICommand AddUserCommand { get; set; }
构造函数添加如下: TestCommand = new Commands.RelayCommand(Test, CanTest);
需要的方法如下:
private bool CanTest(object obj){return true;}private void Test(object obj){Name = "demo";Email = "1130@qq.com";}
前台xaml代码加入: <Button Content="测试" Command="{Binding TestCommand }"></Button>
现在去deubg,我们会发现没有成功,原因是我们的ViewModel没有实现INotifyPropertyChanged
接口。
在WPF中,INotifyPropertyChanged
接口用于实现数据绑定中的属性更改通知。当绑定到UI元素的数据源中的属性值发生更改时,INotifyPropertyChanged
接口可以通知UI元素更新。
INotifyPropertyChanged
接口只定义了一个事件:PropertyChanged
。当属性值发生更改时,应触发此事件。事件参数PropertyChangedEventArgs
包含更改的属性的名称。
现在我们的MainViewModel实现一下INotifyPropertyChanged接口,如下所示:
public class MainViewModel : INotifyPropertyChanged{public ObservableCollection<User> Users { get; set; }public ICommand AddUserCommand { get; set; }public ICommand TestCommand { get; set; }private string? _name;public string? Name{get { return _name; }set{if (_name != value){_name = value;OnPropertyChanged(nameof(Name));}}}private string? _email;public string? Email{get { return _email; }set{if (_email != value){_email = value;OnPropertyChanged(nameof(Email));}}}public MainViewModel(){Users = UserManager.GetUsers();AddUserCommand = new RelayCommand(AddUser, CanAddUser);TestCommand = new RelayCommand(Test, CanTest);}private bool CanTest(object obj){return true;}private void Test(object obj){Name = "demo";Email = "1130@qq.com";}private bool CanAddUser(object obj){return true;}private void AddUser(object obj){User user = new User();user.Name = Name;user.Email = Email;UserManager.AddUser(user);}public event PropertyChangedEventHandler? PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}
如此,便可通知前台界面,消息框成功获取MainViewModel要发送的信息。
此过程对应VVM图中的SendNotifications。
现在来说ViewModel—Model。
VMM关系图
Model(模型):Model代表了业务逻辑和数据。它包含了应用程序中的数据和对数据的操作,例如,从数据库中获取数据,或者向数据库中添加数据。Model是独立于UI的,它不知道UI的存在。
ViewModel(视图模型):ViewModel是Model和View之间的桥梁。它包含了View所需的数据(这些数据来自于Model),并提供了命令以响应View上的用户操作。ViewModel将Model的数据转换为View可以显示的数据,同时,它也将View上的用户操作转换为对Model的操作。
这个例子中我们的数据来源于Model文件夹下的User类与UserManager类,这里的send notifications 如何解释?
首先我们修改MainViewModel类的Test方法:
private void Test(object obj){Users[0].Name = "demo";Users[1].Email = "1130@qq.com";//Name = "demo";//Email = "1130@qq.com";}
发现现在并不会发送通知,实现View上的修改,这是因为User类并没有实现INotifyPropertyChanged接口,现在修改User类实现INotifyPropertyChanged接口:
public class User : INotifyPropertyChanged
{private string? _name;public string? Name{get { return _name; }set{if (_name != value){_name = value;OnPropertyChanged(nameof(Name));}}}private string? _email;public string? Email{get { return _email; }set{if (_email != value){_email = value;OnPropertyChanged(nameof(Email));}}}public event PropertyChangedEventHandler? PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}
如此便实现了通知,成功将表格中的小王修改为emo。
使用MVVM相关库请看这篇文章,使用MVVM相关库了解MVVM模式
相关文章:

C#WPF之快速理解MVVM模式
MVVM是一种设计模式,特别适用于WPF等XAML-based的应用程序开发。MVVM模式主要包含三个部分:Model(模型)、View(视图)和ViewModel(视图模型)。 Model(模型)&a…...
微积分[1]|微积分的底层逻辑——解析几何、不等式与极限(含博主推荐的数理阅读教材共计21本书籍)
原创首发于CSDN,禁止转载,谢谢! 文章目录 微积分的底层逻辑探究一篇网络文章《数学分析的核心——不等式》高中数学与大学数学的脱节|脱节的实质含义|高中与大学的衔接数理书籍推荐 我个人所认为的数学分析的根基更新时…...

1-磁盘建立空闲分区
学习目标: 掌握磁盘分区的基本知识和操作技能,能够独立创建和管理磁盘空闲分区,以优化存储空间和提高系统性能,为后续的系统安装和数据管理打下基础。 学习内容: 1 选择一个适合的磁盘分区软件。推荐DiskGenius、Par…...

使用SearXNG-搭建个人搜索引擎(附国内可用Docker镜像源)
介绍 SearXNG是聚合了七十多种搜索服务的开源搜索工具。我们可以匿名浏览页面,不会被记录和追踪。作为开发者,SearXNG也提供了清晰的API接口以及完整的开发文档。 部署 我们可以很方便地使用Docker和Docker compose部署SearXNG。下面给出Docker部署Se…...

InnoDB 存储引擎<五>undo log, redo log,以及双写缓冲区
目录 撤销⽇志 - Undo Log 双写缓冲区 - Doublewrite Buffer 重做⽇志 - Redo Log 本篇是继承自上篇InnoDB存储引擎的磁盘文件 上篇链接:InnoDB 存储引擎<四>磁盘文件一 撤销⽇志 - Undo Log 1.什么是撤销⽇志? 解答问题&a…...

Find My运动耳机|苹果Find My技术与耳机结合,智能防丢,全球定位
运动耳机是为运动时候佩带的耳机,而是一种区别于一般耳机的能稳定固定在佩戴部位的耳机,该种耳机不会因为身体运动而使耳机从耳朵里掉落,普遍带有防滴溅、轻便等特性,透气性能较好,属于开放式耳机。 在智能化加持下&…...

书生大模型实战营Linux+InternStudio 关卡任务
一、端口映射 使用以下命令进行端口映射 ssh -p {YOUR_PORT} rootssh.intern-ai.org.cn -CNg -L 7860:127.0.0.1:7860 -o StrictHostKeyCheckingno 命令解释: -p 37367:是指定 SSH 连接的端口为 37367。rootssh.intern-ai.org.cn:表示要以…...

研究实锤:别让大模型「想」太多,OpenAI o1准确率竟下降36.3%
思维链(CoT)已被证明可以在许多任务(如多步骤推理)上显著提升大模型的性能。然而,在哪些情况下,CoT 会系统性地降低大模型的性能,这仍然是一个有待进一步讨论的问题。 如今,来自普林…...
C++游戏开发
C游戏开发概述 C 是游戏开发中的主要编程语言之一,因其性能、控制和广泛的生态系统而受到开发者的青睐。随着游戏行业的迅速发展,C 被用来构建许多成功的游戏和游戏引擎。本文将深入探讨 C 在游戏开发中的应用,包括基础概念、技术栈、示例代…...
ChatGPT中的RAG;大模型微调;通过正确的提问和回答数据进行问答系统的微调;
目录 ChatGPT中的RAG 1.检索器: 2.生成器: 3.结合使用: 大模型微调 通过正确的提问和回答数据进行问答系统的微调 ChatGPT中的RAG 在ChatGPT中,RAG(Retrieval-Augmented Generation)是一种结合了检索与生成的技术,旨在提高模型的回答质量和准确性。 RAG模型通常由两个…...

6款IntelliJ IDEA插件,让Spring和Java开发如虎添翼
文章目录 1、SonarLint2、JRebel for IntelliJ3、SwaggerHub插件4、Lombok插件5、RestfulTool插件6、 Json2Pojo插件7、结论 对于任何Spring Boot开发者来说,两个首要的目标是最大限度地提高工作效率和确保高质量代码。IntelliJ IDEA 是目前最广泛使用的集成开发环境…...

源代码加密解决方案:文档加密与沙盒加密的比较分析
源代码加密是保护企业知识产权和市场竞争力的关键手段。在众多源代码加密技术中,文档加密类软件和沙盒加密类软件SDC是两种重要的解决方案。以下是对这两种技术的分析: 文档加密类软件: 这类软件主要采用APIHOOK应用层透明加密技术࿰…...

Spring Boot 与 Vue 共筑高校网上订餐卓越平台
作者介绍:✌️大厂全栈码农|毕设实战开发,专注于大学生项目实战开发、讲解和毕业答疑辅导。 🍅获取源码联系方式请查看文末🍅 推荐订阅精彩专栏 👇🏻 避免错过下次更新 Springboot项目精选实战案例 更多项目…...

【数据仓库】Hive 拉链表实践
背景 拉链表是一种数据模型,主要是针对数据仓库设计中表存储数据的方式而定义的;顾名思义,所谓拉链表,就是记录历史。记录一个事务从开始一直到当前状态的所有变化的信息。 拉链表可以避免按每一天存储所有记录造成的海量存储问题…...

【python_pandas_将列表按照某几列进行分组,再求和,按照原列表的字段顺序返回】
说明: 1、按照[“行描述”,”‘公司代码’, ‘科目代码’, ‘预算项目代码’] 进行分组。 2、对“贷方”列进行求和。 3、最后按照之前的表头顺序进行排序,返回结果列表。 #-*- coding:utf-8-*import pandas as pd def consolidate_salary_provisions(l…...
Vue的双向绑定
Vue的双向绑定特性介绍 在现代前端开发中,数据的管理和UI的更新是至关重要的。Vue.js作为一个渐进式JavaScript框架,提供了强大的双向数据绑定机制,极大地简化了这些操作。在本文中,我们将深入探讨Vue的双向绑定特性。 什么是双…...

谷歌浏览器安装 Vue.js devtools 插件
文章目录 1. 安装2. 使用3. 注意 1. 安装 ① 搜索极简插件:https://chrome.zzzmh.cn/index ② 搜索框输入 Vue,选择 Vue.js devtools ③ 从历史版本里面选择并下载,选择 6.4 版本的就行 ④ 打开浏览器,右上角三个点 → 扩展程序…...

LWIP通信协议UDP发送、接收源码解析
1.UDP发送函数比较简短,带操作系统和裸机一样。以下是udp_sendto源码解析; 2.LWIP源码UDP接收数据 2.1.UDP带操作系统接收数据,以下是源码解析; 2.2.UDP裸机接收数据,以下是源码解析...

Linux—进程学习-01
目录 Linux—进程学习—11.冯诺依曼体系结构2.操作系统2.1操作系统的概念2.2操作系统的目的2.3如何理解管理2.4计算机软硬件体系的理解2.5系统调用和库函数的概念 3.进程3.1进程是什么3.2管理进程3.2.1描述进程-PCB3.2.2组织进程3.2.3总结 3.3查看进程 4.与进程有关的系统调用 …...

FR动态数据源插件支持配置模板中某个数据集进行数据连接的切换
1 需求背景 该插件的需求来源于官方帮助文档: 动态数据源/数据库- FineReport帮助文档 - 全面的报表使用教程和学习资料 官方的方案的缺点是会暴露数据库IP,端口密码等,不安全。...

linux之kylin系统nginx的安装
一、nginx的作用 1.可做高性能的web服务器 直接处理静态资源(HTML/CSS/图片等),响应速度远超传统服务器类似apache支持高并发连接 2.反向代理服务器 隐藏后端服务器IP地址,提高安全性 3.负载均衡服务器 支持多种策略分发流量…...

调用支付宝接口响应40004 SYSTEM_ERROR问题排查
在对接支付宝API的时候,遇到了一些问题,记录一下排查过程。 Body:{"datadigital_fincloud_generalsaas_face_certify_initialize_response":{"msg":"Business Failed","code":"40004","sub_msg…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
c# 局部函数 定义、功能与示例
C# 局部函数:定义、功能与示例 1. 定义与功能 局部函数(Local Function)是嵌套在另一个方法内部的私有方法,仅在包含它的方法内可见。 • 作用:封装仅用于当前方法的逻辑,避免污染类作用域,提升…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...

[拓扑优化] 1.概述
常见的拓扑优化方法有:均匀化法、变密度法、渐进结构优化法、水平集法、移动可变形组件法等。 常见的数值计算方法有:有限元法、有限差分法、边界元法、离散元法、无网格法、扩展有限元法、等几何分析等。 将上述数值计算方法与拓扑优化方法结合&#…...
深入解析 ReentrantLock:原理、公平锁与非公平锁的较量
ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个重要类,用于实现线程同步,支持可重入性,并且可以选择公平锁或非公平锁的实现方式。下面将详细介绍 ReentrantLock 的实现原理以及公平锁和非公平锁的区别。 ReentrantLock 实现原理 基本架构 ReentrantLo…...
C#最佳实践:为何优先使用as或is而非强制转换
C#最佳实践:为何优先使用as或is而非强制转换 在 C# 的编程世界里,类型转换是我们经常会遇到的操作。就像在现实生活中,我们可能需要把不同形状的物品重新整理归类一样,在代码里,我们也常常需要将一个数据类型转换为另…...