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

WPF DataGrid 控件中的动态列(第 2 部分)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.83/5 (8投票s)

2015 年 10 月 27 日

CPOL

5分钟阅读

viewsIcon

28115

downloadIcon

1047

WPF DataGrid 控件中的动态列(第二部分),使用交互请求框架纠正架构约束

引言

本文扩展了第一篇“WPF DataGrid 控件中的动态列(第一部分)”文章。在本文中,我将介绍如何为 WPF DataGrid 控件添加动态列。第二部分侧重于我在第一部分中违反的架构约束。解决方案基于我之前的文章“ViewModel 到 View 的交互请求”。它将使用该文章中已介绍的“小型应用程序框架”中的功能。

下一篇文章“MVVM 数据验证”通过一个数据验证的基类扩展了应用程序框架,并展示了如何根据数据规则验证输入到数据网格中的值。

背景

正如引言中所述,本文旨在修复一个架构约束。那么,需要修复的问题是什么?在原始解决方案中,我将动态列集合放在 MainViewModel 中以最小化代码复杂性。

由于这个架构决策,我不得不在 ViewModel 程序集中引用 PresentationFramework 库。这意味着用户界面层的一部分悄悄地侵入了视图模型层。这违反了分层架构模式。

Using the Code

代码更改

下一个类图显示了旧的情况,其中视图模型包含一个包含数据网格列的可观察集合。现在,数据网格列集合已被移除。库引用已清理,并且已删除 PresentationCorePresentationFrameworkSystem.XamlWindowsBase 的引用。

ObjectTag 类也已移至 GUI 层。此类用于创建一个名为“Tag”的依赖项属性。此属性允许实例(在本例中为角色行)与相应的网格列进行标记。

小型应用程序框架

这个库在我关于 ViewModel 到 View 的交互请求 的文章中进行了介绍。它描述了一种类似的情况,其中视图模型层的逻辑需要与 GUI 层解耦。前一篇文章描述了如何处理文件打开和保存消息框,在这里我使用相同的机制来管理数据网格中的动态列。如果您不熟悉,建议您先阅读那篇文章。

数据模型

数据模型保持不变。

ViewModel

在新系统中,列由网格控件管理。列的变更是通过视图模型向 GUI 层发出的通知来请求的。有四种通知类型:

  1. 添加文本列,用于将文本列添加到网格(名字和姓氏列)
  2. 添加动态列,用于将新角色添加到网格
  3. 更改动态列,用于更新角色列(角色名称更改)
  4. 删除动态列,用于从网格中删除角色

DataColumnService 类包含交互请求类,用于将通知从逻辑层传递到 GUI 层。

MainViewModel 包含数据库上下文和初始化网格的逻辑。它订阅 DataSet 的数据事件,并通过交互请求通知将数据操作事件(添加、删除和修改角色)传递给 GUI 层。

应用程序

应用程序层中的 MainWindow 具有与先前版本相同的功能和实现。与先前解决方案的区别在于,已移除附加行为 DataGridColumnsBehavior。该类负责监视角色数据视图,并在角色更改时更新网格。

新版本使用 Interaction 库中的 Triggers 来解决问题。有四种触发器:一种用于插入文本列,三种用于插入、删除和修改角色列。每种触发器都调用一个动作实现,该实现执行实际任务。所有动作都与主窗口相关联。每个动作都可以访问主窗口及其子控件,因此它可以修改网格的列集合。

另一个触发器是事件触发器,它订阅窗口的加载事件并调用视图模型的 OnLoad 方法。此调用用于初始化网格列。

业务逻辑

应用程序初始化

应用程序通过将 Loaded 事件路由到视图模型来初始化,调用 OnLoaded 方法。第一个任务是初始化标准网格列,即名字和姓氏列。第二个任务是添加现有的角色列。

public void OnLoaded()
{
    this.GenerateDefaultColumns();
    this.InitializeRolesColumns();
}

