上一篇介绍了增加删除行可以怎样做,现在说填写时候,在某一栏让用户选择,选项的集合是每行数据共用。想说说一个真的系统内,你或许要的一些设计、一些你需要做的决定。

技术上,这次有 :

  1. BackgroundWorker 加载列表
  2. 消除采购订单 ViewModel 对另一个它自己要打开的 View 的依赖
  3. 要 Routed Event 的地方你要绑个 ICommand 过去的办法

DataGrid 明细行内选择物料,物料列表是集合,但集合不在明细行的类内

接上篇,物料列表是明细行多行共用一个集合。了解绑定写法的,或许第一想到的就是 RelativeSource 用 FindAncestor 模式,DataGrid 单行内某栏的控件ComboBox 的 ItemSource 也能绑过去 VM 顶层的集合。嗯,你只有三四个选项那好办,但这是物料号,举例我有 50,000 个物料,用 ComboBox 的用户体验不好。这数量的选项,你有很多选择可以用的,比如用可编辑的 ComboBox 然后做自动过滤选项加上写 ComboxBox 拉下的模板,又比如弹出子窗体让用户筛选、选择。

我用子窗体示范。

何时加载物料列表

我会把同步读取放在 VM 构造函数启动来作示例。因为,我认为填新采购订单,用户是先填表头再填明细行,我要的是在用户打开填写时候,甚至是界面还没出现前(VM 构造函数运行在 View.Show() 之前),就开始背景加载物料列表。这做法,在 VM 加载,意味着要重新打开采购订单界面,才能刷新物料列表。

设计

我这做法很简单,同一个 ViewModel 绑两个 View。原来的采购订单是一个 View,弹出窗口是另一个 View。大家的 DataContext 是同一个 ViewModel 这样会少了很多麻烦,后果是 ViewModel 代码变长。我觉得,就一个选择用的窗体而已,不想分别写 VM。如果你要分开,请注意,在采购订单的 ViewModel 分线程加载后更新的集合,你需要有办法通知子窗体的 ViewModel 让它更新视图。

采购订单 ViewModel 加入物料集合及读取线程

首先是采购订单的 ViewModel 内,初始化时开线程读取列表。例子中用 BackgroundWorker,用它比较省事。

C# WPF MVVM 实战 – 2.3

C# WPF MVVM 实战 – 2.3

C# WPF MVVM 实战 – 2.3

读数据跟 MVVM 关系不大。留意一下 InventoryListWorker.RunWorkerCompleted,我把结果放进去两个 List<T>,子窗体过滤时候用,一个是全的、代码不动它的,一个是用户过滤后的。这样比较省事。

采购订单 ViewModel 加入打开子窗体的命令

代码逻辑很简单,Modal Window 用 ShowDialog() 打开,连查看 ShowDialog() 回传的 true/false 都省掉了,因为是同一个 VM,子窗体直接操作采购订单 VM 内的明细行,下部分讲。

 

 

C# WPF MVVM 实战 – 2.3

或许看到这里,new 关键字,子窗体类(View)出现在 VM 代码,马上眉头皱。这不是 MVVM 哦。这里 VM 对 View 依赖。

一下重构就把它灭了。要把子窗体分离出来,想不 new 它,你可以写视图服务让它提供,工厂之类,有 IoC 容器或许更方便。要做不做,你在不在乎这依赖,请自行判断。

C# WPF MVVM 实战 – 2.3

C# WPF MVVM 实战 – 2.3

然后,像这示例没 IoC 容器的,只能在外面 new 咯。

C# WPF MVVM 实战 – 2.3

这样做,你不需要任何 View,就可以测试这 MainWindowViewModel 类。测试下一篇讲。

 

 

子窗体的属性与命令

首先看看这子窗体的外貌:

C# WPF MVVM 实战 – 2.3

两个输入框用来拿过滤用(绑定两个 string 属性),两个按钮(绑定两个 ICommand),一个 DataGrid (绑定过滤后的集合)。还有一个,DataGrid 内 Double Click 命令绑定,让用户双击选物料。

对 MVVM 开始理解了的话,下面这些就很简单。我过滤没有什么算法,直接 LINQ,然后替换集合而已。唯一麻烦的是,Double Click 的绑定。

C# WPF MVVM 实战 – 2.3

C# WPF MVVM 实战 – 2.3

C# WPF MVVM 实战 – 2.3

C# WPF MVVM 实战 – 2.3

子窗体的 DataGrid 双击绑定

关于窗体,我唯一想说的,是双击。

首先,微软有病。控件的命令,为何有时候是指定一定要 RoutedCommand,有时候又可以 ICommand。我不知道,问他们不要问我。

要解决,闲着没事自己写 Attached Property + 转换,或者,下个源码,测试过你喜欢的,或许改一下,收录进去代码库。你会经常用到的。

我拿个 BehaviorBinding 的代码来改。原版它本身有个问题,就是你绑定没写或者名字错,绑不到,它会抛异常。这与其他属性绑定的行为不一样。你觉得没什么大不了的话,直接用。让它吃掉异常这很好改,我这部分不公开了。原版的源码好像是这里的,我不太记得,打不开请FQ,或者自行 Google。

C# WPF MVVM 实战 – 2.3

 

我做法,两步,第一是 Preview 让它执行 ICommand,第二是这个 View 的 DataGrid_MouseDoubleClick。第二步是干嘛?没干嘛:

C# WPF MVVM 实战 – 2.3

重要部分的源码

讲代码部分,终于结束。其实代码挺简单的,几百行其实大部分都是属性的 get/set,说那么多其实是想让大家在决定是否用 MVVM 时候心里有个底,也希望大家有个概念怎样搞。下篇才讲测试。低耦合人人爱之余,我认为测试也是 MVVM 的重点所在。

app.xaml.cs

 

namespace Lepton_Practical_MVVM_2 {
    /// <summary>
   
/// Interaction logic for App.xaml
   
/// </summary>
    public partial class App : Application {
        protected override void OnStartup(StartupEventArgs e) {
            base.OnStartup(e);
            Views.Window1 view = new Views.Window1();
            ViewModels.MainWindowViewModel vm = 
                new ViewModels.MainWindowViewModel(new Views.ViewProvider());
            view.DataContext = vm;
            view.Show();
        }
    }
}

 

 

Window1.xaml

 

<Window x:Class="Lepton_Practical_MVVM_2.Views.Window1"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my
="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit"
    Title
="WPF MVVM 实战 - 新添加采购订单" Height="500" Width="668.772" 
   
>
    <DockPanel>
        <!-- 单据表头部分 -->
        <Grid DockPanel.Dock="Top" Height="200">
            <ComboBox Height="23" Margin="121.465,21.435,160.048,0" 
                      VerticalAlignment
="Top" 
                      ItemsSource
="{Binding SupplierList}" 
                      DisplayMemberPath
="Name" 
                      SelectedItem
="{Binding SelectedSupplier}"
                     
/>
            <my:DatePicker Margin="121.465,58.069,160.048,0" Height="24.233" 
                           VerticalAlignment
="Top" 
                           SelectedDate
="{Binding DocDate}"/>
            <TextBox Margin="121.465,88.598,160.048,88.598" 
                     Text
="{Binding DocNo}"/>
            <Label HorizontalAlignment="Left" Margin="6,21.435,0,0" 
                   Width
="100.03" Height="28" 
                   VerticalAlignment
="Top">供应商</Label>
            <Label Height="28" HorizontalAlignment="Left" 
                   Margin
="6.577,54.302,0,0" VerticalAlignment="Top" 
                   Width
="100.03">单据日期</Label>
            <Label HorizontalAlignment="Left" Margin="6.577,88.598,0,82.882" 
                   Width
="100.03">单据号</Label>
            <Label Height="28.52" HorizontalAlignment="Left" 
                   Margin
