深入理解WPF中MVVM的设计思想

近些年来,随着WPF在生产,制造,工业控制等领域应用越来越广发,很多企业对WPF开发的需求也逐渐增多,使得很多人看到潜在机会,不断从Web,WinForm开发转向了WPF开发,但是WPF开发也有很多新的概念及设计思想,如:数据驱动,数据绑定,依赖属性,命令,控件模板,数据模板,MVVM等,与传统WinForm,ASP.NET WebForm开发,有很大的差异,今天就以一个简单的小例子,简述WPF开发中MVVM设计思想及应用。

为什么要用MVVM?

传统的WinForm开发,一般采用事件驱动,即用户点击事件,触发对应的事件,并在事件中通过唯一标识符获取页面上用户输入的数据,然后进行业务逻辑处理。这样做会有一个弊端,就是用户输入(User Interface)和业务逻辑(Business)是紧密耦合在一起的,无法做到分离,随着项目的业务不断复杂化,这种高度耦合的弊端将会越来越明显。并且会出现分工不明确(如:后端工程师,前端UI),工作无法拆分的现象。所以分层(如:MVC,MVVM),可测试(Unit Test),前后端分离,就成为必须要面对的问题。而今天要讲解的MVVM设计模式,就非常好的解决了我们所面临的问题。

什么是MVVM?

MVVM即模型(Model)-视图(View)-视图模型(ViewModel) ,是用于解耦 UI 代码和非 UI 代码的 设计模式。 借助 MVVM,可以在 XAML 中以声明方式定义 UI,将 UI使用数据绑定标到包含数据和命令的其他层。 数据绑定提供数据和结构的松散耦合,使 UI 和链接的数据保持同步,同时可以将用户输入路由到相应的命令。具体如下图所示:

如上图所示:

  1. View(用户页面),主要用于向使用者展示信息,并接收用户输入的信息(数据绑定),及响应用户的操作(Command)。
  2. ViewModel(用户视图业务逻辑),主要处理客户请求,以及数据呈现。
  3. Model数据模型,作为存储数据的载体,是一个个的具体的模型类,通过ViewModel进行调用。但是在小型项目中,Model并不是必须的
  4. IService(数据接口),数据访问服务,用于获取各种类型数据的服务。数据的形式有很多种,如网络数据,本地数据,数据库数据,但是在ViewModel调用时,都统一封装成了Service。在小型项目中,IService数据接口也并不是必须的,不属于MVVM的范畴
  5. 在上图中,DataBase,Network,Local等表示不同的数据源形式,并不属于MVVM的范畴

前提条件

要实现MVVM,首先需要满足两个条件:

  1. 属性变更通知,在MVVM思想中,由WinForm的事件驱动,转变成了数据驱动。在C#中,普通的属性,并不具备变更通知功能,要实现变更通知功能,必须要实现INotifyPropertyChanged接口。
  2. 绑定命令,在WPF中,为了解决事件响应功能之间的耦合,提出了绑定命令思想,即命令可以绑定的方式与控件建立联系。绑定命令必须实现ICommand接口。

在上述两个条件都满足后,如何将ViewModel中的具备变更通知的属性和命令,与View中的控件关联起来呢?答案就是绑定(Binding)

当View层的数据控件和具备通知功能的属性进行Binding后,Binging就会自动侦听来自接口的PropertyChanged事件。进而达到数据驱动UI的效果,可谓【一桥飞架南北,天堑变通途】。

MVVM实例

为了进一步感受MVVM的设计思想,验证上述的理论知识,以实例进行说明。本实例的项目架构如下所示:

MVVM核心代码

1. 具备通知功能的属性

首先定义一个抽象类ObservableObject,此接口实现INotifyPropertyChanged接口,如下所示:

using System.ComponentModel;
using System.Runtime.CompilerServices;namespace DemoMVVM.Core
{/// <summary>/// 可被观测的类/// </summary>public abstract class ObservableObject : INotifyPropertyChanged{/// <summary>/// 属性改变事件/// </summary>public event PropertyChangedEventHandler? PropertyChanged;/// <summary>/// 属性改变触发方法/// </summary>/// <param name="propertyName">属性名称</param>protected void RaisePropertyChanged([CallerMemberName]string propertyName=null){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}/// <summary>/// 设置属性值,如果发生改变,则调用通知方法/// </summary>/// <typeparam name="T"></typeparam>/// <param name="target"></param>/// <param name="value"></param>/// <param name="propertyName"></param>/// <returns></returns>protected bool SetProperty<T>(ref T target,T value, [CallerMemberName] string propertyName = null){if (EqualityComparer<T>.Default.Equals(target, value)){return false;}else{target=value;RaisePropertyChanged(propertyName);return true;}}}
}

 

