如何在WPF中使用MVVM开发架构开发一个餐馆点餐系统(CrazyElephant)
2013-08-19 16:11
1221 查看
MVVM的最大优势就是在界面的本质没有改变的情况下ViewModel代码是都不需要改动的。
1)确定UI中使用了多好命令属性和数据属性。
先来看看看法完的界面,进行一下分析:
通过UI窗体可以分析出来:
(1)餐馆的名字;餐馆的地址;订餐电话都是有可能更改的所以,这些属性需要Binding到一个ViewModel上去,并且需要一个餐馆的类来作为Model类。
(2)下面的DataGrid里都是菜品的信息,需要一个菜品的Model类。
(3)“共计“和”Order“是需要使用到命令属性Binding的。
数据属性有:餐馆信息;DataGrid里显示的菜品信息;”共计”里显示的数据。
命令属性有:“Order”操作;DataGrid中的“选中“操作。
这里用到了包含对命令属性俩种最典型的使用方法:一种是单独UI元素和命令属性的绑定;第二种是集合元素中每个元素与命令属性的关联关系。
2)MVVM的架构:
接着分析的话,面向对象的开发,数据要组织在对象里,而不是凌乱的乱放。所以这里的餐馆的名字,地址,电话都应该是来自同一个数据源对象。也就是这几个数据应该来源于一个餐馆的对象的属性。菜品信息应该来源于菜品对象。
3)开发源代码架构:
(1)Data文件家里的xml文件是作为项目的DB使用。先新建一个Data文件夹,然后把已经准备的XML文件copy进来,为了让这个data数据能够与程序发布,需要秀爱该文件的属性为:
(2)有了Data后,我们需要一个Service来读取这个Data数据,由于这个是一个模拟数据,所以我们的DataService在后期实际中会发生变化,这个时候就需要使用接口把数据读取的定义和实现进行分开:先新建一个Services文件夹,在这个文件夹中新建一个接口IDataService用来定义读取数据的接口类:
(3)那这个时候就可以发现我们的Service和Model是紧密相关的(这里的Dish类),所以就需要新建一个Model文件夹,在该文件夹里新建一个Dish类,而且这个Dish菜品信息类是对应着我们的Data.xml数据的:
(4)有了这个Model后,我们就可以继续写Service了,需要实现读取xml文件的类来实现IDataService
(5)下订单的Service:IOrderService
实现该接口的MockOrderService,这里只是简单的保存到txt文本中:
(6)下一步是需要对餐馆进行抽象,在Models里新建一个Restaurant类:
(7)这样我们Model有了Service也有了,下一步是建立ViewModel和View,这里需要说明的是,最好把ViewMode和Model分开,因为View是与ViewModel进行交互的,如果直接与Model进行交互,则会把一些没有进行处理判断的数据提交给Model,ViewModel可以起到对数据的过滤和校验的工作,这样的话从ViewModel传回来的数据就是干净的数据,起到对Model的保护的作用。所以并不建议直接在View上绑定到Model里的属性上。新建一个ViewModels文件夹,在该文件夹里新建MainWindowViewModel类,那么这个ViewModel里需要的一个属性是餐馆属性,另外一个属性就需要在分析一下,DataGrid里是需要N列Dish来显示菜单信息吗?实际上不是,因为每道菜都会有一个”选中“的属性,这个”选中“用户操作的实际上不是每道菜,而是每道菜的ViewModel。很显然这样的话我们需要给每个Dish建立以个ViewModel:
(8)建立主要的View的ViewModel:MainWindowViewModel
(9)View的实现,绑定到ViewModel上的数据属性和命令属性。
(10)千万不要忘了如何把view文件与viewmodel之间如何建立起联系,这里需要指定当前View的DataContext为哪个ViewModel:
源代码: http://download.csdn.net/detail/eric_k1m/5975757
1)确定UI中使用了多好命令属性和数据属性。
先来看看看法完的界面,进行一下分析:
通过UI窗体可以分析出来:
(1)餐馆的名字;餐馆的地址;订餐电话都是有可能更改的所以,这些属性需要Binding到一个ViewModel上去,并且需要一个餐馆的类来作为Model类。
(2)下面的DataGrid里都是菜品的信息,需要一个菜品的Model类。
(3)“共计“和”Order“是需要使用到命令属性Binding的。
数据属性有:餐馆信息;DataGrid里显示的菜品信息;”共计”里显示的数据。
命令属性有:“Order”操作;DataGrid中的“选中“操作。
这里用到了包含对命令属性俩种最典型的使用方法:一种是单独UI元素和命令属性的绑定;第二种是集合元素中每个元素与命令属性的关联关系。
2)MVVM的架构:
接着分析的话,面向对象的开发,数据要组织在对象里,而不是凌乱的乱放。所以这里的餐馆的名字,地址,电话都应该是来自同一个数据源对象。也就是这几个数据应该来源于一个餐馆的对象的属性。菜品信息应该来源于菜品对象。
3)开发源代码架构:
(1)Data文件家里的xml文件是作为项目的DB使用。先新建一个Data文件夹,然后把已经准备的XML文件copy进来,为了让这个data数据能够与程序发布,需要秀爱该文件的属性为:
(2)有了Data后,我们需要一个Service来读取这个Data数据,由于这个是一个模拟数据,所以我们的DataService在后期实际中会发生变化,这个时候就需要使用接口把数据读取的定义和实现进行分开:先新建一个Services文件夹,在这个文件夹中新建一个接口IDataService用来定义读取数据的接口类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WPFMVVMDemo.Services { public interface IDataService { //使用接口把定义和实现分离开 有利于后期更改数据源 //接口定以后,可以用其他的类来实现接口内的方法,来指定实现接口的方法名称。 List<Dish> GetAllDishes(); } }
(3)那这个时候就可以发现我们的Service和Model是紧密相关的(这里的Dish类),所以就需要新建一个Model文件夹,在该文件夹里新建一个Dish类,而且这个Dish菜品信息类是对应着我们的Data.xml数据的:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WPFMVVMDemo.Models { ////作为ViewModel的基础类来存在 public class Dish { public string Name { set; get; } public string Category { set; get; } public string Comment { set; get; } public double Score { set; get; } } }
(4)有了这个Model后,我们就可以继续写Service了,需要实现读取xml文件的类来实现IDataService
using System; using System.Collections.Generic; using System.Linq; using System.Text; using WPFMVVMDemo.Models; using System.Xml.Linq; namespace WPFMVVMDemo.Services { //继承IDataService后来实现GetAllDishes方法来获取数据信息 public class XmlDataService:IDataService { public List<Dish> GetAllDishes() { List<Dish> dishList = new List<Dish>(); string xmlFileName = System.IO.Path.Combine(Environment.CurrentDirectory, @"Data\Data.xml"); XDocument xDoc = XDocument.Load(xmlFileName); //从文件创建新 XDocument //Descendants(XName) 按文档顺序返回此文档或元素的经过筛选的子代元素集合。集合中只包括具有匹配 XName 的元素。 //也就是获取XML文件中的各个<Dish></Dish>之间的数据集合 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 = double.Parse(d.Element("Score").Value); dishList.Add(dish); } return dishList; } } }
(5)下订单的Service:IOrderService
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WPFMVVMDemo.Services { //点击order按钮下订单用 public interface IOrderService { void PlaceOrder(List<string> dishes); } }
实现该接口的MockOrderService,这里只是简单的保存到txt文本中:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WPFMVVMDemo.Services { //实现IOrderService,来保存所点的菜品信息。 public class MockOrderService:IOrderService { public void PlaceOrder(List<string> dishes) { System.IO.File.WriteAllLines(@"C:\order.txt", dishes.ToArray()); } } }
(6)下一步是需要对餐馆进行抽象,在Models里新建一个Restaurant类:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace WPFMVVMDemo.Models { class Restaurant { public string Name { get; set; } public string Address { get; set; } public string PhoneNumber { get; set; } } }
(7)这样我们Model有了Service也有了,下一步是建立ViewModel和View,这里需要说明的是,最好把ViewMode和Model分开,因为View是与ViewModel进行交互的,如果直接与Model进行交互,则会把一些没有进行处理判断的数据提交给Model,ViewModel可以起到对数据的过滤和校验的工作,这样的话从ViewModel传回来的数据就是干净的数据,起到对Model的保护的作用。所以并不建议直接在View上绑定到Model里的属性上。新建一个ViewModels文件夹,在该文件夹里新建MainWindowViewModel类,那么这个ViewModel里需要的一个属性是餐馆属性,另外一个属性就需要在分析一下,DataGrid里是需要N列Dish来显示菜单信息吗?实际上不是,因为每道菜都会有一个”选中“的属性,这个”选中“用户操作的实际上不是每道菜,而是每道菜的ViewModel。很显然这样的话我们需要给每个Dish建立以个ViewModel:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Practices.Prism.ViewModel; using WPFMVVMDemo.Models; namespace WPFMVVMDemo.ViewModels { //由于有了菜单是否被选中的操作,就会有是否选中这个操作属性,只有把这道菜放到用户界面上 //用户进行选择的时候才会有是否选择这个属性,这个时候用户操作的不在是菜品本身Model而是ViewModel //每一道菜也应该有自己的ViewModel,每一条菜品都有一个自己的ViewModel //INotifyPropertyChanged和NotificationObject的区别是:INotifyPropertyChanged不用再引用新的类, //只要using System.ComponentModel即可;但NotificationObject需要引用,而且也不用写相关的事件实现: // public event PropertyChangedEventHandler PropertyChanged;所以使用NotificationObject更方便, //引用Prism位置在C:\Program Files (x86)\Prism4.0\Bin\Silverlight里的Microsoft.Practices.Prism类文件。 class DishMenuItemViewModel:NotificationObject { //这里组成了一个菜品在view上显示所需的属性信息,每个菜品都需要一个是否选中的操作checkbox,所以这里需要 //添加一个IsSelected的属性来组成一个新的要在view上显示的菜品信息。 //这个时候也就是说在viewmodel中有一个model类型的属性Dish,这叫做有一个的关系(ViewModel中有一个Model) //通过这种关系我们可以获取Model的值和属性。 public Dish Dish { set; get; } private bool isSelected; public bool IsSelected { get { return isSelected; } set { isSelected = value; RaisePropertyChanged("IsSelected"); } } } }
(8)建立主要的View的ViewModel:MainWindowViewModel
using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Practices.Prism.ViewModel; using Microsoft.Practices.Prism.Commands; using WPFMVVMDemo.Models; using WPFMVVMDemo.Services; using System.Windows; namespace WPFMVVMDemo.ViewModels { //一个ViewModel对应着一个(或多个)View,也就是说每个View都会有一个(或多个)后台的ViewModel, //但Model指的是对各个对象的抽象,一个ViewModel里可以有多个Model类作为属性存在。 class MainWindowViewModel:NotificationObject { //命令属性 DelegateCommand public DelegateCommand PlaceOrderCommand { get; set; } public DelegateCommand SelectMenuItemCommand { get; set; } //数据属性 显示选中了几个菜品 private int count; public int Count { get { return count; } set { count = value; RaisePropertyChanged("Count"); } } //数据属性 View中显示餐馆相关的餐馆名称,地址电话等属性。 private Restaurant restaurant; public Restaurant Restaurant { get { return restaurant; } set { restaurant = value; RaisePropertyChanged("Restaurant"); //这里的Restaurant是对象,在View中可以使用Binding Restaurant.Name 等属性 } } //数据属性 一列DishMenuViewModel,用来在DataGrid里显示菜品信息 private List<DishMenuItemViewModel> dishMenu; public List<DishMenuItemViewModel> DishMenu { get { return dishMenu; } set { dishMenu = value; RaisePropertyChanged("DishMenu"); } } public MainWindowViewModel() { //加载餐馆信息 this.LoadRestaurant(); //加载菜单信息 this.LoadDishMenu(); //命令属性绑定到相应的委托命令上来执行操作 this.PlaceOrderCommand = new DelegateCommand(new Action(this.PlaceOrderCommandExecute)); this.SelectMenuItemCommand = new DelegateCommand(new Action(this.SelectMenuItemCommandExecute)); } private void LoadDishMenu() { //先用DataService把所有菜品先读取进来,在根据菜品一个一个的创建菜品的ViewModel XmlDataService ds = new XmlDataService(); var dishes = ds.GetAllDishes(); //新建一列菜品信息,把菜品信息赋值给当前ViewModel的DishMenu属性 this.DishMenu = new List<DishMenuItemViewModel>(); foreach (var dish in dishes) { DishMenuItemViewModel item = new DishMenuItemViewModel(); item.Dish = dish; this.DishMenu.Add(item); } } private void LoadRestaurant() { //给当前ViewModle中Restaurant属性赋值 this.Restaurant = new Restaurant(); this.Restaurant.Name = "餐馆名称"; this.Restaurant.Address = "餐馆地址"; this.Restaurant.PhoneNumber = "电话号码"; } private void PlaceOrderCommandExecute() { //查询出当前ViewModel的DishMenu属性为check状态的Dish菜品的名字集合 //由于DishMenu属性是List<>类型的,所以可以使用LINQ来查询。 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 SelectMenuItemCommandExecute() { //给当前Count属性赋值是当前ViewModel中的DishMenu属性的Count属性的值 //由于DishMenu属性是List<>类型的,所以可以使用LINQ来查询。 this.Count = this.DishMenu.Count(i => i.IsSelected == true); } } }
(9)View的实现,绑定到ViewModel上的数据属性和命令属性。
<Window x:Class="WPFMVVMDemo.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="{Binding Restaurant.Name,StringFormat=\{0\}-在线订餐系统}" Height="600" Width="1000" WindowStartupLocation="CenterScreen"> <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="6" Background="Yellow"> <Grid x:Name="Root" Margin="4"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Border BorderBrush="Orange" BorderThickness="1" CornerRadius="6" Padding="4"> <StackPanel> <StackPanel Orientation="Horizontal"> <StackPanel.Effect> <DropShadowEffect Color="LightGray" /> </StackPanel.Effect> <TextBlock Text="欢迎光临-" FontSize="60" FontFamily="LiShu" /> <!--绑定到MainWindowViewModel中的Restaurant属性类的Name属性上--> <TextBlock Text="{Binding Restaurant.Name}" FontSize="60" FontFamily="LiShu" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="小店地址:" FontSize="24" FontFamily="LiShu" /> <!--绑定到MainWindowViewModel中的Restaurant属性类的Address属性上--> <TextBlock Text="{Binding Restaurant.Address}" FontSize="24" FontFamily="LiShu" /> </StackPanel> <StackPanel Orientation="Horizontal"> <TextBlock Text="订餐电话:" FontSize="24" FontFamily="LiShu" /> <!--绑定到MainWindowViewModel中的Restaurant属性类的PhoneNumber属性上--> <TextBlock Text="{Binding Restaurant.PhoneNumber}" FontSize="24" FontFamily="LiShu" /> </StackPanel> </StackPanel> </Border> <!--把DataGrid的ItemSource绑定到MainWindowViewModel中的DishMenu属性类上--> <DataGrid AutoGenerateColumns="False" GridLinesVisibility="None" CanUserDeleteRows="False" CanUserAddRows="False" Margin="0,4" Grid.Row="1" FontSize="16" ItemsSource="{Binding DishMenu}"> <DataGrid.Columns> <!--绑定到MainWindowViewModel类的DishMenu(List<DishMenuItemViewModel>类型的)属性类的DishMenuItemViewModel. Dish属性类的Name属性上--> <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" /> <!--Check功能,为了达到点一次就能选取上需要使用CellTemplate来实现,数据绑定到MainWindowViewModel类的 DishMenu(List<DishMenuItemViewModel>类型的)属性类的IsSelected属性上--> <DataGridTemplateColumn Header="选中" SortMemberPath="IsSelected" Width="120"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <!--绑定到MainWindowViewModel类的SelectedMenuItemCommand命令属性上--> <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" /> <!--绑定到当前ViewModel(MainWindowViewModel类)的Count属性上--> <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> </Window>
(10)千万不要忘了如何把view文件与viewmodel之间如何建立起联系,这里需要指定当前View的DataContext为哪个ViewModel:
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Documents; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Navigation; using System.Windows.Shapes; using WPFMVVMDemo.ViewModels; namespace WPFMVVMDemo { /// <summary> /// Interaction logic for MainWindow.xaml /// </summary> public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); //指定MainWindow的DataContext为MainWindowViewModel这个ViewModel this.DataContext = new MainWindowViewModel(); } } }
源代码: http://download.csdn.net/detail/eric_k1m/5975757
相关文章推荐
- 如何在cocos2d-x中使用ECS(实体-组件-系统)架构方法开发一个游戏?
- 如何在cocos2d-x中使用ECS(实体-组件-系统)架构方法开发一个游戏?
- 如何开发一个异常检测系统:使用什么特征变量(features)来构建异常检测算法
- 如何使用PHP开发高效的WEB系统
- 使用jQuery mobile开发一个基于手机的在线餐馆订餐查询应用(第一部分)
- 初学者如何开发出一个高质量的J2EE系统
- 如何开发一个灵活的可容错的ERP系统的一些沉思
- (译)如何使用cocos2d开发一个简单的iphone游戏:旋转炮塔。(第二部分)
- 初学者如何开发出一个高质量的J2EE系统
- 使用jQuery mobile开发一个基于手机的在线餐馆订餐查询应用
- 【WPF开发备忘】使用MVVM模式开发中列表控件内的按钮事件无法触发解决方法
- 如何使用国际开源项目构建一个完整的GIS(地理信息)应用系统
- 初学者如何开发出一个高质量的J2EE系统
- 3 .如何使用cocos2d开发一个简单的iphone游戏:旋转炮塔(第二部分)
- 初学者如何开发出一个高质量的J2EE系统
- 如何在WPF项目中使用ArcEngine的控件做开发
- 如何使用区块链开发一个落地项目?这位实战大牛手把手教会你
- 使用jQuery mobile开发一个基于手机的在线餐馆订餐查询应用(第一部分)
- 初学者如何开发出一个高质量的J2EE系统
- 初学者如何开发出一个高质量的J2EE系统