您的位置:首页 > 运维架构 > 网站架构

如何在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用来定义读取数据的接口类:

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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