通用 WPF CRUD 控件(解决方案设计)






4.94/5 (15投票s)
基于 MVVM 模式实现的通用 CRUD 控件
引言
本文描述了WPF CrudControl
的用法和实现细节,它是一个基于MVVM模式实现的通用CRUD控件。该控件抽象了UI和业务逻辑,为完整的CRUD操作实现奠定了基础。在入门文章中,我们将解释如何使用WPF CrudControl
。
问题定义
开发一个可重用控件,用于处理所有CRUD操作及相关方面(如验证和重置),并以最少的代码开发查找数据管理模块。
UI线框图
由两个主要视图组成,如图1所示。
视图 #1
- 搜索条件:包含业务相关的搜索条件控件、搜索操作和重置操作的占位符。
- 排序:包含用于业务属性的排序组合框和排序方向组合框。
- 列表、添加、编辑和删除操作
- DataGrid:用于列出数据,包含以下列:
- 一个复选框列:用于选择要删除的行
- 业务相关列
- 编辑操作:弹出绑定到选定实体的窗口。
- 添加和删除操作
- 添加操作:弹出绑定到新实体的窗口。
- 删除操作:删除选定的实体。
- DataGrid:用于列出数据,包含以下列:
- 分页器:包含下一页/上一页操作、当前页码、总记录数和页面大小组合框。
视图 #2
Northwind演示
该解决方案应用于Northwind
数据库的两个模块:Suppliers
和Products
。在本文中,我们将演示Products
模块的解决方案,因为它具有更高级的场景。在演示中,我们使用Unity作为IoC/DI容器、MVVMLight工具包和WPF Modern UI库来样式化主窗口和导航。
运行演示
- 使用NuGet包管理器恢复包。
- 安装SQL Server LocalDB
解决方案设计
基于MVVM,主要依赖多态和泛型。点击查看完整的设计图。该解决方案目前依赖于Microsoft.Practices.ServiceLocation
和EntityFramework
。
1. 核心
2. 视图层
抽象视图由五个部分组成,如下图所示。
主要的抽象视图是GenericCrudControl
,它包含Listing
、Sorting
、Pager
和SearchCriteriaContainer
的XAML部分。AddEditPopupWindow
和SearchCriteriaContainer
都有一个ContentControl
来容纳业务控件。
用户控件GenericCrudControl
使用DataGrid
控件列出数据并加载业务列,这些列通过一个公开的、基于集合的依赖属性CustomDataGridColumn
(继承自基类DataGridColumn
)提供。它根据ColumnType
生成元素。它提供两种类型的列:默认类型为TextColumn
,以及作为DataTemplate
的TemplateColumn
类型。
SortingProperties
是一个类型为SortingProperty
的基于集合的依赖属性,用于提供业务排序属性,它绑定到“排序依据”组合框。它有一个名为PropertyPath
的属性,用于标识用于根据当前选定值生成动态IOrderedQueryable<Entity>
的路径。
所有UI CRUD控件的样式都可以通过公开的Style
类型依赖属性进行自定义。
用法
以下XAML代码片段来自ProductList.xaml用户控件,演示了GenericCrudControl
的用法。
<crud:GenericCRUDControl>
<crud:GenericCRUDControl.SortingProperties>
<crud:SortingProperty DisplayName="Product Name" PropertyPath="ProductName">
</crud:SortingProperty>
<crud:SortingProperty DisplayName="Category" PropertyPath="Category.CategoryName">
</crud:SortingProperty>
<crud:SortingProperty DisplayName="Supplier" PropertyPath="Supplier.ContactName">
</crud:SortingProperty>
</crud:GenericCRUDControl.SortingProperties>
<crud:GenericCRUDControl.Columns>
<crud:CustomDataGridColumn Header="Category Name" BindingExpression="Category.CategoryName">
</crud:CustomDataGridColumn>
<crud:CustomDataGridColumn Header="Product Name" BindingExpression="ProductName">
</crud:CustomDataGridColumn>
<crud:CustomDataGridColumn ColumnType="TemplateColumn" Header="Stock">
<crud:CustomDataGridColumn.DataGridColumnTemplate>
<DataTemplate>
<ProgressBar Maximum="150" Value="{Binding UnitsInStock}"></ProgressBar>
</DataTemplate>
</crud:CustomDataGridColumn.DataGridColumnTemplate>
</crud:CustomDataGridColumn>
<crud:CustomDataGridColumn Header="Supplier Name"
BindingExpression="Supplier.ContactName" Width="*"></crud:CustomDataGridColumn>
</crud:GenericCRUDControl.Columns>
</crud:GenericCRUDControl>
3. 视图模型层
包含解决方案的核心逻辑,如下图所示。
GenericCRUDBase<Entity>
是控制器类,由您的业务视图模型继承,因此用法如下:
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<Entity>
和AddEditEntityBase<Entity>
具体对象。它负责:
- 订阅搜索和分页操作的更改条件事件。
- 加载数据,包括搜索、分页和排序。
- 使用
DialogService
弹出包含新实体的“添加”弹出窗口。 - 使用
DialogService
弹出包含选定实体的“编辑”弹出窗口。 - 删除选定的实体。
ListingIncludes
是lambda表达式数组,引用数据检索中需要包含的导航属性。
AddEditEntityBase<Entity>
是一个基视图模型类,负责保存Entity
、重置Entity
更改,并在成功保存后关闭其关联窗口,因为它继承了基类PopupViewModelBase
,该基类在DialogService
中定义了委托CloseAssociatedWindow
。
它有一个ContentControl
,用于容纳由WPF引擎根据指定的DataTemplate
解析的具体视图。
SearchCriteriaBase<Entity>
有两个虚拟方法,在具体类中被重写。
GetSearchCriteria()
方法根据输入的搜索条件返回Expression<Func<Entity, bool>>
。ResetSearchCriteria()
方法重置输入控件。
委托(Delegates)
有一些委托可以在抽象逻辑之前/之后注入业务逻辑,例如:
在GenericCrudBase<Entity>
中
PostDataRetrievalDelegate
:一个委托,在从数据库检索数据后调用,用于根据业务逻辑操作数据(即更新某些属性)。PreAddEditDelegate
:一个委托,在弹出添加/编辑窗口之前调用,以便根据业务逻辑准备其数据(即,根据Entity
值重新绑定组合框的ItemSource
)。PostAddEditDelegate
:一个委托,在成功保存Entity
后调用,以应用特定的业务逻辑。
结果
以下截图演示了将WPF CrudControl
应用于Northwind
的Product
模块的结果。
第三方实现
INotifyDataErrorInfo
IEditableObject
ObservableObject
RelayCommand<T>
RelayCommand
动态排序
EnumToItemsSource
结论
使用WPF CrudControl
可以大大提高直接CRUD操作的生产力。它所需的编码工作相对最少,同时仍然可以自定义其行为。