使用Prism提供的類實現WPF MVVM點餐Demo


由於公司開發的技術需求,近期在學習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

Hello Prism Using MEF

沒什么高端的知識,老鳥繞過輕拍~

希望對你有幫助~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM