WPF——初识MVVM(二)
说明:该案例与前一个案例的来源一样,同样讲述的是MVVM,算是MVVM的进阶吧。在该案例中,涉及到数据访问,Prism等更为复杂的问题。首先来看界面设计,我在作者的基础上稍微做了改动,大致如下:
1.首先还是来一个解决方案的截图:
2.实现步骤
2.1)从OO的角度来说,首先是完成基类的书写,该案例主要有两个对象,是Dish类(菜品类)和Restaurant(餐馆类),实现起来都较为简单,具体如下:
1 public class Restaurant 2 { 3 /// <summary> 4 /// 餐馆名称 5 /// </summary> 6 public string Name 7 { 8 get; 9 set; 10 } 11 12 /// <summary> 13 /// 餐馆地址 14 /// </summary> 15 public string Address 16 { 17 get; 18 set; 19 } 20 21 /// <summary> 22 /// 订餐电话 23 /// </summary> 24 public string PhoneNumber 25 { 26 get; 27 set; 28 } 29 }
1 /// <summary> 2 ///菜品类 3 /// </summary> 4 class Dish 5 { 6 //1.Data Property 7 /// <summary> 8 /// 菜品名称 9 /// </summary> 10 public string Name { get; set; } 11 /// <summary> 12 /// 菜品类别 13 /// </summary> 14 public string Category { get; set; } 15 /// <summary> 16 /// 菜品评论 17 /// </summary> 18 public string Comment { get; set; } 19 /// <summary> 20 /// 菜品评分 21 /// </summary> 22 public double Score { get; set; } 23 }
2.2)接口的定义和实现
无论是从技术上还是从团队的角度来谈,使用接口的好处是显而易见的。这里做个简单的架设,该项目中的数据源是xml,如果涉及到不同的数据源就可以用不同的方法来实现接口来获取数据。
首先定义接口IDataService和IOrderService:
1 interface IDataService 2 { 3 List<Dish> GetAllDishes(); 4 }
1 interface IOrderService 2 { 3 void PlaceOrder(List<string> dishes); 4 }
接口的实现:
1 class XmlDataService :IDataService 2 { 3 public List<Dish> GetAllDishes() 4 { 5 List<Dish> dishList = new List<Dish>(); 6 //System.IO.Path.Combine将两个字符串组合成一个路径(http://msdn.microsoft.com/zh-cn/library/z8te35sa(v=vs.80).aspx) 7 string xmlFileName = System.IO.Path.Combine(Environment.CurrentDirectory, @"Data\Data.xml"); 8 //XDocument.Load(string uri) 9 XDocument xDoc = XDocument.Load(xmlFileName); 10 //按文档顺序返回此文档或元素的经过筛选的子代元素集合。集合中只包括具有匹配 XName 的元素。 11 //XName即Data.xml中的 <Dish></Dish>节点 12 var dishes = xDoc.Descendants("Dish"); 13 //IEnumerable dishes = xDoc.Descendants("Dish"); 14 15 foreach (var d in dishes) 16 { 17 Dish dish = new Dish(); 18 dish.Name = d.Element("Name").Value; 19 dish.Category = d.Element("Category").Value; 20 dish.Comment = d.Element("Comment").Value; 21 dish.Score = double.Parse(d.Element("Score").Value); 22 dishList.Add(dish); 23 } 24 return dishList; 25 } 26 27 }
1 class MockOrderService :IOrderService 2 { 3 public void PlaceOrder(List<string> dishes) 4 { 5 System.IO.File.WriteAllLines(@"C:\order.txt", dishes.ToArray()); 6 } 7 }
说明:该XmlDataService的作用是从xml文件中读取数据,MockOrderService则是简单的将订单信息存储在本地磁盘。
2.3)主要的业务逻辑实现
这里涉及到了Prism,Prism已经为我们实现了INotifyPropertyChanged和ICommand接口,我们只需添加引用集,添加命名空间和使相关类继承NotificationObject。
1 using Microsoft.Practices.Prism.ViewModel; 2 using MVVMDemo2.Models; 3 4 class DishMenuItemViewModel : NotificationObject 5 { 6 public Dish Dish { get; set; }//(有一个关系) 7 8 private bool isSelected; 9 10 public bool IsSelected 11 { 12 get { return isSelected; } 13 set 14 { 15 isSelected = value; 16 this.RaisePropertyChanged("IsSelected"); 17 } 18 } 19 20 }
在引用了Microsoft.Practices.Prism.ViewModel命名空间,并继承NotificationObject后就可以使用RasisePropertyChanged()方法了,NotificationObject实际上派生自INotifyPropertyChanged。
1 using Microsoft.Practices.Prism.ViewModel; 2 using Microsoft.Practices.Prism.Commands; 3 using MVVMDemo2.Services; 4 using System.Windows; 5 6 namespace MVVMDemo2.ViewModels 7 { 8 class MainWindowViewModel : NotificationObject 9 { 10 #region 定义命令属性和数据属性 11 //Command property 12 public DelegateCommand PlaceOrderCommand { get; set; } 13 public DelegateCommand SelectMenuItemCommand { get; set; } 14 15 //Data Property 16 /// <summary> 17 /// 选择个数 18 /// </summary> 19 private int count; 20 21 public int Count 22 { 23 get { return count; } 24 set 25 { 26 count = value; 27 //若不继承NotificationObject,这里将会报错 28 this.RaisePropertyChanged("Count"); 29 } 30 } 31 32 /// <summary> 33 /// 餐馆信息 34 /// </summary> 35 private Restaurant restaurant; 36 37 public Restaurant Restaurant 38 { 39 get { return restaurant; } 40 set 41 { 42 restaurant = value; 43 this.RaisePropertyChanged("Restaurant"); 44 } 45 } 46 47 /// <summary> 48 /// 餐馆菜品 49 /// </summary> 50 private List<DishMenuItemViewModel> dishMenu; 51 52 public List<DishMenuItemViewModel> DishMenu 53 { 54 get { return dishMenu; } 55 set 56 { 57 dishMenu = value; 58 this.RaisePropertyChanged("DishMenu"); 59 } 60 } 61 #endregion 62 63 64 public MainWindowViewModel() 65 { 66 this.LoadRestaurant(); 67 this.LoadDishMenu(); 68 69 this.PlaceOrderCommand = new DelegateCommand(new Action(this.PlaceOrderCommandExecute)); 70 this.SelectMenuItemCommand = new DelegateCommand(new Action(this.SelectMenuItemExecute)); 71 72 } 73 74 /// <summary> 75 /// 加载餐馆信息 76 /// </summary> 77 private void LoadRestaurant() 78 { 79 this.Restaurant = new Restaurant(); 80 81 this.Restaurant.Name = "乔治伊诺"; 82 this.Restaurant.Address = "小花园立交桥西口233号"; 83 this.Restaurant.PhoneNumber = "0932313520"; 84 85 } 86 87 /// <summary> 88 /// 加载菜品信息 89 /// </summary> 90 private void LoadDishMenu() 91 { 92 IDataService ds = new XmlDataService(); 93 var dishes = ds.GetAllDishes(); 94 this.DishMenu = new List<DishMenuItemViewModel>(); 95 foreach (var dish in dishes) 96 { 97 DishMenuItemViewModel item = new DishMenuItemViewModel(); 98 item.Dish = dish; 99 this.DishMenu.Add(item ); 100 101 } 102 103 } 104 105 /// <summary> 106 /// 执行订餐操作 107 /// </summary> 108 private void PlaceOrderCommandExecute() 109 { 110 var selectedDishes = this.DishMenu.Where(i => i.IsSelected == true).Select(i => i.Dish.Name).ToList(); 111 112 IOrderService orderService = new MockOrderService(); 113 orderService.PlaceOrder(selectedDishes); 114 //var unSelected = this.DishMenu.Where(i => i.IsSelected == false).Select(i => i.Dish.Name).ToList(); 115 // orderService.PlaceOrder(unSelected); 116 MessageBox.Show("订餐成功!"); 117 } 118 119 /// <summary> 120 /// 执行操作 121 /// </summary> 122 private void SelectMenuItemExecute() 123 { 124 this.Count = this.DishMenu.Count(i=>i.IsSelected==true); 125 } 126 127 } 128 }
说明:添加了 Microsoft.Practices.Prism.Commands命名空间,就可以使用DelegateCommand,如上代码所示。其继承层次结构为:DelegateCommand>DelegateCommandBae>ICommand, IActiveAware。
最后通过在CS中添加 this.DataContext = new MainWindowViewModel();并在XAML代码中进行绑定即可:
1 <Window x:Class="MVVMDemo2.MainWindow" 2 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 3 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 Title="{Binding Restaruant.Name,StringFormat=\{0\}-在线订餐}" Height="600" Width="1000" WindowStartupLocation="CenterScreen"> 5 <Border BorderThickness="10" Name="borderWrap" Background="Orange" BorderBrush="GreenYellow" CornerRadius="10"> 6 <Border BorderBrush="Orange" BorderThickness="3" CornerRadius="6" Background="Yellow"> 7 <Grid x:Name="Root" Margin="4"> 8 <Grid.RowDefinitions> 9 <RowDefinition Height="Auto" /> 10 <RowDefinition Height="*" /> 11 <RowDefinition Height="Auto" /> 12 </Grid.RowDefinitions> 13 <Border BorderBrush="Orange" BorderThickness="2" CornerRadius="6" Padding="4"> 14 <StackPanel> 15 <StackPanel Orientation="Horizontal"> 16 <StackPanel.Effect> 17 <DropShadowEffect Color="LightGray" /> 18 </StackPanel.Effect> 19 <TextBlock Text="欢迎光临-" FontSize="60" FontFamily="KaiTi" /> 20 <TextBlock Text="{Binding Restaurant.Name}" FontSize="60" FontFamily="LiShu" /> 21 </StackPanel> 22 <StackPanel Orientation="Horizontal"> 23 <TextBlock Text="餐厅地址:" FontSize="24" FontFamily="LiShu" /> 24 <TextBlock Text="{Binding Restaurant.Address}" FontSize="24" FontFamily="LiShu" /> 25 </StackPanel> 26 <StackPanel Orientation="Horizontal"> 27 <TextBlock Text="订餐电话:" FontSize="24" FontFamily="LiShu" /> 28 <TextBlock Text="{Binding Restaurant.PhoneNumber}" FontSize="24" FontFamily="LiShu" /> 29 </StackPanel> 30 </StackPanel> 31 </Border> 32 <DataGrid AutoGenerateColumns="False" GridLinesVisibility="None" CanUserDeleteRows="False" 33 CanUserAddRows="False" Margin="0,4" Grid.Row="1" FontSize="16" ItemsSource="{Binding DishMenu}"> 34 <DataGrid.Columns> 35 <DataGridTextColumn Header="菜品" Binding="{Binding Dish.Name}" Width="120" /> 36 <DataGridTextColumn Header="种类" Binding="{Binding Dish.Category}" Width="120" /> 37 <DataGridTextColumn Header="点评" Binding="{Binding Dish.Comment}" Width="120" /> 38 <DataGridTextColumn Header="推荐分数" Binding="{Binding Dish.Score}" Width="120" /> 39 <DataGridTemplateColumn Header="选中" SortMemberPath="IsSelected" Width="120"> 40 <DataGridTemplateColumn.CellTemplate> 41 <DataTemplate> 42 <CheckBox IsChecked="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" 43 VerticalAlignment="Center" HorizontalAlignment="Center" 44 Command="{Binding Path=DataContext.SelectMenuItemCommand, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}" /> 45 </DataTemplate> 46 </DataGridTemplateColumn.CellTemplate> 47 </DataGridTemplateColumn> 48 </DataGrid.Columns> 49 </DataGrid> 50 <StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="2"> 51 <TextBlock Text="共计" VerticalAlignment="Center" /> 52 <TextBox IsReadOnly="True" TextAlignment="Center" Width="120" Text="{Binding Count}" Margin="4,0" /> 53 <Button Content="我要订餐" Height="24" Width="120" Command="{Binding PlaceOrderCommand}" /> 54 </StackPanel> 55 </Grid> 56 </Border> 57 </Border> 58 </Window>
Form:深入浅出WPF MVVM入门与提高(刘铁锰 wpfgeek@live.com)