前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >WPF【框架学习】MVVM初探(经典)

WPF【框架学习】MVVM初探(经典)

作者头像
zls365
发布2021-02-26 10:35:50
2.5K0
发布2021-02-26 10:35:50
举报
文章被收录于专栏:CSharp编程大全

由于工作需要,自己学习了一下MVVM,做以总结。

一、学习前提:

(1)Data Binding

(2)Dependency Property

(3)委托命令

上面三点内容,在学习MVVM之前要求简单了解并掌握使用。

二、MVVM介绍

之前接触并使用过MVC,Model - View - Controller的模式,页面和代码分离的写法,MVVM:Model - View - ViewModel,和WPF很好的进行结合,View负责界面,主要是写.xaml的文件,Model是一些实体类,ViewModel是关键,意思是View的Model,View需要什么,ViewModel提供什么,如果将View理解为界面,Model和ViewModel以及Service等理解为后台的话,那么界面和后台是没有任何关系的,界面开发人员只要告诉后台人员需要哪些对象\属性,就可以进行开发了,二者之间的结合通过Binding操作进行绑定,解耦效果优于MVC,架构图如下:

几个重要的概念:

1、属性

1)数据属性;

2)命令属性;

2、NotificationObject类

3、ICommand接口

MVVM的难点和重点在于View以及MiewModel之间的绑定。

三、项目实战

按照上述架构图新建目录如下:

按照从底层到显示层的策略:

(1)Data
代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<Dishes>
<Dish>
<Name>土豆泥底披萨</Name>
<Category>披萨</Category>
<Comment>本店特色</Comment>
<Score>4.5</Score>
</Dish>
<Dish>
<Name>烤囊底披萨</Name>
<Category>披萨</Category>
<Comment>本店特色</Comment>
<Score>5</Score>
</Dish>
<Dish>
<Name>水果披萨</Name>
<Category>披萨</Category>
<Comment></Comment>
<Score>4</Score>
</Dish>
<Dish>
<Name>牛肉披萨</Name>
<Category>披萨</Category>
<Comment></Comment>
<Score>5</Score>
</Dish>
</Dishes></span>
(2)Model
代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">//定义Model,用于和xml文档中的节点属性匹配 
public class Dish : NotificationObject
{
public string Name { get; set; }
public string Category { get; set; }
public string Comment { get; set; }
public double Score { get; set; }
}</span>
代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">class Restaurant : NotificationObject
{
public string Name { get; set; }
public string Address { get; set; }
public string PhoneNumber { get; set; }
}</span><span style="font-family: 'Microsoft YaHei';font-size:14px; background-color: rgb(255, 255, 255);">  </span>
(3)Service

接口:

代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">//对Data数据进行处理
public interface IDataService
{
List<Dish> GetAllDishes();
}</span>
代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;"> //对订单进行处理
interface IOrderService
{
void PlaceOrder(List<string> dishes);
}</span>

实现:

1.对于Data中的xml数据进行处理

