C# WPF MVVM 實戰 - 2.1


上一篇,只介紹 VM 與 View 是如何關聯起來,說了些注意項,還有個超簡化的例子。這次來點比較實際的,比較靠近項目內會遇到的。

這次看看,采購訂單這業務單據,在 MVVM 模式中實現方式的一個演示。實現方式很多,這示范也只是其中一種。這內容比較多,要分開幾次講。

說在前面,以下是用 VS 2008,.net 3.5,以及對應的 WPF Toolkit 制作。這樣的話,應該絕大部分人都能應用以下例子。

MODELS

假設,系統是有供應商記錄,也有物料記錄,作為主數據。單據記錄就是采購訂單。整個業務層由這四個類組成。設計從 Model 做起,Model 來自用例,這比較自然。數據結構就這樣先吧:

image

 

image

代碼如下:

 

  1. namespace Lepton_Practical_MVVM_2.Models
  2. {
  3.     public class Supplier
  4.     {
  5.         public int Id { get; set; }
  6.         public string SupplierCode { get; set; }
  7.         public string Name { get; set; }
  8.         public string BillAddress { get; set; }
  9.         public string ShipmentAddress { get; set; }
  10.         public string ContactPerson { get; set; } // 聯系人
  11.     }
  12. }

 

 

 

  1. namespace Lepton_Practical_MVVM_2.Models
  2. {
  3.     public class Inventory
  4.     {
  5.         public int Id { get; set; }
  6.         public string ItemCode { get; set; }
  7.         public string ItemName { get; set; }
  8.         public string Specification { get; set; }
  9.         public string Uom { get; set; } // 計量單位
  10.     }
  11. }

 

 

  1. using System;
  2.  
  3. namespace Lepton_Practical_MVVM_2.Models
  4. {
  5.     public class PurchaseOrderDetail
  6.     {
  7.         public int Id { get; set; }
  8.         public int ParentId { get; set; }
  9.         public string ItemCode { get; set; }
  10.         public decimal OrderedQty { get; set; }
  11.         public DateTime? RequestedDeliveryDate { get; set; } // 要求送貨日期
  12.         public string Remark { get; set; }
  13.     }
  14. }

 

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4.  
  5. namespace Lepton_Practical_MVVM_2.Models
  6. {
  7.     public class PurchaseOrder
  8.     {
  9.         public int Id { get; set; }
  10.         public string DocNo { get; set; }
  11.         public DateTime DocDate { get; set; }
  12.         public string Remark { get; set; }
  13.         public string SupplierCode { get; set; }
  14.         public IList<PurchaseOrderDetail> PoDetails { get; set; } // 行明細
  15.     }
  16. }

 

采購訂單有表頭與明細行,兩個部分組成,明細行在 PurchaseOrder 類內是 IList<T> 因為一張單可以有多行記錄,一對多,我用 IList 因為准備用 NHibernate 做 ORM。你喜歡其他集合也可以。熟悉商用開發的朋友,應該對這樣的結構很熟悉了。其他我不多說了。

VIEWS

 

 

 

 

 

然后看看界面,是這樣樣子:

image

 

