由于公司开发的技术需求,近期在学习MVVM模式开发WPF应用程序。进过一段时间的学习,感受到:学习MVVM模式,最好的方法就是用MVVM做几个Demo,因为编程里面的东西还是原来的WPF的相关知识。最近学习的资料来源大多为CodePlex、CodeProject和MSDN,以及博客园MS的MVP刘铁锰的一些资料。
前面几篇博文DebugLZQ写了,不用Prism框架如何来写MVVM,以及Prism框架的下载使用等等。如果不清楚,请参考前面的博文。
首先来介绍下今天这个Demo要实现的功能,今天开启的系统是XP,所以下面各位看到的将是XP风格的界面。:
·界面上方TextBlock显示餐馆的信息(粉红色字),该信息保存在一个ViewModel的一个餐馆的属性中。
·DataGrid显示菜品信息,从一个模拟的Service中读出;并在最后添加一个CheckBox Binding一个命令用来选择菜品
·下面的TextBox显示选中了几个菜,Button则Binding一个Command实现点菜(象征性的存入本地磁盘)
下面来实现它:
//---------------------
最终的项目的文件结构如下:
前面说过,可以直接饮用Prism,只引入相关的程序集也可以(虽然是一回事),这次我们就这么干!
1.新建一个WpfPrism的WPF项目,添加Prism dll引用,(使用NotificationObject、DelegateCommand)如下:
2.在项目中添加一个Data文件夹,放入Data.XML文件,文件如下:
<?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> <Dish> <Name>培根披萨</Name> <Category>披萨</Category> <Comment></Comment> <Score>4.5</Score> </Dish> <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>5</Score> </Dish> <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>5</Score> </Dish> <Dish> <Name>寂寞小章鱼</Name> <Category>风味小吃</Category> <Comment></Comment> <Score>5</Score> </Dish> <Dish> <Name>照烧鸡软骨</Name> <Category>风味小吃</Category> <Comment></Comment> <Score>5</Score> </Dish> <Dish> <Name>芝士青贝</Name> <Category>风味小吃</Category> <Comment></Comment> <Score>4.5</Score> </Dish> <Dish> <Name>奥尔良烤翅</Name> <Category>风味小吃</Category> <Comment>秒杀KFC</Comment> <Score>5</Score> </Dish> <Dish> <Name>双酱煎泥肠</Name> <Category>风味小吃</Category> <Comment></Comment> <Score>4</Score> </Dish> <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>5</Score> </Dish> <Dish> <Name>日式素沙拉</Name> <Category>沙拉</Category> <Comment></Comment> <Score>5</Score> </Dish> <Dish> <Name>冰糖洛神</Name> <Category>饮料</Category> <Comment></Comment> <Score>5</Score> </Dish> <Dish> <Name>玫瑰特饮</Name> <Category>饮料</Category> <Comment></Comment> <Score>5</Score> </Dish> <Dish> <Name>清新芦荟</Name> <Category>饮料</Category> <Comment></Comment> <Score>5</Score> </Dish> <Dish> <Name>薄荷汽水</Name> <Category>饮料</Category> <Comment>本店特色</Comment> <Score>5</Score> </Dish> </Dishes>
3.在项目中添加Model文件夹。添加两个Model Dish和Restaurant,分别如下:
namespace WpfPrism.Models { class Dish { public string Name { get; set; } public string Category { get; set; } public string Comment { get; set; } public string Score { get; set; } } }
namespace WpfPrism.Models { class Restaurant { public string Name { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } } }
4.在项目中添加Services文件夹,其中IDataService、XMLDataService用来定义和实现:获取菜品信息功能。IOrderService和MockOrderService用来定义和实现:点菜功能。之所以使用接口,是为了定义和实现相分离!
其代码依次如下:
using System.Collections.Generic; using WpfPrism.Models; namespace WpfPrism.Services { interface IDataService { List<Dish> GetAllDishes(); } }
using System; using System.Collections.Generic; using WpfPrism.Models; using System.IO; using System.Xml.Linq; namespace WpfPrism.Services { class XMLDataService:IDataService//接口:定义和实现相分离 { #region IDataService 成员 public List<Models.Dish> GetAllDishes() { List<Dish> dishList = new List<Dish>(); string xmlFile = Path.Combine(Environment.CurrentDirectory, @"Data/Data.xml"); XDocument xDoc = XDocument.Load(xmlFile); var dishes = xDoc.Descendants("Dish"); 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 = d.Element("Score").Value; dishList.Add(dish); } return dishList; } #endregion } }
using System.Collections.Generic; namespace WpfPrism.Services { interface IOrderService { void PlaceOrder(List<string> dishes); } }
using System.Collections.Generic; using System.IO; namespace WpfPrism.Services { class MockOrderService:IOrderService//接口:实现定义和实现相分离 { #region IOrderService 成员 public void PlaceOrder(List<string> dishes) { File.WriteAllLines(@"D:/order.txt", dishes.ToArray()); } #endregion } }
5.在项目中添加一个ViewModels文件夹,并添加两个Model:DishMenuItemViewModel和MianWindowViewModel。
稍微解释一下:MianWindowViewModel中的一个属性是List<MianWindowViewModel>类型的。两者代码分别如下:
using Microsoft.Practices.Prism.ViewModel; using WpfPrism.Models; namespace WpfPrism.ViewModels { class DishMenuItemViewModel:NotificationObject { public Dish Dish { get; set; } private bool isSelected; public bool IsSelected//这个地方刚开始写错了,废了太大的劲才找出来(注意拼写!) { get { return isSelected; } set { isSelected = value; RaisePropertyChanged("IsSelected"); } } } }
using System; using System.Collections.Generic; using System.Linq; using Microsoft.Practices.Prism.ViewModel; using WpfPrism.Models; using WpfPrism.Services; using Microsoft.Practices.Prism.Commands; using System.Windows; namespace WpfPrism.ViewModels { class MianWindowViewModel:NotificationObject { private Restaurant restaurant; public Restaurant Restaurant { get { return restaurant; } set { restaurant = value; RaisePropertyChanged("Restaurant"); } } //外加的一个属性,点菜的数量 private int count; public int Count { get { return count; } set { count = value; RaisePropertyChanged("Count"); } } private List<DishMenuItemViewModel> dishMenu; public List<DishMenuItemViewModel> DishMenu { get { return dishMenu; } set { dishMenu = value; RaisePropertyChanged("DishMenu"); } } public MianWindowViewModel() { LoadRestuarant();//赋值Restaurant属性 LoadDishMenu();//赋值DishMenu属性 //初始化两个命令属性 PlaceOrderCommand = new DelegateCommand(new Action(PlaceOrderCommandExecute)); SelectMenuItemCommand = new DelegateCommand(new Action(SelectMenuItemCommandExecute)); } private void LoadRestuarant() { Restaurant = new Restaurant() {Name="百年苏韵", Address="江苏大学", PhoneNumber="0511-12345678"}; } private void LoadDishMenu() { DishMenu = new List<DishMenuItemViewModel>(); IDataService ds = new XMLDataService(); var dishes = ds.GetAllDishes(); foreach (var d in dishes) { DishMenuItemViewModel item = new DishMenuItemViewModel() { Dish=d}; DishMenu.Add(item); } } //两个命令属性 public DelegateCommand PlaceOrderCommand { get; set; } public DelegateCommand SelectMenuItemCommand { get; set; } private void PlaceOrderCommandExecute() { //获取点菜单 var selectedDishes = dishMenu.Where(d => d.IsSelected == true).Select(d => d.Dish.Name).ToList(); //仅保存到本地磁盘--可以写一些有意义的代码 IOrderService orderService = new MockOrderService(); orderService.PlaceOrder(selectedDishes ); MessageBox.Show("订餐成功!"); } private void SelectMenuItemCommandExecute() { Count = DishMenu.Count(n=>n.IsSelected==true); } } }
注意NotificationObject是ViewModel的基类。
最后,为View添加Binding:
<Window x:Class="WpfPrism.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="590"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> <RowDefinition Height="Auto"/> </Grid.RowDefinitions> <!-- 餐馆信息--> <StackPanel Grid.Row="0"> <StackPanel Orientation="Horizontal" > <TextBlock Text="欢迎光临-" FontSize="40"/> <TextBlock Text="{Binding Restaurant.Name}" FontSize="40" Foreground="HotPink" /> </StackPanel> <StackPanel Orientation="Horizontal" > <TextBlock Text="地址:" FontSize="40"/> <TextBlock Text="{Binding Restaurant.Address}" FontSize="40" Foreground="HotPink" /> </StackPanel> <StackPanel Orientation="Horizontal" > <TextBlock Text="电话:" FontSize="40"/> <TextBlock Text="{Binding Restaurant.PhoneNumber}" FontSize="40" Foreground="HotPink" /> </StackPanel> </StackPanel> <!--菜品信息,选菜--> <DataGrid Grid.Row="1" ItemsSource="{Binding DishMenu}" AutoGenerateColumns="False" GridLinesVisibility="All" CanUserDeleteRows="False" CanUserAddRows="False" > <DataGrid.Columns> <!-- 这4个来自(ViewModel )Dish属性,UI上一次读出,不会变--> <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 Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Right" > <TextBlock Text="点了几个菜?" TextAlignment="Center" /> <TextBox IsReadOnly="True" Text="{Binding Count}" Width="120" TextAlignment="Center" /> <Button Content="点菜" Command="{Binding PlaceOrderCommand}"/> </StackPanel> </Grid> </Window>
using System.Windows; using WpfPrism.ViewModels; namespace WpfPrism { /// <summary> /// MainWindow.xaml 的交互逻辑 /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MianWindowViewModel(); } } }
程序运行如下:
可以在D盘找到如下的txt文件:
心得:学习Prism MVVM唯一的方法就是多看优秀的代码、多写代码、多多思考,才能理解其模式原理!