private void GenerateDefaultColumns()
{
    this.AddTextColumn("First Name", "FirstName");
    this.AddTextColumn("Last Name", "LastName");
}

private void InitializeRolesColumns()
{
    foreach (var role in this.databaseContext.DataSet.Role)
    {
        this.AddRoleColumn(role);
    }
}

private void AddTextColumn(string header, string binding)
{
    var addTextColumnNotification = new AddTextColumnNotification
    {
        Header = header,
        Binding = binding
    };
    DataColumnService.Instance.AddTextColumn.Raise(addTextColumnNotification);
}

private void AddRoleColumn(UserRoleDataSet.RoleRow role)
{
    var notification = new AddDynamicColumnNotification { Role = role };
    DataColumnService.Instance.AddDynamicColumn.Raise(notification);
}

交互请求框架负责执行 AddTextColumnAction 实例以插入名字和姓氏列。

public class AddTextColumnAction : TriggerActionBase<AddTextColumnNotification>
{
    protected override void ExecuteAction()
    {
        var mainWindow = this.AssociatedObject as MainWindow;
        if (mainWindow != null)
        {
            mainWindow.DataGridUsers.Columns.Add(
                new DataGridTextColumn
                    {
                        Header = this.Notification.Header,
                        Binding = new Binding(this.Notification.Binding)
                    });
        }
    }
}

插入角色列

新角色列的添加方式与文本列相同。框架调用 AddDynamicColumnAction 类,该类创建列并分配绑定。绑定包括:

  • Converter,一个值转换器。它处理将角色分配给用户
  • RelativeSource,包含复选框控件的 DataGridCell
  • Path,指向绑定的对象列
  • Mode,双向绑定,用于与网格单元格控件进行更新

该列是 DataGridCheckBoxColumn,它使用角色、绑定和元素样式作为标题创建。元素样式可防止在新项目行时显示复选框(参见第一部分)。

最后,将该列添加到网格控件的列列表中。

public class AddDynamicColumnAction : TriggerActionBase<AddDynamicColumnNotification>
{
    protected override void ExecuteAction()
    {
        var resourceDictionary = 
        ResourceDictionaryResolver.GetResourceDictionary("Styles.xaml");
        var userRoleValueConverter = 
        resourceDictionary["UserRoleValueConverter"] as IValueConverter;
        var checkBoxColumnStyle = resourceDictionary["CheckBoxColumnStyle"] as Style;
        var binding = new Binding
                          {
                              Converter = userRoleValueConverter,
                              RelativeSource =
                                  new RelativeSource
                                  (RelativeSourceMode.FindAncestor, typeof(DataGridCell), 1),
                              Path = new PropertyPath("."),
                              Mode = BindingMode.TwoWay
                          };
        var dataGridCheckBoxColumn = new DataGridCheckBoxColumn
                                         {
                                             Header = this.Notification.Role.Name,
                                             Binding = binding,
                                             IsThreeState = false,
                                             CanUserSort = false,
                                             ElementStyle = checkBoxColumnStyle,
                                         };
        ObjectTag.SetTag(dataGridCheckBoxColumn, this.Notification.Role);
        var mainWindow = this.AssociatedObject as MainWindow;
        if (mainWindow != null)
        {
            mainWindow.DataGridUsers.Columns.Add(dataGridCheckBoxColumn);
        }
    }
}

角色分配

角色分配在 UserRoleValueConverter 中完成。逻辑相同,并在 上一篇文章 中进行了说明。

关注点

我通常会遇到的一个初步反应是:“为什么费事?旧的解决方案也能工作,所以就让它这样吧”。我明白这篇文章具有巨大的软件布道潜力。但我认为,一个具有清晰分层的架构更容易管理。它值得我们为此付出额外的努力。

致谢

我谨此感谢我的同事兼朋友 Thomas Britschgi 的建议和对本文的审阅。

© . All rights reserved.