呃,有點丑。咱們還是看代碼吧…

 

  1. <Window x:Class="Lepton_Practical_MVVM_2.Views.Window1"
  2.     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.     xmlns:my="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
  5.     Title="WPF MVVM 實戰 - 新添加采購訂單" Height="500" Width="668.772"
  6.     >
  7.     <DockPanel>
  8.         <!-- 單據表頭部分 -->
  9.         <Grid DockPanel.Dock="Top" Height="200">
  10.             <ComboBox Height="23" Margin="121.465,21.435,160.048,0"
  11.                       VerticalAlignment="Top"
  12.                       ItemsSource="{Binding SupplierList}"
  13.                       DisplayMemberPath="Name"
  14.                       SelectedItem="{Binding SelectedSupplier}"
  15.                       />
  16.             <my:DatePicker Margin="121.465,58.069,160.048,0" Height="24.233"
  17.                            VerticalAlignment="Top"
  18.                            SelectedDate="{Binding DocDate}"/>
  19.             <TextBox Margin="121.465,88.598,160.048,88.598"
  20.                      Text="{Binding DocNo}"/>
  21.             <Label HorizontalAlignment="Left" Margin="6,21.435,0,0"
  22.                    Width="100.03" Height="28"
  23.                    VerticalAlignment="Top">供應商</Label>
  24.             <Label Height="28" HorizontalAlignment="Left"
  25.                    Margin="6.577,54.302,0,0" VerticalAlignment="Top"
  26.                    Width="100.03">單據日期</Label>
  27.             <Label HorizontalAlignment="Left" Margin="6.577,88.598,0,82.882"
  28.                    Width="100.03">單據號</Label>
  29.             <Label Height="28.52" HorizontalAlignment="Left"
  30.                    Margin="6.577,0,0,48.586" VerticalAlignment="Bottom"
  31.                    Width="100.03">備注</Label>
  32.             <TextBox Height="58.589" Margin="121.465,0,160.048,18.577"
  33.                      VerticalAlignment="Bottom" TextWrapping="Wrap"
  34.                      Text="{Binding DocRemark}"/>
  35.             <TextBlock Height="21" HorizontalAlignment="Right"
  36.                        Margin="0,23.435,6,0" Name="textBlock1"
  37.                        VerticalAlignment="Top" Width="146.903"
  38.                        Text="{Binding SelectedSupplier.ContactPerson}"/>
  39.         </Grid>
  40.         
  41.         <!-- 單據操作按鈕部分 -->
  42.         <Grid DockPanel.Dock="Bottom" Height="50">
  43.             <Button HorizontalAlignment="Right" Margin="0,19.722,6,6"
  44.                     Width="75" Content="取消"
  45.                     Command="{Binding CloseViewCommand}"/>
  46.             <Button HorizontalAlignment="Right" Margin="0,19.722,87.169,6"
  47.                     Width="75" Content="保存"
  48.                     Command="{Binding SaveCommand}"/>
  49.         </Grid>
  50.         
  51.         <!-- 單據表體,明細行部分 -->
  52.         <DockPanel>
  53.             <!-- 添加刪除行按鈕 -->
  54.             <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Height="30">
  55.                 <Button Content="添加行" Margin="3,3,3,3"
  56.                         Command="{Binding AddRowCommand}"/>
  57.                 <Button Content="刪除行" Margin="3,3,3,3"
  58.                         Command="{Binding DeleteRowCommand}"/>
  59.             </StackPanel>
  60.             <!-- 明細行表格 -->
  61.             <my:DataGrid CanUserAddRows="False"
  62.                           AutoGenerateColumns="False"
  63.                           ItemsSource="{Binding purchaseOrder.PoDetails}"
  64.                           SelectedItem="{Binding CurrentRow}" >
  65.                 <my:DataGrid.Resources>
  66.                     <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
  67.                                      Color="LightBlue"/>
  68.                 </my:DataGrid.Resources>
  69.                 <my:DataGrid.Columns>
  70.                     
  71.                     <my:DataGridTemplateColumn Header="物料號">
  72.                         <my:DataGridTemplateColumn.CellEditingTemplate>
  73.                             <DataTemplate>
  74.                                 <StackPanel Orientation="Horizontal">
  75.                                     <TextBlock MinWidth="100" Text="{Binding ItemCode}"/>
  76.                                     <Button Content="..."
  77.                                             Command="{Binding ItemCodeSelectionCommand}"/>
  78.                                 </StackPanel>
  79.                             </DataTemplate>
  80.                         </my:DataGridTemplateColumn.CellEditingTemplate>
  81.                         <my:DataGridTemplateColumn.CellTemplate>
  82.                             <DataTemplate>
  83.                                 <TextBlock MinWidth="100" Text="{Binding ItemCode}"/>
  84.                             </DataTemplate>
  85.                         </my:DataGridTemplateColumn.CellTemplate>
  86.                     </my:DataGridTemplateColumn>
  87.                     
  88.                     <my:DataGridTextColumn Header="數量" Binding="{Binding OrderedQty}"/>
  89.                     
  90.                     <my:DataGridTemplateColumn Header="要求交貨日期">
  91.                         <my:DataGridTemplateColumn.CellEditingTemplate>
  92.                             <DataTemplate>
  93.                                 <my:DatePicker SelectedDate="{Binding RequestedDeliveryDate}"/>
  94.                             </DataTemplate>
  95.                         </my:DataGridTemplateColumn.CellEditingTemplate>
  96.                         <my:DataGridTemplateColumn.CellTemplate>
  97.                             <DataTemplate>
  98.                                 <TextBlock Text="{Binding RequestedDeliveryDate, StringFormat=dd/MM/yyyy}"/>
  99.                             </DataTemplate>
  100.                         </my:DataGridTemplateColumn.CellTemplate>
  101.                     </my:DataGridTemplateColumn>
  102.                     
  103.                     <my:DataGridTextColumn Header="備注" Width="200"
  104.                                            Binding="{Binding Remark}"/>
  105.                     
  106.                 </my:DataGrid.Columns>
  107.             </my:DataGrid>
  108.         </DockPanel>
  109.     </DockPanel>
  110. </Window>

整個布局,用 DockPanel,分上中下三個部分,分別用來放置表頭,明細行,和操作按鈕。明細行區域又用了 DockPanel 再分開了添加行、刪除行按鈕區域,和明細行的 GridView。整個 XAML 我唯一調過樣式的,是 GridView 的當前行高亮底色,原來的藍色實在太刺眼了。

