主从结构在企业级应用中相当常见,这里结合我的例子谈一下wpf中主从结构列表展示的常用做法,具体效果见 wpf企业级开发中的几种常见业务场景。
首先,Model有两种,主表对应model(假设为modelA),从表对应的model(假设为modelB),两种model分别用于绑定列表,就是普通列表的绑定。
其次,由于要实现联动效果(即选择主表中的一条记录显示从表的记录),故而我们的ViewModel里面必须设计一个SelectedModelA用来绑定选中项,SelectedModelA变化时去更新modelB列表的数据源(通常SelectedModelA中会包含一个集合,不过我这里由于其他原因单独弄了个集合,逻辑其实大同小异)。
下面是我的代码,由于夹杂着一些业务,仅供参考,其实读者只需明白主表的选中项作为从表UI的数据源即可。
UI部分,可先直接看下具体效果 wpf企业级开发中的几种常见业务场景,绑定部分主要看两个DataGrid即可。
<Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="200"/> <ColumnDefinition/> </Grid.ColumnDefinitions> <HeaderedContentControl> <HeaderedContentControl.Header> <DockPanel> <TextBlock VerticalAlignment="Center" Text="{DynamicResource ProductClassify_Header_Classify}" DockPanel.Dock="Left"/> <StackPanel VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal"> <Button Name="Button_RefreshClassify" Style="{StaticResource RefreshIconButton}" Content="{DynamicResource Common_Refresh}" Command="{Binding TreeVM.RefreshCommand}"/> </StackPanel> </DockPanel> </HeaderedContentControl.Header> <local:ClassifyTreeControl DataContext="{Binding TreeVM}"/> </HeaderedContentControl> <HeaderedContentControl Name="Header_Product" Grid.Column="1"> <HeaderedContentControl.Header> <DockPanel> <TextBlock VerticalAlignment="Center" Text="{DynamicResource ProductList_Header_Product}" DockPanel.Dock="Left"/> <StackPanel VerticalAlignment="Center" HorizontalAlignment="Right" Orientation="Horizontal"> <Button Name="Button_RefreshClassify2" Style="{StaticResource RefreshIconButton}" Content="{DynamicResource Common_Refresh}" Command="{Binding RefreshCommand}"/> <Button Name="Button_AddProduct" Visibility="{Binding AddButtonVisibility}" IsEnabled="{Binding CanAdd}" Style="{StaticResource AddIconButton}" Content="{DynamicResource Common_Add}" Click="Button_AddProduct_Click"/> <Button Name="Button_ModifyProduct" Visibility="{Binding ModifyButtonVisibility}" IsEnabled="{Binding CanModify}" Style="{StaticResource ModifyIconButton}" Content="{DynamicResource Common_Modify}" Click="Button_ModifyProduct_Click"/> <Button Name="Button_DeleteProduct" Visibility="{Binding DeleteButtonVisibility}" Style="{StaticResource DeleteIconButton}" Content="{DynamicResource Common_Delete}" Command="{Binding DeleteCommand}"/> </StackPanel> </DockPanel> </HeaderedContentControl.Header> <DataGrid Name="DataGrid_Product" ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct}" SelectionMode="Single" AutoGenerateColumns="False" IsReadOnly="True"> <DataGrid.Columns> <DataGridTextColumn Header="{DynamicResource Product_Num}" Binding="{Binding Num}" Width="120"/> <DataGridTextColumn Header="{DynamicResource Product_Name_CH}" Binding="{Binding Name_CH}" Width="100"/> <DataGridTextColumn Header="{DynamicResource Product_Name_EN}" Binding="{Binding Name_EN}" Width="100"/> <DataGridTextColumn Header="{DynamicResource Product_Unit}" Binding="{Binding Unit}" Width="50"/> <DataGridTextColumn Header="{DynamicResource Product_Weight}" Binding="{Binding Weight}" Width="100"/> <DataGridTextColumn Header="{DynamicResource Product_Size}" Binding="{Binding Size}" Width="100"/> <DataGridTemplateColumn Header="{DynamicResource Product_CanSale}"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox IsThreeState="False" IsChecked="{Binding CanSale}" IsEnabled="False" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" HorizontalAlignment="Center" Width="50"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn Header="{DynamicResource Product_ReferencePrice}" Binding="{Binding ReferencePrice}" Width="100"/> <DataGridTemplateColumn Header="{DynamicResource Product_StopProduction}"> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <CheckBox IsThreeState="False" IsChecked="{Binding StopProduction}" IsEnabled="False" VerticalContentAlignment="Center" HorizontalContentAlignment="Center" HorizontalAlignment="Center" Width="50"/> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> <DataGridTextColumn Header="{DynamicResource Product_MinSaleCount}" Binding="{Binding MinSaleCount}" Width="80"/> <DataGridTextColumn Header="{DynamicResource Product_PackageCount}" Binding="{Binding PackageCount}" Width="80"/> <DataGridTextColumn Header="{DynamicResource Product_Remark}" Binding="{Binding Remark}" Width="200"/> </DataGrid.Columns> <DataGrid.RowStyle> <Style TargetType="DataGridRow" BasedOn="{StaticResource {x:Type DataGridRow}}"> <Setter Property="ToolTip"> <Setter.Value> <Border Width="100" Height="100" BorderBrush="#FF7DB6D8" BorderThickness="1" Padding="1"> <Border.Resources> <product:PicUrlConverter x:Key="urlConverter"/> </Border.Resources> <Image Source="{Binding ProductPic, Converter={StaticResource urlConverter}}"/> </Border> </Setter.Value> </Setter> <EventSetter Event="MouseDoubleClick" Handler="Button_ModifyProduct_Click"/> </Style> </DataGrid.RowStyle> </DataGrid> </HeaderedContentControl> </Grid>
ViewModel中相关代码
public class ProductListVM : ViewModelBase { public ProductListVM() { LoadData(); TreeVM.SelectedChanged += (s, e) => { LoadData(); if (TreeVM.SelectedModel != null && !string.IsNullOrEmpty(TreeVM.SelectedModel.ID)) CanAdd = true; else CanAdd = false; }; } private ClassifyTreeVM _treeVM; public ClassifyTreeVM TreeVM { get { return _treeVM ?? (_treeVM = new ClassifyTreeVM()); } } private tb_product _selectedProduct; public tb_product SelectedProduct { get { return _selectedProduct; } set { _selectedProduct = value; OnPropertyChanged("SelectedProduct"); if (value != null) CanModify = true; else CanModify = false; RaiseCanExecute(); } } private List<tb_product> _products; public List<tb_product> Products { get { return _products; } set { _products = value; OnPropertyChanged("Products"); } } protected override void LoadData() { if (TreeVM.SelectedModel != null && !string.IsNullOrEmpty(TreeVM.SelectedModel.ID)) Products = XDBContext.tb_product.Where(p => p.ClassifyID == TreeVM.SelectedModel.ID).AsNoTracking().ToList(); else Products = XDBContext.tb_product.AsNoTracking().ToList(); } }
public class ClassifyTreeVM : ViewModelBase { public ClassifyTreeVM() { LoadData(); } private BindingList<TB_ClassifyTreeModel> _classifyModels; public BindingList<TB_ClassifyTreeModel> ClassifyModels { get { return _classifyModels; } set { _classifyModels = value; OnPropertyChanged("ClassifyModels"); } } public event EventHandler SelectedChanged; private TB_ClassifyTreeModel _selectedModel; public TB_ClassifyTreeModel SelectedModel { get { return _selectedModel; } set { _selectedModel = value; OnPropertyChanged("SelectedModeld"); if (SelectedChanged != null) SelectedChanged(SelectedModel, null); } } protected override void LoadData() { ClassifyModels = new BindingList<TB_ClassifyTreeModel>(); ClassifyModels.Add(new TB_ClassifyTreeModel(XDBContext) { ClassifyName = "All", ID = string.Empty }); } }
关于主从结构编辑的保存,UI绑定就是一个主表model(包含从表集合),没什么特别的。只是在保存的时候一起保存从表信息即可(从表中可能有增删改,具体如何操作取决于数据操作层,数据少懒惰点的做法可以一股脑先将相关从表记录全删掉然后添加,这样就不用区分哪些记录是删除的、哪些是修改的及哪些是新添加的;通常有时候会在model中设计一个状态属性,以方便区分model的状态)