• 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)

  • 相关阅读:
    第一篇博客
    【面试大系】PHP程序员的知识盘点
    【PHP资源】PHP 资源大全
    【前端经纬】将页面元素定位
    大爱卡农三百年
    【夯实PHP基础】PHP标准库 SPL
    【http抓包】记录一次抓手机app的接口
    【算法】PHP实现冒泡排序和快速排序--防遗忘
    Apache的初中级面试题
    【生活感悟系列】感悟在一瞬间(不断完善中)
  • 原文地址:https://www.cnblogs.com/leptonation/p/2493545.html
Copyright © 2020-2023  润新知