WPF通用CRUD控件实现指南

在现代软件开发中,CRUD(创建、读取、更新、删除)操作是应用程序中最常见的功能之一。为了提高开发效率和代码复用性,本文将介绍如何使用WPF(Windows Presentation Foundation)实现一个基于MVVM(Model-View-ViewModel)模式的通用CRUD控件。

问题定义

开发一个可复用的控件,能够处理所有CRUD操作及其相关方面(如验证和重置),并便于开发具有最少代码的查找数据管理模块。

UI线框图

UI线框图包含两个主要视图,如图1所示:

视图#1:

  • 搜索条件:包含业务相关的搜索条件控件、搜索操作和重置操作的占位符。
  • 排序:包含用于业务属性的排序组合框和排序方向组合框。
  • 列表、添加、编辑和删除操作:DataGrid用于列出数据,包括以下列:
    • 复选框列:用于选择行进行删除。
    • 业务相关列。
    • 编辑操作:填充弹出窗口,绑定到选定的实体。
    • 添加和删除操作:添加操作填充弹出窗口,绑定到新实体;删除操作删除选定的实体。
  • 分页器:包含下一页/上一页操作、当前页码、总记录数和页面大小组合框。

视图#2:

  • 添加/编辑弹出窗口:包含业务相关输入控件的占位符,这些控件将保存实体值,保存操作和重置更改为原始值的操作。

Northwind演示

该解决方案应用于Northwind数据库的两个模块:供应商和产品。本文将以产品模块为例进行演示,因为它具有更高级的场景。在演示中,使用了Unity作为IoC/DI容器,MVVMLight工具包和WPFModern UI库来设计主窗口和导航。

要运行演示,请执行以下步骤:

  • 使用NuGet包管理器恢复包。
  • 安装SQL Server LocalDB。

解决方案设计

基于MVVM,主要依赖于多态性和泛型。点击查看完整的设计图。

解决方案目前依赖于Microsoft.Practices.ServiceLocation和EntityFramework。

1. 核心:

  • Entity类是所有视图模型层基类的核心,业务模型应继承自该类。它执行以下操作:
    • 实现INotifyDataErrorInfo接口,具有一个public方法Validate(),由AddEditEntityBase类调用。它使用ValidationAttribute数据注释,这些注释与Entity的属性一起使用ValidationContext,因此当输入的值不正确时会触发错误,并将关联的UI控件用其错误模板着色。
    • 实现IEditableObject,备份实体的原始值以在重置操作触发时恢复它们。
    • 具有IsSelected属性,用于标识从UI中选择的实体以用于任何用途(例如删除)。它还具有IsSelectable属性,用于绑定以确定基于业务逻辑实体是否可选。
  • 泛型仓库接口IRepository受Entity类型的约束。在本解决方案中,IRepository和IUnitOfWork的实现需要DbContext对象在它们的实例化过程中。

2. 视图层:

  • 抽象视图由以下五个部分组成:
    • 主抽象视图是GenericCrudControl,包含XAML部分的Listing、Sorting、Pager和SearchCriteriaContainer。AddEditPopupWindow和SearchCriteriaContainer具有ContentControl以持有业务控件
    • 用户控件GenericCrudControl使用DataGrid控件列出数据并加载业务列,这些列是通过公开的基于集合的依赖属性提供的,类型为CustomDataGridColumn,它继承自基类DataGridColumn。它根据ColumnType生成元素。它提供两种类型的列,TextColumn作为默认类型和TemplateColumn类型作为DataTemplate。
    • SortingProperties是一个基于集合的依赖属性,类型为SortingProperty,用于提供业务排序属性,并绑定到Sorting-By组合框。它具有一个名为PropertyPath的属性,用于标识用于生成动态IOrderedQueryable的路径,基于当前选定的值。
    • 所有UI CRUD控件样式都可以使用公开的依赖属性类型Style进行自定义。

