65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (15投票s)

2016 年 8 月 17 日

CPOL

5分钟阅读

viewsIcon

36169

downloadIcon

2463

基于 MVVM 模式实现的通用 CRUD 控件

引言

本文描述了WPF CrudControl的用法和实现细节,它是一个基于MVVM模式实现的通用CRUD控件。该控件抽象了UI和业务逻辑,为完整的CRUD操作实现奠定了基础。在入门文章中,我们将解释如何使用WPF CrudControl

问题定义

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

UI线框图

由两个主要视图组成,如图1所示。

视图 #1

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

视图 #2

  1. 添加/编辑弹出窗口:包含用于保存实体值的业务相关输入控件的占位符、保存操作以及将更改重置为原始值的操作。

Northwind演示

该解决方案应用于Northwind数据库的两个模块:SuppliersProducts。在本文中,我们将演示Products模块的解决方案,因为它具有更高级的场景。在演示中,我们使用Unity作为IoC/DI容器、MVVMLight工具包和WPF Modern UI库来样式化主窗口和导航。

运行演示

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

解决方案设计

基于MVVM,主要依赖多态和泛型。点击查看完整的设计图。该解决方案目前依赖于Microsoft.Practices.ServiceLocationEntityFramework

1. 核心

Entity类是视图模型层所有基类的核心,业务模型应继承它,如图所示。它执行以下操作:

  • 实现INotifyDataErrorInfo接口,并有一个由AddEditEntityBase类调用的public方法Validate()。它使用ValidationContext应用与Entity属性提供的ValidationAttribute数据注释,因此当绑定的属性中输入不正确的值时会引发错误,并且关联的UI控件将用其错误模板着色。
  • 实现IEditableObject,它备份实体的原始值,以便在触发重置操作时恢复它们。
  • 具有IsSelected属性,用于标识Entity是否从UI中选中以进行任何用途(即删除)。此外,它还具有IsSelectable属性,用于绑定以根据业务逻辑标识Entity是否可选择。

泛型存储库接口IRepository<Entity>Entity类型约束。在此解决方案中,IRepository<Entity>IUnitOfWork的实现需要在实例化期间提供DbContext对象。

2. 视图层

抽象视图由五个部分组成,如下图所示。

主要的抽象视图是GenericCrudControl,它包含ListingSortingPagerSearchCriteriaContainer的XAML部分。AddEditPopupWindowSearchCriteriaContainer都有一个ContentControl来容纳业务控件。

用户控件GenericCrudControl使用DataGrid控件列出数据并加载业务列,这些列通过一个公开的、基于集合的依赖属性CustomDataGridColumn(继承自基类DataGridColumn)提供。它根据ColumnType生成元素。它提供两种类型的列:默认类型为TextColumn,以及作为DataTemplateTemplateColumn类型。

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应用于NorthwindProduct模块的结果。

第三方实现

  • INotifyDataErrorInfo
  • IEditableObject
  • ObservableObject
  • RelayCommand<T>
  • RelayCommand
  • 动态排序
  • EnumToItemsSource

结论

使用WPF CrudControl可以大大提高直接CRUD操作的生产力。它所需的编码工作相对最少,同时仍然可以自定义其行为。

您可以在此存储库中增强WPF CrudControl,并通过此NuGet包直接使用它。

© . All rights reserved.