="6.577,0,0,48.586" VerticalAlignment="Bottom" 
                   Width
="100.03">备注</Label>
            <TextBox Height="58.589" Margin="121.465,0,160.048,18.577" 
                     VerticalAlignment
="Bottom" TextWrapping="Wrap" 
                     Text
="{Binding DocRemark}"/>
            <TextBlock Height="21" HorizontalAlignment="Right" 
                       Margin
="0,23.435,6,0" Name="textBlock1" 
                       VerticalAlignment
="Top" Width="146.903" 
                       Text
="{Binding SelectedSupplier.ContactPerson}"/>
        </Grid>
        
        <!-- 单据操作按钮部分 -->
        <Grid DockPanel.Dock="Bottom" Height="50">
            <Button HorizontalAlignment="Right" Margin="0,19.722,6,6" 
                    Width
="75" Content="取消" 
                    Command
="{Binding CloseViewCommand}" Click="Button_Click" />
            <Button HorizontalAlignment="Right" Margin="0,19.722,87.169,6" 
                    Width
="75" Content="保存" 
                    Command
="{Binding SaveCommand}"/>
        </Grid>
        
        <!-- 单据表体,明细行部分 -->
        <DockPanel>
            <!-- 添加删除行按钮 -->
            <StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Height="30">
                <Button Content="添加行" Margin="3,3,3,3" 
                        Command
="{Binding AddRowCommand}"/>
                <Button Content="删除行" Margin="3,3,3,3" 
                        Command
="{Binding DeleteRowCommand}"/>
            </StackPanel>
            <!-- 明细行表格 -->
            <my:DataGrid  CanUserAddRows="False" 
                          AutoGenerateColumns
="False" 
                          ItemsSource
="{Binding purchaseOrder.PoDetails}"
                          SelectedItem
="{Binding CurrentRow}"
                          Name
="myDataGrid">
                <my:DataGrid.Resources>
                    <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" 
                                     Color
="LightBlue"/>
                </my:DataGrid.Resources>
                <my:DataGrid.Columns>
                    
                    <my:DataGridTemplateColumn Header="物料号">
                        <my:DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <StackPanel Orientation="Horizontal">
                                    <TextBlock MinWidth="100" Text="{Binding ItemCode}"/>
                                    <Button Content="..." 
                                            Command
="{Binding DataContext.ItemCodeSelectionCommand,RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type Window}}}"/>
                                </StackPanel> 
                            </DataTemplate>
                        </my:DataGridTemplateColumn.CellEditingTemplate>
                        <my:DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock MinWidth="100" Text="{Binding ItemCode}"/>
                            </DataTemplate>
                        </my:DataGridTemplateColumn.CellTemplate>
                    </my:DataGridTemplateColumn>
                    
                    <my:DataGridTextColumn Header="数量" Binding="{Binding OrderedQty}"/>
                    
                    <my:DataGridTemplateColumn Header="要求交货日期">
                        <my:DataGridTemplateColumn.CellEditingTemplate>
                            <DataTemplate>
                                <my:DatePicker SelectedDate="{Binding RequestedDeliveryDate}"/>
                            </DataTemplate>
                        </my:DataGridTemplateColumn.CellEditingTemplate>
                        <my:DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding RequestedDeliveryDate, StringFormat=dd/MM/yyyy}"/>
                            </DataTemplate>
                        </my:DataGridTemplateColumn.CellTemplate>
                    </my:DataGridTemplateColumn>
                    
                    <my:DataGridTextColumn Header="备注" Width="200"
                                           Binding
="{Binding Remark}"/>
                    
                </my:DataGrid.Columns>
            </my:DataGrid>
        </DockPanel>
    </DockPanel>
</Window>

 

 

MainWindowViewModel.cs

 

namespace Lepton_Practical_MVVM_2.ViewModels {
    public class MainWindowViewModel : ViewModelBase {

        private BackgroundWorker InventoryListWorker;
        private BackgroundWorker SupplierListWorker;
        private readonly Views.IViewProvider viewProvider;

