WPF MVVM系统入门-上

简介: 本文详细讲解WPF,MVVM开发,实现UI与逻辑的解耦。

WPF MVVM系统入门-上

本文详细讲解WPF,MVVM开发,实现UI与逻辑的解耦。

Models:存放数据的模型,实体对象

Views:可视化界面

ViewModels:业务逻辑。ViewModels与Models的联系会更紧密,而Views页面会主动绑定ViewModels中的数据,原则上ViewModels不要直接去操作Views,被动的被Views来获取数据即可。

一般遵循MVVM模式的项目下,都会有Models、Views、ViewModels三个文件夹来存放不同的代码工程。

案例入门

实现一个加法计算器,输入两个值,进行相加,并返回结果。

Model

一共需要三个double类型,两个输入,一个输出。

publicclassMainModel

{

   publicdoubleValue1 { get; set; }

   publicdoubleValue2 { get; set; }

   publicdoubleValue3 { get; set; }

}

ViewModel

编写相加的逻辑代码

publicclassMainViewModel

{

   //声明一个Model类型的属性

   publicMainModelmainModel { get; set; } =newMainModel();

   //业务逻辑

   publicvoidAdd()

   {

       mainModel.Value3=mainModel.Value2+mainModel.Value1;

   }

   publicMainViewModel()

   {

       //业务逻辑的调用

       //为了便于观察,延迟3s后调用方法

       Task.Factory.StartNew(() =>

       {

           Task.Delay(3000).Wait();

           Add();

       });

   }

}

View

view中,利用一个Textbox和一个slider作为输入分别绑定Value1和value2,使用另一个TextBox绑定value3

<Window.DataContext>

   <vm:MainViewModel/>

</Window.DataContext>

<Grid>

   <StackPanel>

       <TextBoxText="{Binding mainModel.Value1, UpdateSourceTrigger=PropertyChanged}"/>

       <Slider

           Maximum="100"

           Minimum="0"

           Value="{Binding mainModel.Value2}"/>

       <TextBoxText="{Binding mainModel.Value3}"/>

   </StackPanel>

</Grid>

这样等待3秒,下面的TextBox中的数值一直没有更新

造成这样的原因是,Value3虽然更新了,但是并没有通知绑定他的控件,所以Value3作为输出,需要在更新时触发一个事件,让该事件的订阅者(也就是绑定该值得控件)进行响应。

改进Model

publicclassMainModel:INotifyPropertyChanged

{

   publicdoubleValue1 { get; set; }

   publicdoubleValue2 { get; set; }

   privatedouble_value3;

   publicdoubleValue3

   {

       get { return_value3; }

       set

       {

           _value3=value;

           PropertyChanged?.Invoke(this, newPropertyChangedEventArgs("Value3"));

       }

   }

   publiceventPropertyChangedEventHandler?PropertyChanged;

}

在本案例中,只有三个属性,但是属性很多,难道都需要这样触发事件吗?有没有简单的方式?

PropertyChanged.Fody插件

PropertyChanged.Fody插件可让用户简化事件的通知,Nuget安装PropertyChanged.Fody

[AddINotifyPropertyChangedInterface]

publicclassMainModel

{

   publicdoubleValue1 { get; set; }

   publicdoubleValue2 { get; set; }

   publicdoubleValue3 { get; set; }

}

只需要这样就可以完成上面的工作,它的原理是在编译的时候,把所有的属性都会使用PropertyChanged.Invoke来触发事件。但有时有些属性不需要进行触发,比如本案例的Value1和Value2,所以可以这样

[AddINotifyPropertyChangedInterface]

publicclassMainModel

{

   [DoNotNotify]

   publicdoubleValue1 { get; set; }

   [DoNotNotify]

   publicdoubleValue2 { get; set; }

   publicdoubleValue3 { get; set; }

}

//其他的特性

/// AlsoNotifyFoAttribute     实现通知的时候,同时通知其属性

/// DoNotNotify 指定不需要通知相关的代码

/// DependsOn 指定哪些属性变化的时候,通知当前属性变化

/// DoNotCheckEquality    强制不做旧值比对(默认情况会自动添加比对代码)

命令Command

