由於公司開發的技術需求,近期在學習MVVM模式開發WPF應用程序。進過一段時間的學習,感受到:學習MVVM模式,最好的方法就是用MVVM做幾個Demo,因為編程里面的東西還是原來的WPF的相關知識。最近學習的資料來源大多為CodePlex、CodeProject和MSDN,以及博客園MS的MVP劉鐵錳的一些資料。
前面幾篇博文DebugLZQ寫了,如何來寫MVVM,以及Prism框架的安裝等等。
本篇在前面的基礎上,通過一個相對復雜一點的Demo,來學習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文件,文件如下:
View Code
<?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,分別如下:
View Code
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; } } }
View Code
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用來定義和實現:點菜功能。之所以使用接口,是為了定義和實現相分離!
其代碼依次如下:
View Code
using System.Collections.Generic; using WpfPrism.Models; namespace WpfPrism.Services { interface IDataService { List<Dish> GetAllDishes(); } }
View Code
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 } }
View Code
using System.Collections.Generic; namespace WpfPrism.Services { interface IOrderService { void PlaceOrder(List<string> dishes); } }
View Code
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文件:

也請參考CodeProject:WPF Master Details MVVM Application
說明:本文使用Prism框架中的幾個類,來簡化MVVM的編寫。並未所見標准Prism的Bootstrapper、Shell、Region、Module、Unity/MEF...
關於Prism框架的知識,請關注DebugLZQ后續博文:
Prism框架-Hello Prism Using Unity
沒什么高端的知識,老鳥繞過輕拍~
希望對你有幫助~