        public MainWindowViewModel(Views.IViewProvider viewProvider) {
            this.viewProvider = viewProvider;
            Initialize();
            InitializeAndStartWorkers();
        }

        private void Initialize() {
            // 新的采购订单业务对象
            purchaseOrder = new Models.PurchaseOrder();
            purchaseOrder.PoDetails = new ObservableCollection<Models.PurchaseOrderDetail>();
        }

        private void InitializeAndStartWorkers() {
            // 读取供应商列表
            SupplierListWorker = new BackgroundWorker();
            SupplierListWorker.DoWork += (s, e) => {
                e.Result = DataAccess.DataProvider.GetAllSuppliers();
            };
            SupplierListWorker.RunWorkerCompleted += (s, e) => {
                if (!(e.Error == null)) {
                    System.Windows.MessageBox.Show("获取供应商数据失败:" + e.Error.Message);
                } else {
                    this.SupplierList = (List<Models.Supplier>)e.Result;
                }
            };
            SupplierListWorker.RunWorkerAsync();

            // 读取物料列表
            InventoryListWorker = new BackgroundWorker();
            InventoryListWorker.DoWork += (s, e) => {
                e.Result = DataAccess.DataProvider.GetAllInventoryItems();
            };
            InventoryListWorker.RunWorkerCompleted += (s, e) => {
                if (!(e.Error == null)) {
                    System.Windows.MessageBox.Show("获取物料列表数据失败:" + e.Error.Message);
                } else {
                    this.ItemList = (List<Models.Inventory>)e.Result;
                    this.FilteredItemList = this.ItemList;
                }
            };
            InventoryListWorker.RunWorkerAsync();
        }

        #region Acutal Model Object reference
        public Models.PurchaseOrder purchaseOrder {
            get;
            set;
        }
        #endregion

        #region Supplier Selection Combo Box

        private Models.Supplier selectedSupplier;
        public Models.Supplier SelectedSupplier {
            get {
                return selectedSupplier;
            }
            set {
                if (selectedSupplier != value) {
                    selectedSupplier = value;
                    purchaseOrder.SupplierCode = value.SupplierCode;
                    OnPropertyChanged("SelectedSupplier");
                }
            }
        }

        private List<Models.Supplier> supplierList;
        public List<Models.Supplier> SupplierList {
            get {
                return supplierList;
            }
            set {
                supplierList = value;
                OnPropertyChanged("SupplierList");
            }
        }

        #endregion

        #region Purchase Order Document Number, Date, Remark
        public string DocNo {
            get {
                return purchaseOrder.DocNo;
            }
            set {
                purchaseOrder.DocNo = value;
                OnPropertyChanged("DocNo");
            }
        }

        public DateTime DocDate {
            get {
                return purchaseOrder.DocDate;
            }
            set {
                purchaseOrder.DocDate = value;
                OnPropertyChanged("DocDate");
            }
        }

        public string DocRemark {
            get {
                return purchaseOrder.Remark;
            }
            set {
                purchaseOrder.Remark = value;
                OnPropertyChanged("DocRemark");
            }
        }
        #endregion

        #region Detail Rows Add and Remove Commands

        private Models.PurchaseOrderDetail currentRow;
        public Models.PurchaseOrderDetail CurrentRow {
            get {
                return currentRow;
            }
            set {
                if (currentRow != value) {
                    currentRow = value;
                    OnPropertyChanged("CurrentRow");
                }
            }
        }

        RelayCommand addRowCommand;
        public ICommand AddRowCommand {
            get {
                if (addRowCommand == null) {
                    addRowCommand = new RelayCommand(x => this.AddRow());
                }
                return addRowCommand;
            }
        }

        private void AddRow() {
            this.purchaseOrder.PoDetails.Add(new Models.PurchaseOrderDetail());
        }