在上面案例中是使用了构造函数调用了Add方法,但是如果我想增加一个按钮,在点击的时候才执行Add方法要怎么办,当然可以绑定按钮的Click事件,但是这样的话Click事件要放置在View的后台类中,不能很好的利用绑定与ViewModel建立联系,所以引出了命令Command

命令的用途

  1. 将调用命令的对象与执行命令的逻辑分开,这允许多个源调用相同的命令逻辑
  2. 可以指示命令是否可用,如登陆时,用户名为空则登陆按钮不可用

命令要实现ICommand接口,该接口中包含

  • CanExecute:是否可执行方法
  • Execute:主要执行逻辑
  • CanExecuteChanged:触发检查是否可执行

案例入门

  1. 定义一个类实现ICommand接口

publicclassCommandBase : ICommand

{

   publiceventEventHandler?CanExecuteChanged;

   publicboolCanExecute(object?parameter)

   {

       returntrue;//返回true表示命令可用

   }

   //这样做可以在外部给DoExecute委托赋值,根据不同的逻辑业务赋予不同的值

   publicAction<object>DoExecute { get; set; }

   publicvoidExecute(object?parameter)

   {

       DoExecute?.Invoke(parameter);

   }

}

  1. 在ViewModel中定义一个CommandBase属性

publicclassMainViewModel

{

   publicMainModelmainModel { get; set; } =newMainModel();

   publicvoidAdd(objectobj)

   {

       mainModel.Value3=mainModel.Value2+mainModel.Value1;

   }

   publicCommandBaseBtnCommand { get; set; }//命令

   publicMainViewModel()

   {

       BtnCommand=newCommandBase() {DoExecute   =newAction<object>(Add) };

   }

}

  1. 页面增加一个Button按键并进行绑定<Button Command="{Binding BtnCommand}" Content="Ok" />

检测是否可执行

上面的CommandBase中,直接将CanExecute返回为true,其实可以利用这个方法来实现检测是当前按钮是否可以使用

将上面的CommandBase改为

publicFunc<object,bool>DoCanExecute { get; set; }

publicboolCanExecute(object?parameter)

{

   returnDoCanExecute?.Invoke(parameter) ==true;

}

更改ViewModel

publicclassMainViewModel

{

   publicMainModelmainModel { get; set; } =newMainModel();

   publicvoidAdd(objectobj)

   {

       mainModel.Value3=mainModel.Value2+mainModel.Value1;

   }

   publicboolCanCal(objectobj)

   {

       returnmainModel.Value1!=0;

   }

   publicCommandBaseBtnCommand { get; set; }//命令

   publicMainViewModel()

   {

       BtnCommand=newCommandBase() {

           DoExecute=newAction<object>(Add),

           DoCanExecute=newFunc<object, bool>(CanCal) };

   }

}

运行后可以看出OK按钮为不可用状态,但是将上面的文本框改为非零状态,仍然是不可用,这是因为在更改value1后,并没有触发检测事件CanExecuteChanged也就是说此时并没有再次执行CanExecute方法。所以需要触发CanExecuteChanged事件,框架会自动执行CanExecute方法。

因为事件必须在本类中进行触发,所以在CommandBase中增加DoCanExecuteChanged方法并触发CanExecuteChanged事件。

publicvoidDoCanExecuteChanged()

{

   CanExecuteChanged?.Invoke(this, EventArgs.Empty);

}

因为需要在Value1赋值的时候,触发 CanExecuteChanged事件,所以需要将Model中定义一个CommandBase。并将ViewModel中的CommandBase删除。

publicclassMainModel:INotifyPropertyChanged

{

   privatedouble_value1;

   publicdoubleValue1

   {

       get { return_value1; }

       set {

           _value1=value;

           BtnCommand.DoCanExecuteChanged();//在更改时触发CanExecuteChanged事件,该事件触发后会执行CanExecute方法

       }

   }

   publicdoubleValue2 { get; set; }

   privatedouble_value3;

   publicdoubleValue3

   {

       get { return_value3; }

       set

       {

           _value3=value;

           PropertyChanged?.Invoke(this, newPropertyChangedEventArgs("Value3"));

       }

   }

   publiceventPropertyChangedEventHandler?PropertyChanged;

   publicCommandBaseBtnCommand { get; set; }//命令

   publicMainModel()

   {

       BtnCommand=newCommandBase()

       {

           DoExecute=newAction<object>(Add),

           DoCanExecute=newFunc<object, bool>(CanCal)

       };

   }

   publicvoidAdd(objectobj)

   {

       Value3=Value2+Value1;

   }

   publicboolCanCal(objectobj)

   {

       returnValue1!=0;

   }

}