全部綁定都是寫 Path,因為整個 Window 的 DataContext 就是 ViewModel,它提供一切數據(或者負責指向實際業務類的實例)。我假設大家會用 Template,會一般的綁定,不解釋了。

VIEWMODELS

然后是 ViewModel,我從表頭開始講。

里面比較有趣的,是一個 Combo Box,它應該出現的選項,是 Supplier 業務類的集合。再看看 PurchaseOrder 這個類的結構,用戶選了 Supplier 之后,放進去 PurchaseOrder 不是 Supplier 實例,而是 SupplierCode 。

image

除此之外,看看 XAML ,我還搞了一個 TextBlock 在 Combo Box 旁邊,用來顯示一些關於這 Supplier 供應商的額外信息,比如我顯示了聯系人。

image

image

要做到這兩點需求,不能單靠 Path 綁來綁去就能解決,我需要一個已選擇了的供應商對象,存放在 ViewModel,然后在 TextBlock 綁過去,用 Path 指定要顯示信息的路徑。在我這例子,這已選定的供應商屬性,變量名是 SelectedSupplier,我要顯示聯系人,所以整個 Binding 的路徑就是 SelectedSupplier.ContactPerson,見 XAML 第 38 行。

我認為這做法的好處是,如果有哪天你需要更多關於該選定供應商的信息,顯示在界面,你 ViewModel 啥都不用改,只在 View 的 XAML 加個控件設一下路徑即可。

然后,選定的供應商,是這樣傳到 SelectedSupplier 屬性的:

image

 

 

那么,供應商編號,又是如何傳進去 Model (PurchaseOrder)的 SupplierCode 呢?就在 ViewModel 的 SelectedSupplier 中 Setter 代碼,這里:

image

每一次選定的供應商變化,由 Combo Box 綁定至 SelectedSupplier,而它除了更新屬性值以外,還同時更新到業務對象 PurchaseOrder 的實例屬性 SupplierCode 內。

整個 Combo Box 和它的“額外信息”,就是這樣處理了。

看看到目前為止的 ViewModel 代碼,數據層代碼我不貼出來了,我只是做了些假數據讓數據層提供而已。我代碼內的 FillSupplierList 方法,也應該開線程來讀,各位自己注意一下自己改吧。其他部分下次繼續…

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Collections.ObjectModel;
  6. using System.Windows.Input; // ICommand
  7. using IPE.Framework.UI.ViewModels; // ViewModelBase
  8. using IPE.Framework.UI.Commands; // RelayCommand
  9.  
  10. namespace Lepton_Practical_MVVM_2.ViewModels
  11. {
  12.     public class MainWindowViewModel : ViewModelBase
  13.     {
  14.         public MainWindowViewModel()
  15.         {
  16.             Initialize();
  17.         }
  18.  
  19.         private void Initialize()
  20.         {
  21.             purchaseOrder = new Models.PurchaseOrder();
  22.             purchaseOrder.PoDetails = new ObservableCollection<Models.PurchaseOrderDetail>();
  23.             SupplierList = new ObservableCollection<Models.Supplier>();
  24.  
  25.             FillSupplierList();
  26.         }
  27.  
  28.         private void FillSupplierList()
  29.         {
  30.             List<Models.Supplier> customerlist = DataAccess.DataProvider.GetAllCustomers();
  31.             foreach (Models.Supplier customer in customerlist)
  32.             {
  33.                 this.SupplierList.Add(customer);
  34.             }
  35.             customerlist = null;
  36.         }
  37.  
  38.         #region Acutal Model Object reference
  39.         public Models.PurchaseOrder purchaseOrder { get; set; }
  40.         #endregion
  41.  
  42.         #region Supplier Selection Combo Box
  43.  
  44.         private Models.Supplier selectedSupplier;
  45.         public Models.Supplier SelectedSupplier
  46.         {
  47.             get { return selectedSupplier; }
  48.             set
  49.             {
  50.                 if (selectedSupplier != value)
  51.                 {
  52.                     selectedSupplier = value;
  53.                     purchaseOrder.SupplierCode = value.SupplierCode;
  54.                     OnPropertyChanged("SelectedSupplier");
  55.                 }
  56.             }
  57.         }
  58.  
  59.         public ObservableCollection<Models.Supplier> SupplierList { get; set; }
  60.  
  61.         #endregion
  62.  
  63.         // 待續 ...
  64.     }
  65. }

 

效果圖:

image

image

我在這群里,歡迎加入交流:
開發板玩家群 578649319開發板玩家群 578649319
硬件創客 (10105555)硬件創客 (10105555)


免責聲明!

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



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