        RelayCommand deleteRowCommand;
        public ICommand DeleteRowCommand {
            get {
                if (deleteRowCommand == null) {
                    deleteRowCommand = new RelayCommand(
                        x => this.DeleteRow(),
                        x => {
                            return this.CurrentRow != null;
                        }
                        );
                }
                return deleteRowCommand;
            }
        }

        private void DeleteRow() {
            this.purchaseOrder.PoDetails.Remove(CurrentRow);
            CurrentRow = null;
        }

        #endregion

        #region Popup window item list and filtered list
        private List<Models.Inventory> itemList;
        public List<Models.Inventory> ItemList {
            get {
                return itemList;
            }
            set {
                itemList = value;
                OnPropertyChanged("ItemList");
            }
        }

        private List<Models.Inventory> filteredItemList;
        public List<Models.Inventory> FilteredItemList {
            get {
                return filteredItemList;
            }
            set {
                filteredItemList = value;
                OnPropertyChanged("FilteredItemList");
            }
        }
        #endregion

        #region Command for open popup window, note the tight coupling here

        RelayCommand itemCodeSelectionCommand;
        public ICommand ItemCodeSelectionCommand {
            get {
                if (itemCodeSelectionCommand == null) {
                    itemCodeSelectionCommand =
                        new RelayCommand(x => this.OpenItemSelectionDialog());
                }
                return itemCodeSelectionCommand;
            }
        }
        private void OpenItemSelectionDialog() {
            System.Windows.Window dialog = viewProvider.GetItemCodeSelectionWindow();
            dialog.DataContext = this;
            dialog.ShowDialog();
        }

        #endregion

        #region Popup window binding properties and commands for filter

        private string searchItemNameText;
        public string SearchItemNameText {
            get {
                return searchItemNameText;
            }
            set {
                searchItemNameText = value;
                OnPropertyChanged("SearchItemNameText");
            }
        }

        private string searchItemSpecText;
        public string SearchItemSpecText {
            get {
                return searchItemSpecText;
            }
            set {
                searchItemSpecText = value;
                OnPropertyChanged("SearchItemSpecText");
            }
        }

        RelayCommand searchCommand;
        public ICommand SearchCommand {
            get {
                if (searchCommand == null) {
                    searchCommand = new RelayCommand(x => this.Search());
                }
                return searchCommand;
            }
        }
        private void Search() {
            if (!String.IsNullOrEmpty(SearchItemNameText)) {
                FilteredItemList = ItemList
                    .Where(x => x.ItemName.ToLower().Contains(SearchItemNameText.ToLower()))
                    .ToList();
            }

            if (!String.IsNullOrEmpty(SearchItemSpecText)) {
                FilteredItemList = ItemList
                    .Where(x => x.Specification.ToLower().Contains(SearchItemSpecText.ToLower()))
                    .ToList();
            }
        }

        RelayCommand clearResultCommand;
        public ICommand ClearResultCommand {
            get {
                if (clearResultCommand == null) {
                    clearResultCommand = new RelayCommand(x => this.ClearResult());
                }
                return clearResultCommand;
            }
        }
        private void ClearResult() {
            FilteredItemList = ItemList;
            SearchItemNameText = string.Empty;
            SearchItemSpecText = string.Empty;
        }

        #endregion

        #region Popup window double click DataGrid Command and property

        private Models.Inventory selectedInventoryItem;
        public Models.Inventory SelectedInventoryItem {
            get {
                return selectedInventoryItem;
            }
            set {
                selectedInventoryItem = value;
                OnPropertyChanged("SelectedInventoryItem");
            }
        }

        RelayCommand selectCommand;
        public ICommand SelectCommand {
            get {
                if (selectCommand == null) {
                    selectCommand = new RelayCommand(x => this.Select());
                }
                return selectCommand;
            }
        }
        private void Select() {
            if (SelectedInventoryItem != null) {
                CurrentRow.ItemCode = SelectedInventoryItem.ItemCode;
            }
        }

        #endregion
    }
}

 

 

IViewProvider.cs