View中的Button更换command绑定<Button Command="{Binding mainModel.BtnCommand}" Content="Ok" />

但是这样的话就必须将Command定义在Model中。那Command如何定义在ViewModel中,请看MVVM系统入门-下。

相关文章
|
前端开发 Ubuntu Linux
【.NET6+Avalonia】开发支持跨平台的仿WPF应用程序以及基于ubuntu系统的演示
随着跨平台越来越流行,.net core支持跨平台至今也有好几年的光景了。但是目前基于.net的跨平台,大多数还是在使用B/S架构的跨平台上;至于C/S架构,大部分人可能会选择QT进行开发,或者很早之前还有一款Mono可以支持.NET开发者进行开发跨平台应用。
848 0
【.NET6+Avalonia】开发支持跨平台的仿WPF应用程序以及基于ubuntu系统的演示
|
4月前
|
人工智能 机器人 C#
Windows编程课设(C#)——基于WPF和.net的即时通讯系统(仿微信)
一款参考QQ、微信的即时通讯软件。采用CS结构,客户端基于.Net与WPF开发,服务端使用Java开发。
|
7月前
|
设计模式 开发框架 前端开发
深入理解WPF中MVVM的设计思想
近些年来,随着WPF在生产,制造,工业控制等领域应用越来越广发,很多企业对WPF开发的需求也逐渐增多,使得很多人看到潜在机会,不断从Web,WinForm开发转向了WPF开发,但是WPF开发也有很多新的概念及设计思想,如:数据驱动,数据绑定,依赖属性,命令,控件模板,数据模板,MVVM等,与传统WinForm,ASP.NET WebForm开发,有很大的差异,今天就以一个简单的小例子,简述WPF开发中MVVM设计思想及应用。
61 0
|
8月前
|
前端开发
WPF-Binding问题-MVVM中IsChecked属性CommandParameter转换值类型空异常
WPF-Binding问题-MVVM中IsChecked属性CommandParameter转换值类型空异常
86 0
|
9月前
|
前端开发 算法 JavaScript
走进WPF之MVVM完整案例
走进WPF之MVVM完整案例
150 0
|
11月前
|
Oracle 数据管理 关系型数据库
WPF实验室信息系统源码,LIS源码
检验报告集中管理: 主要包含申请单详细信息、申请检验组合信息、申请单列表数据、以及结构数据等4四功能模块。可在此模块中查看检验结果,进行检验报告预览。
WPF实验室信息系统源码,LIS源码
|
前端开发 C# 图形学
【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入
前言:在C/S架构上,WPF无疑已经是“桌面一霸”了。在.NET生态环境中,很多小伙伴还在使用Winform开发C/S架构的桌面应用。但是WPF也有很多年的历史了,并且基于MVVM的开发模式,受到了很多开发者的喜爱。
561 0
【.NET6+WPF】WPF使用prism框架+Unity IOC容器实现MVVM双向绑定和依赖注入
|
前端开发 C# 数据库
WPF MVVM系统入门-下
本文详细讲解WPF,MVVM开发,实现UI与逻辑的解耦。
|
18天前
|
C# 开发者 Windows
基于Material Design风格开源、易用、强大的WPF UI控件库
基于Material Design风格开源、易用、强大的WPF UI控件库
|
4月前
|
C#
浅谈WPF之装饰器实现控件锚点
使用过visio的都知道,在绘制流程图时,当选择或鼠标移动到控件时,都会在控件的四周出现锚点,以便于修改大小,移动位置,或连接线等,那此功能是如何实现的呢?在WPF开发中,想要在控件四周实现锚点,可以通过装饰器来实现,今天通过一个简单的小例子,简述如何在WPF开发中,应用装饰器,仅供学习分享使用,如有不足之处,还请指正。
65 1