注意:上述SetProperty主要用于将普通属性,变为具备通知功能的属性。

然后定义一个ViewMode基类,继承自ObservableObject,以备后续扩展,如下所示:

namespace DemoMVVM.Core
{/// <summary>/// ViewModel基类,继承自ObservableObject/// </summary>public abstract class ViewModelBase:ObservableObject{}
}

 

2. 具备绑定功能的命令

首先定义一个DelegateCommand,实现ICommand接口,如下所示:

namespace DemoMVVM.Core
{public class DelegateCommand : ICommand{private Action<object> execute;private Predicate<object> canExecute;public event EventHandler? CanExecuteChanged;public DelegateCommand(Action<object> execute, Predicate<object> canExecute){if (execute == null){throw new ArgumentNullException("execute 不能为空");}this.execute = execute;this.canExecute = canExecute;}public DelegateCommand(Action<object> execute):this(execute,null){}public bool CanExecute(object? parameter){return  canExecute?.Invoke(parameter)!=false;}public void Execute(object? parameter){execute?.Invoke(parameter);}}
}

 

注意,DelegateCommand的构造函数,接收两个参数,一个是Execute(干活的),一个是CanExecute(判断是否可以干活的)

MVVM应用代码

本实例主要实现两个数的运算。如加,减,乘,除等功能。

首先定义ViewModel,继承自ViewModelBase,主要实现具备通知功能的属性和命令,如下所示:

using DemoMVVM.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime;
using System.Text;
using System.Threading.Tasks;namespace DemoMVVM
{public class MainWindowViewModel:ViewModelBase{#region 属性及构造函数private double leftNumber;public double LeftNumber{get { return leftNumber; }set { SetProperty(ref leftNumber , value); }}private double rightNumber;public double RightNumber{get { return rightNumber; }set { SetProperty(ref rightNumber , value); }}private double resultNumber;public double ResultNumber{get { return resultNumber; }set { SetProperty(ref resultNumber , value); }}public MainWindowViewModel(){}#endregion#region 命令private DelegateCommand operationCommand;public DelegateCommand OperationCommand{get {if (operationCommand == null){operationCommand = new DelegateCommand(Operate);}return operationCommand; }}private void Operate(object obj){if(obj == null){return;}var type=obj.ToString();switch (type){case "+":this.ResultNumber = this.LeftNumber + this.RightNumber;break;case "-":this.ResultNumber = this.LeftNumber - this.RightNumber;break;case "*":this.ResultNumber = this.LeftNumber * this.RightNumber;break;case "/":if (this.RightNumber == 0){this.ResultNumber = 0;}else{this.ResultNumber = this.LeftNumber / this.RightNumber;}break;}}#endregion}
}

 

 创建视图,并在视图中进行数据绑定,将ViewModel和UI关联起来,如下所示:

<Window x:Class="DemoMVVM.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:DemoMVVM"mc:Ignorable="d"Title="MVVM示例" Height="350" Width="600"><Grid><Grid.ColumnDefinitions><ColumnDefinition></ColumnDefinition><ColumnDefinition></ColumnDefinition><ColumnDefinition Width="0.3*"></ColumnDefinition><ColumnDefinition></ColumnDefinition></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition></RowDefinition><RowDefinition></RowDefinition><RowDefinition></RowDefinition><RowDefinition></RowDefinition></Grid.RowDefinitions><StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal"><TextBlock Text="A1:" VerticalAlignment="Center" ></TextBlock><TextBox  Margin="10" Width="120" Height="35" Text="{Binding LeftNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox></StackPanel><StackPanel Grid.Row="1" Grid.Column="1" Orientation="Horizontal"><TextBlock Text="A2:" VerticalAlignment="Center" ></TextBlock><TextBox  Margin="10" Width="120" Height="35" Text="{Binding RightNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox></StackPanel><TextBlock Grid.Row="1" Grid.Column="2" Text="=" VerticalAlignment="Center" HorizontalAlignment="Center"></TextBlock><StackPanel Grid.Row="1" Grid.Column="3" Orientation="Horizontal"><TextBlock Text="A3:" VerticalAlignment="Center" ></TextBlock><TextBox  Margin="10" Width="120" Height="35" Text="{Binding ResultNumber, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center"></TextBox></StackPanel><StackPanel Grid.Row="2" Grid.ColumnSpan="4" Orientation="Horizontal" HorizontalAlignment="Center"><Button Content="+" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="+"></Button><Button Content="-" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="-"></Button><Button Content="*" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="*"></Button><Button Content="/" Width="100" Height="35" Margin="10" Command="{Binding OperationCommand}" CommandParameter="/"></Button></StackPanel>                                               </Grid>
</Window>

 

注意,在xaml前端UI代码中,分别对TextBox的Text和Button的Command进行了绑定,已达到数据驱动UI,以及UI响应客户的功能

在UI的构造函数中,将DataContext数据上下文和ViewModel进行关联,如下所示:

namespace DemoMVVM
{/// <summary>/// Interaction logic for MainWindow.xaml/// </summary>public partial class MainWindow : Window{private MainWindowViewModel viewModel;public MainWindow(){InitializeComponent();viewModel = new MainWindowViewModel();this.DataContext = viewModel;}}
}

 

MVVM实例演示

通过以上步骤,已经完成了MVVM的简单应用。实例演示如下:

以上就是深入理解WPF中MVVM的设计思想的全部内容。希望可以抛砖引玉,一起学习,共同进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/84518.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图像处理软件Photoshop 2024 mac新增功能

Photoshop 2024 mac是一款图像处理软件的最新版本。ps2024提供了丰富的功能和工具&#xff0c;使用户能够对照片、插图、图形等进行精确的编辑和设计。 Photoshop 2024 mac软件特点 快速性能&#xff1a;Photoshop 2024 提供了更快的渲染速度和更高效的处理能力&#xff0c;让用…

MyBatis 缓存模块

文章目录 前言缓存的实现Cache接口PerpetualCache 缓存的应用缓存对应的初始化一级缓存二级缓存第三方缓存 前言 MyBatis作为一个强大的持久层框架&#xff0c;缓存是其必不可少的功能之一&#xff0c;Mybatis中的缓存分为一级缓存和二级缓存。但本质上是一样的&#xff0c;都…

【异常错误】detected dubious ownership in repository ****** is owned by: ‘

今天在github git的时候&#xff0c;突然出现了这种问题&#xff0c;下面的框出的部分一直显示&#xff1a; detected dubious ownership in repository at D:/Pycharm_workspace/SBDD/1/FLAG D:/Pycharm_workspace/SBDD/1/FLAG is owned by: S-1-5-32-544 but the current use…

多线程的学习第二篇

多线程 线程是为了解决并发编程引入的机制. 线程相比于进程来说,更轻量 ~~ 更轻量的体现: 创建线程比创建进程,开销更小销毁线程比销毁进程,开销更小调度线程比调度进程,开销更小 进程是包含线程的. 同一个进程里的若干线程之间,共享着内存资源和文件描述符表 每个线程被独…

外国固定资产管理系统功能有哪些

很多公司都在寻找提高自己资产管理效益的方法。为了满足这一要求&#xff0c;国外的固定资产管理系统已经发展成多种形式。以下是国外一些常见的固定资产管理系统的特点:自动化和智能化:许多现代固定资产管理系统采用自动化和数字化技术&#xff0c;以简化流程&#xff0c;减少…

网络编程day04(网络属性函数、广播、组播、TCP并发)

今日任务 对于newfd的话&#xff0c;最好是另存然后传入给分支线程&#xff0c;避免父子线程操作同一个文件描述符 1.广播&#xff1a; 接收端 代码&#xff1a; #include <stdio.h> #include <string.h> #include <stdlib.h> #include <sys/types.h&…

Databend 开源周报第 111 期

Databend 是一款现代云数仓。专为弹性和高效设计&#xff0c;为您的大规模分析需求保驾护航。自由且开源。即刻体验云服务&#xff1a;https://app.databend.cn 。 Whats On In Databend 探索 Databend 本周新进展&#xff0c;遇到更贴近你心意的 Databend 。 理解 SHARE END…

大数据(九):数据可视化(一)

专栏介绍 结合自身经验和内部资料总结的Python教程&#xff0c;每天3-5章&#xff0c;最短1个月就能全方位的完成Python的学习并进行实战开发&#xff0c;学完了定能成为大佬&#xff01;加油吧&#xff01;卷起来&#xff01; 全部文章请访问专栏&#xff1a;《Python全栈教…

Linux_9_网络协议和管理

目录 1网络基础1.1 网络概念1.2常见的网络物理组件1.3网络应用程序1.3.1各种网络应用1.3.2应用程序对网络的要求 1.4网络的特征1.4.1速度(带宽)1.4.2网络拓扑 1.5网络1.5.1网络准和分层1.5.2开放系统互联OSI1.5.3网络的通信过程1.5.3.1数据封装和数据解封1.5.3.2协议数据单元PD…

基于javaweb的顶岗实习管理系统(jsp+servlet)

系统简介 本项目采用eclipse工具开发&#xff0c;jspservletjquery技术编写&#xff0c;数据库采用的是mysql&#xff0c;navicat开发工具。 三个角色&#xff1a;管理员&#xff0c;教师&#xff0c;学生 模块简介 管理员&#xff1a; 1、登录 2、学生管理 3、公告管理 …

如何通过一键导出导入数据实现批量重命名文件名称

在日常办公中&#xff0c;我们经常需要对大量的文件进行重命名&#xff0c;以便更好地管理和查找文件。而且&#xff0c;有时候我们还需要将文件名称翻译成其他语言&#xff0c;以适应不同的工作需求。如何高效地完成这项任务呢&#xff1f;接下来&#xff0c;我将介绍一种方法…

【李沐深度学习笔记】线性代数实现

课程地址和说明 线性代数实现p2 本系列文章是我学习李沐老师深度学习系列课程的学习笔记&#xff0c;可能会对李沐老师上课没讲到的进行补充。 这节就算之前内容的复习&#xff0c;后面以截图形式呈现 标量由只有一个元素的张量表示 import torch x torch.tensor([3.0]) y …

Spring Boot魔法:简化Java应用的开发与部署

文章目录 什么是Spring Boot&#xff1f;1. 自动配置&#xff08;Auto-Configuration&#xff09;2. 独立运行&#xff08;Standalone&#xff09;3. 生产就绪&#xff08;Production Ready&#xff09;4. 大量的起步依赖&#xff08;Starter Dependencies&#xff09; Spring …

免杀对抗-java语言-shellcode免杀-源码修改+打包exe

JAVA-ShellCode免杀-源码修改&打包EXE Shellcode-生成/上线 1.msf生成shellcode 命令&#xff1a;msfvenom -p java/meterpreter/reverse_tcp LHOSTx.x.x.x LPORTxxxx -f jar -o msf.jar 2.msf设置监听 3.执行msf生成的shellcode jar包&#xff0c;成功上线 命令&#xff1…

05_Bootstrap插件02

7 小标签 通过 .label 实现小标签&#xff0c;用于提示类。 <h1>h1标题 <span class"label label-default">标签</span></h1> <h2>h2标题<span class"label label-default">标签</span></h2> <h3&g…

Uni-app 调用微信地图导航功能【有图】

前言 我们在使用uni-app时&#xff0c;有时候会遇到需要开发地图和导航的功能&#xff0c;这些方法其实微信小程序的API已经帮我们封装好了 详见&#xff1a;微信小程序开发文档 接下来我们就演示如何用uni-app来使用他们 使用 <template><view><button type…

5G通信与蜂窝模组之间的关系

5G通信是第五代移动通信技术的简称&#xff0c;它代表了一种新一代的无线通信技术标准。5G通信的主要目标是提供更高的数据传输速度、更低的延迟、更大的网络容量以及更可靠的连接&#xff0c;以支持各种新兴应用和服务&#xff0c;包括高清视频流、虚拟现实、物联网&#xff0…

Mybatis学习笔记11 缓存相关

Mybatis学习笔记10 高级映射及延迟加载_biubiubiu0706的博客-CSDN博客 缓存:cache 缓存的作用:通过减少IO的方式,来提高程序的执行效率 Mybatis的缓存:将select语句的查询结果放到缓存(内存)当中,下一次还是这条select语句的话,直接从缓存中取,不再查数据库.一方面是减少了I…

【Vue3 源码讲解】nextTick

nextTick 是 Vue 3 中用于异步执行回调函数的函数&#xff0c;它会将回调函数延迟到下一个微任务队列中执行。其中&#xff0c;Vue 更新 DOM 是异步的。下面是对 nextTick 函数的详细解释&#xff1a; export function nextTick<T void, R void>(this: T,fn?: (this:…

websocket php教程

WebSocket 是 HTML5 提供的一种网络通讯协议&#xff0c;用于服务端与客户端实时数据传输。广泛用于浏览器与服务器的实时通讯&#xff0c;APP与服务器的实时通讯等场景。 相比传统HTTP协议请求响应式通讯&#xff0c;WebSocket协议可以做到实时的双向通讯&#xff0c;服务端可…