namespace Lepton_Practical_MVVM_2.Views {
    public interface IViewProvider {
        System.Windows.Window GetItemCodeSelectionWindow();
    }
}

 

ViewProvider.cs

 

namespace Lepton_Practical_MVVM_2.Views {
    public class ViewProvider :IViewProvider{

        #region IViewProvider Members

        public System.Windows.Window GetItemCodeSelectionWindow() {
            return new ItemCodeSelectionWindow();
        }

        #endregion
    }
}

 

 

ItemCodeSelectionWindow.xaml

 

<Window x:Class="Lepton_Practical_MVVM_2.Views.ItemCodeSelectionWindow"
    xmlns
="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:my
="http://schemas.microsoft.com/wpf/2008/toolkit"
    xmlns:cmd
="clr-namespace:IPE.Framework.UI.Commands;assembly=IPE.Framework"
    Title
="请选择物料" MinHeight="300" MinWidth="400"
    WindowStyle
="ToolWindow" >
    <DockPanel>
        <Grid DockPanel.Dock="Top" Height="50" >
            <Label HorizontalAlignment="Left" Margin="19,6,0,16" Name="label2" Width="59">物料名:</Label>
            <TextBox Text="{Binding SearchItemNameText}" Margin="70,6,0,21" Name="textBox1" HorizontalAlignment="Left" Width="120" />
            <Label Margin="205,6,250,16" Name="label3">规格:</Label>
            <TextBox Text="{Binding SearchItemSpecText}" Margin="245,6,137,21" Name="textBox2" />
            <Button Command="{Binding SearchCommand}"  HorizontalAlignment="Right" Margin="0,4.638,73,22.362" Width="58" Content="搜索"/>
            <Button Command="{Binding ClearResultCommand}"  HorizontalAlignment="Right" Margin="0,4.638,9,22.362" Width="58" Content="清空搜索"/>
        </Grid>
        <my:DataGrid AutoGenerateColumns="False"
                     CanUserAddRows
="False"
                     ItemsSource
="{Binding FilteredItemList}"
                     SelectedItem
="{Binding SelectedInventoryItem}"
                     MouseDoubleClick
="DataGrid_MouseDoubleClick"
                    
>
            <cmd:CommandBehaviorCollection.Behaviors>
                <cmd:BehaviorBinding Event="PreviewMouseDoubleClick" Command="{Binding SelectCommand}"/>
            </cmd:CommandBehaviorCollection.Behaviors>
            
            <my:DataGrid.Resources>
                <SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" 
                                     Color
="LightBlue"/>
            </my:DataGrid.Resources>
            <my:DataGrid.Columns>
                <my:DataGridTemplateColumn Header="物料号" MinWidth="100">
                    <my:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding ItemCode}"/>
                        </DataTemplate>
                    </my:DataGridTemplateColumn.CellTemplate>
                </my:DataGridTemplateColumn>
                <my:DataGridTemplateColumn Header="物料名称" MinWidth="200">
                    <my:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding ItemName}"/>
                        </DataTemplate>
                    </my:DataGridTemplateColumn.CellTemplate>
                </my:DataGridTemplateColumn>
                <my:DataGridTemplateColumn Header="规格" MinWidth="200">
                    <my:DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <TextBlock Text="{Binding Specification}"/>
                        </DataTemplate>
                    </my:DataGridTemplateColumn.CellTemplate>
                </my:DataGridTemplateColumn>
            </my:DataGrid.Columns>
        </my:DataGrid>
    </DockPanel>
</Window>

我在这群里,欢迎加入交流:
C# WPF MVVM 实战 – 2.3开发板玩家群 578649319
C# WPF MVVM 实战 – 2.3硬件创客 (10105555)

相关文章:

  • 2022-12-23
  • 2022-12-23
  • 2022-02-20
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-08-09
  • 2021-08-20
猜你喜欢
  • 2021-10-06
  • 2022-12-23
  • 2022-12-23
  • 2021-10-01
  • 2022-12-23
相关资源
相似解决方案