代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">class XmlDataService : IDataService
{
//读取xml文件 - 张振华 - 2016年8月5日10:11:03
public List<Dish> GetAllDishes()
{
//实例化实体Dish集合,用于接收数据并返回值
List<Dish> dishList = new List<Dish>();
//获取xml文件路径
string xmlFileName = System.IO.Path.Combine(Environment.CurrentDirectory, @"Data\Data.xml");
//加载xml文件
XDocument xDoc = XDocument.Load(xmlFileName);
//按照顺序返回<Dishes>集合下<Dish>标签里的所有内容
var dishes = xDoc.Descendants("Dish");
//将xml筛选的集合里的属性与Model对象绑定
foreach (var d in dishes)
{
Dish dish = new Dish();
dish.Name = d.Element("Name").Value;
dish.Category = d.Element("Category").Value;
dish.Comment = d.Element("Comment").Value;
dish.Score = double.Parse(d.Element("Score").Value);
//添加到List集合
dishList.Add(dish);
}

return dishList;
}</span>

2.对订单进行处理

代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">//对选中的订单进行txt文档输出 
class MockOrderService : IOrderService
{
public void PlaceOrder(List<string> dishes)
{
System.IO.File.WriteAllLines(@"C:\order.txt", dishes.ToArray());
}
}</span>

至此,静态的代码编写完毕,无论是使用什么类型的框架,这部分东西大同小异。

(4)ViewModels
代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">//ViewModel类中的属性
class DishMenuItemViewModel : NotificationObject
{
public Dish Dish { get; set; }
//将IsSelected属性和Dish中的属性一起作为DishMenuItemViewModel里的属性
private bool isSelected;

public bool IsSelected
{
get { return isSelected; }
set
{
//RaisePorpertyChanged方法,源于引入的Microsoft.Practices.Prism.ViewModel中的dll属性
isSelected = value;
this.RaisePropertyChanged("IsSelected");//"IsSelected"属性值变化之后,自动通知使用该属性的方法,有点观察者模式的意思
}
}
}</span>

不禁会问,继承的NotificationObject是什么?

它只是一个外部引入的dll文件罢了:

将其放到对应代码的“bin\Debug”下即可,同时”using Microsoft.Practices.Prism.ViewModel;”,当然也可以手写一个NotificationObject类,其中的三个方法自己简单填充就好了。继承NotificationObject之后,在ViewModel当中书写”数据属性”,如上例所属的:"IsSelected",由上述架构图可知,数据属性在View以及ViewModel之间是双向关联的。就是因为this.RaisePropertyChanged("IsSelected")来决定的。

代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">class MainWindowViewModel : NotificationObject
{
//通过委托形式,定义命令属性
public DelegateCommand PlaceOrderCommand { get; set; }
public DelegateCommand SelectMenuItemCommand { get; set; }

private int count;
//数据属性 - Count
public int Count
{
get { return count; }
set
{
count = value;
this.RaisePropertyChanged("Count");
}
}

private Restaurant restaurant;
//数据属性 - Restaurant
public Restaurant Restaurant
{
get { return restaurant; }
set
{
restaurant = value;
this.RaisePropertyChanged("Restaurent");
}
}

private List<DishMenuItemViewModel> dishMenu;
//数据属性 - DishMenu
public List<DishMenuItemViewModel> DishMenu
{
get { return dishMenu; }
set
{
dishMenu = value;
this.RaisePropertyChanged("DishMenu");
}
}

public MainWindowViewModel()
{
this.LoadRestaurant();
this.LoadDishMenu();
//单向“命令属性”的加载过程,通过实例化委托的形式
this.PlaceOrderCommand = new DelegateCommand(new Action(this.PlaceOrderCommandExecute));
this.SelectMenuItemCommand = new DelegateCommand(new Action(this.SelectMenuItemExecute));
}

private void LoadRestaurant()
{
this.Restaurant = new Restaurant();
this.Restaurant.Name = "茶馆餐厅";
this.Restaurant.Address = "深圳华为(来吧,我在这里)";
this.Restaurant.PhoneNumber = "13500672406";
}

private void LoadDishMenu()
{
IDataService ds = new XmlDataService();
var dishes = ds.GetAllDishes();
//为数据属性赋值
this.DishMenu = new List<DishMenuItemViewModel>();
foreach (var dish in dishes)
{
DishMenuItemViewModel item = new DishMenuItemViewModel();
item.Dish = dish;
this.DishMenu.Add(item);
}
}

private void PlaceOrderCommandExecute()
{
//lamad表达式的形式来选取所需要的数据
var selectedDishes = this.DishMenu.Where(i => i.IsSelected == true).Select(i => i.Dish.Name).ToList();
IOrderService orderService = new MockOrderService();
orderService.PlaceOrder(selectedDishes);
MessageBox.Show("订餐成功!");
}

private void SelectMenuItemExecute()
{
this.Count = this.DishMenu.Count(i => i.IsSelected == true);
}
}</span>

这个"栗子"当中,发现“public DelegateCommand PlaceOrderCommand { get; set; }”使用到了命令属性,这里引用:

代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;">using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.ViewModel;
</span>

两个命名空间即可,因为命令属性实现了ICommand接口。

其实会发现,在ViewModel当中并没有很强的业务逻辑,业务逻辑更多的是放到Service当中的,在ViewModel当中所存放的内容,更多的会是一些属性,包括命令属性、数据属性,这些用于和View进行绑定,通过Binding,发现,后台的数据改变了,直接就会在前台页面上更新,这就是MVVM + WPF的魅力之一。同时也要知道,View和ViewModel之间的绑定,也是使用这个框架的难点之一。

(5)View

WPF中View的一个特色就是1、可以拖动控件;2、通过HTML页面对控件的属性、样式进行设置;3、通过Binding和后台数据进行绑定。

如下,是示例中的View:

代码语言:javascript
复制
<span style="font-family:Microsoft YaHei;font-size:14px;"><Border BorderBrush="Orange" BorderThickness="3" CornerRadius="6" Background="Yellow">
<DataGrid AutoGenerateColumns="False" GridLinesVisibility="None" CanUserDeleteRows="False"
CanUserAddRows="False" Margin="0,4" Grid.Row="1" FontSize="16" ItemsSource="{Binding DishMenu}">
<DataGrid.Columns>
<DataGridTextColumn Header="菜品" Binding="{Binding Dish.Name}" Width="120" />
<DataGridTextColumn Header="种类" Binding="{Binding Dish.Category}" Width="120" />
<DataGridTextColumn Header="点评" Binding="{Binding Dish.Comment}" Width="120" />
<DataGridTextColumn Header="推荐分数" Binding="{Binding Dish.Score}" Width="120" />
<DataGridTemplateColumn Header="选中" SortMemberPath="IsSelected" Width="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=IsSelected,  UpdateSourceTrigger=PropertyChanged}"
VerticalAlignment="Center" HorizontalAlignment="Center"
Command="{Binding Path=DataContext.SelectMenuItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2">
<TextBlock Text="共计" VerticalAlignment="Center" />
<TextBox IsReadOnly="True" TextAlignment="Center" Width="120" Text="{Binding Count}" Margin="4,0" />
<Button Content="Order" Height="24" Width="120" Command="{Binding PlaceOrderCommand}" />
</StackPanel>
</Grid>
</Border>
</span>

同时,通过左侧的导航栏,可以快速定位到需要编辑的控件:

HTML页面里,Binding 用的特别多,例如:

代码语言:javascript
复制
<DataGridTextColumn Header="菜品" Binding="{Binding Dish.Name}" Width="120" />
<Button Content="Order" Height="24" Width="120" Command="{Binding PlaceOrderCommand}" />

对于Gird表格,属性里直接Binding="{Binding Dish.Name}",就会把ViewModel当中的Dish对象的Name数据属性与该Gird的对应列进行绑定,对于Command按钮,Command="{BindingPlaceOrderCommand}",将ViewModel当中的PlaceOrderCommand命令属性与Command控件的Command事件进行绑定,从而实现数据更新。

此时会发现,在View的.cs代码里,没有类似于onClick(),这样的方法,都通过绑定实现自动更新了。

(6)设置View的数据来源
代码语言:javascript
复制
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
//设置数据来源
this.DataContext = new MainWindowViewModel();
}
}

在View的.cs文件中,通过this.DataContext = new MainWindowViewModel();的方式,绑定该View的数据来自于哪个ViewModel。

至此,MVVM框架的简单实用,就通过这个例子实现了。

That's all.

运行效果:

参考连接:

1. https://blog.csdn.net/zzh920625/article/details/52138683

2.https://www.cnblogs.com/freedomshe/archive/2012/11/17/WPF_MVVM_todo.html

 下载Prism:http://compositewpf.codeplex.com/releases/view/55576

项目源码网盘下载地址:

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-01-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 CSharp编程大全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、学习前提:
  • 二、MVVM介绍
  • 三、项目实战
    • (1)Data
      • (2)Model
        • (3)Service
          • (4)ViewModels
            • (6)设置View的数据来源
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档