使用示例

以下XAML片段来自ProductList.xaml用户控件,展示了GenericCrudControl的使用:

<crud:GenericCRUDControl> <crud:GenericCRUDControl.SortingProperties> <crud:SortingProperty DisplayName="产品名称" PropertyPath="ProductName"></crud:SortingProperty> <crud:SortingProperty DisplayName="类别" PropertyPath="Category.CategoryName"></crud:SortingProperty> <crud:SortingProperty DisplayName="供应商" PropertyPath="Supplier.ContactName"></crud:SortingProperty> </crud:GenericCRUDControl.SortingProperties> <crud:GenericCRUDControl.Columns> <crud:CustomDataGridColumn Header="类别名称" BindingExpression="Category.CategoryName"></crud:CustomDataGridColumn> <crud:CustomDataGridColumn Header="产品名称" BindingExpression="ProductName"></crud:CustomDataGridColumn> <crud:CustomDataGridColumn ColumnType="TemplateColumn" Header="库存"> <crud:CustomDataGridColumn.DataGridColumnTemplate> <DataTemplate> <ProgressBar Maximum="150" Value="{Binding UnitsInStock}"></ProgressBar> </DataTemplate> </crud:CustomDataGridColumn.DataGridColumnTemplate> </crud:CustomDataGridColumn> <crud:CustomDataGridColumn Header="供应商名称" BindingExpression="Supplier.ContactName" Width="*"></crud:CustomDataGridColumn> </crud:GenericCRUDControl.Columns> </crud:GenericCRUDControl>

视图模型层

包含解决方案的骨干逻辑,如图示所示:

public class ProductsListViewModel : GenericCRUDBase<Product> { public ProductsListViewModel(ProductsSearchViewModel productsSearchViewModel, ProductAddEditViewModel productAddEditViewModel) : base(productsSearchViewModel, productAddEditViewModel) { ListingIncludes = new Expression<Func<Product, object>>[] { p => p.Category, p => p.Supplier }; } }

它需要SearchCriteriaBase和AddEditEntityBase的具体对象在构造函数中。它负责:

  • 订阅更改标准事件以进行搜索和分页操作
  • 使用搜索、分页和排序加载数据
  • 使用DialogService用新实体填充添加弹出窗口
  • 使用DialogService用选定的实体填充编辑弹出窗口
  • 删除选定的实体

ListingIncludes是一个lambda表达式数组,引用数据检索中需要包含的导航属性。

AddEditEntityBase是一个基视图模型类,负责保存Entity,重置Entity更改,并在成功保存后关闭其关联窗口,因为它继承了基类PopupViewModelBase,该类在DialogService中定义了一个委托CloseAssociatedWindow。

它具有ContentControl,将持有WPF引擎基于指定DataTemplate解析的具体视图。

SearchCriteriaBase有两个虚拟方法,在具体类中被重写:

  • GetSearchCriteria()方法,返回基于输入的搜索条件的Expression>。
  • ResetSearchCriteria()方法,重置输入控件。

委托

有一些委托用于在抽象逻辑之前/之后注入业务逻辑,例如:

  • 在GenericCrudBase中:
    • PostDataRetrievalDelegate:在从数据库检索数据后调用的委托,以便根据业务逻辑操作数据(例如,更新某些属性)。
    • PreAddEditDelegate:在填充添加/编辑弹出窗口之前调用的委托,以便根据业务逻辑准备其数据(例如,基于Entity值重新绑定组合框的ItemSource)。
    • PostAddEditDelegate:在成功保存Entity后调用的委托,以应用特定的业务逻辑。

结果

以下截图展示了将WPFCrudControl应用于Northwind的Product模块的结果。

第三方实现

  • INotifyDataErrorInfo
  • IEditableObject
  • ObservableObject
  • RelayCommand
  • RelayCommand
  • 动态排序
  • EnumToItemsSource
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485