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






4.83/5 (8投票s)
WPF DataGrid 控件中的动态列(第二部分),使用交互请求框架纠正架构约束
引言
本文扩展了第一篇“WPF DataGrid 控件中的动态列(第一部分)”文章。在本文中,我将介绍如何为 WPF DataGrid
控件添加动态列。第二部分侧重于我在第一部分中违反的架构约束。解决方案基于我之前的文章“ViewModel 到 View 的交互请求”。它将使用该文章中已介绍的“小型应用程序框架”中的功能。
下一篇文章“MVVM 数据验证”通过一个数据验证的基类扩展了应用程序框架,并展示了如何根据数据规则验证输入到数据网格中的值。
背景
正如引言中所述,本文旨在修复一个架构约束。那么,需要修复的问题是什么?在原始解决方案中,我将动态列集合放在 MainViewModel
中以最小化代码复杂性。
由于这个架构决策,我不得不在 ViewModel
程序集中引用 PresentationFramework
库。这意味着用户界面层的一部分悄悄地侵入了视图模型层。这违反了分层架构模式。
Using the Code
代码更改
下一个类图显示了旧的情况,其中视图模型包含一个包含数据网格列的可观察集合。现在,数据网格列集合已被移除。库引用已清理,并且已删除 PresentationCore
、PresentationFramework
、System.Xaml 和 WindowsBase
的引用。
ObjectTag
类也已移至 GUI 层。此类用于创建一个名为“Tag
”的依赖项属性。此属性允许实例(在本例中为角色行)与相应的网格列进行标记。
小型应用程序框架
这个库在我关于 ViewModel 到 View 的交互请求 的文章中进行了介绍。它描述了一种类似的情况,其中视图模型层的逻辑需要与 GUI 层解耦。前一篇文章描述了如何处理文件打开和保存消息框,在这里我使用相同的机制来管理数据网格中的动态列。如果您不熟悉,建议您先阅读那篇文章。
数据模型
数据模型保持不变。
ViewModel
在新系统中,列由网格控件管理。列的变更是通过视图模型向 GUI 层发出的通知来请求的。有四种通知类型:
- 添加文本列,用于将文本列添加到网格(名字和姓氏列)
- 添加动态列,用于将新角色添加到网格
- 更改动态列,用于更新角色列(角色名称更改)
- 删除动态列,用于从网格中删除角色
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 的建议和对本文的审阅。