精通
英语
和
开源
,
擅长
开发
与
培训
,
胸怀四海
第一信赖
锐英源精品原创,禁止全文或局部转载,禁止任何形式的非法使用,侵权必究。点名“简易百科”和闲暇巴盗用锐英源原创内容。
动态列在动态表显示时非常有用,常见的DataGrid显示时,xaml里静态指定了列名和列属性,而动态列是通过代码加列,在列名是动态时,比较有用。本例子来自于codeproject,经过锐英源的详细翻译,供大家学习,有些细节因为有闲暇巴盗用不能公布,有兴趣的请关注联系锐英源,锐英源有专业的开源企业服务。
Datagrid控件非常适合显示存储在表格中的数据。数据库表中的一行等于数据网格中的一行。当数据存储在多个表中时,比如表 A 和 B,并且行 A 与表 B 中的行具有一对多(也称为 1:N、父子或主从)关系,则行 A 可以引用表 B 中的多行。这种类型的数据可以在主从数据视图类型中显示。另一种类型的数据关系是多对多类型(也称为 N:M 关系)。表A的行可以对表B的行有多个引用。但是除了前面的情况,表B的一行可以被表A的多行引用。
datagrid本文介绍了一种可以在 WPF控件中显示和修改多对多关系的方法。可以通过编辑 A 和/或 B 表的行来添加、删除和修改行和列。
本文分为两部分。在这第一部分中,我将重点介绍处理动态列的解决方案。为了简化解决方案,我打破了架构约束,即顶层的对象不应在较低层中使用(在这种情况下,作为 GUI 层的一部分的网格列,而不是视图模型层)。本文的第二部分修复了这个约束。
示例代码实现了一个用户管理表单,可以在其中管理用户、角色和用户角色分配。角色和用户显示在两个数据网格中。用户角色分配在用户数据网格中完成。因此,此网格具有动态内容,将每个角色显示为单独的复选框列。通过选中相应的复选框来完成用户角色分配。
这个样本的数据模型由一个User和一个Role表组成,一个UserRole表是其他两个表之间的关联表。表中的条目UserRole意味着用户(由其用户 id 引用)具有分配的角色(由角色的 id 引用)。如果某个用户-角色组合没有条目,则意味着相关用户没有分配相应的角色。
数据模型是使用 .NET 实现的DataSet。它是一个具有引用完整性的良好内存数据库,它包含发布数据行的插入、删除和修改的内置通知委托。它的内容可以存储到一个 XML 文件中,在本例中用作持久性机制。
下一个组件图显示了应用程序的分层:
该应用程序是使用 MVVM 设计模式编写的。这意味着主窗口绑定到主视图模型,视图控件绑定到主视图模型的属性。
参考 | 查看控件属性 | ViewModel 属性 |
1 | MainWindow:DataGridRoles.ItemsSource | MainViewModel.Roles |
---|---|---|
2 | MainWindow:DataGridUsers.ItemsSource |
MainViewModel.Users |
3 | MainWindow:DataGridUsers.Column | MainViewModel.UserRoleColumns |
要点1:将数据库角色表绑定到角色数据网格控件
要点2:将数据库用户表绑定到用户数据网格控件
要点 3:将列的可观察集合绑定到用户网格控件的列属性。动态列行为是通过此属性实现的,因为视图模型中的逻辑在此集合中添加和删除列。
数据网格控件的列属性被声明为只读,因此它不能绑定到视图模型属性。这DataGridColumnsBehavior是克服此限制的附加行为。原始文章和来源可以在这里找到。
注:要点。
<Window x:Class="Application.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:attachedBehaviors="clr-namespace:Application.AttachedBehaviors"
xmlns:viewModel="clr-namespace:ViewModel;assembly=ViewModel"
Title="User Administration" Height="350" Width="525"><Window.DataContext>
<viewModel:MainViewModel/>
</Window.DataContext><DockPanel LastChildFill="True">
<ToolBar DockPanel.Dock="Top">
<Button Content="Save" Command="{Binding SaveCommand}"/>
</ToolBar><Grid>
<Grid.RowDefinitions>
<RowDefinition Height="146*"/>
<RowDefinition Height="147*"/>
</Grid.RowDefinitions><GroupBox x:Name="UsersGroupBox"
Grid.Column="0"
Header="User Role Assignment">
<DataGrid x:Name="DataGridUsers"
ItemsSource="{Binding Users}"
attachedBehaviors:DataGridColumnsBehavior.BindableColumns=
"{Binding UserRoleColumns}"
AutoGenerateColumns="False"
EnableRowVirtualization="False"/>
</GroupBox><GroupBox x:Name="RolesGroupBox"
Grid.Row="1" Grid.Column="0"
Header="Roles">
<DataGrid x:Name="DataGridRoles"
ItemsSource="{Binding Roles}"
AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name"
Binding="{Binding Name}"/>
</DataGrid.Columns>
</DataGrid>
</GroupBox>
</Grid>
</DockPanel>
</Window>
数据保存在UserRoleDataSet(Role、User和RoleUser)中的三个表中。Role和User表通过DataView 绑定到数据网格控件。允许修改、插入和删除行以及阻止这些操作。DataView上也可以过滤和排序。数据网格控件可以使用DataView控制数据. 可以在数据网格控件中插入、修改和删除行(网格底部有一个新的项目行,按下删除键时删除行),通过DataView可以直接更新数据。
public class MainViewModel { public MainViewModel() { --- Code omitted --- this.UserRoleColumns = new ObservableCollection<DataGridColumn>();
--- Code omitted --- } public DataView Users { get { return this.dataContext.DataSet.User.DefaultView; } } public DataView Roles { get { return this.dataContext.DataSet.Role.DefaultView; } } public ObservableCollectionUserRoleColumns { get; private set; } }
DataSet可以与数据库连接一起使用,以从 SQL 服务器等存储和检索数据。在这个应用程序中,我使用持久性机制在 XML 文件中存储和检索数据。
每个DataSet表都有一组事件,可用于在数据修改时获得通知。此机制用于在修改角色表时添加、删除和更新动态列。
public class MainViewModel { public MainViewModel() { --- Code omitted --- this.dataContext = DatabaseContext.Instance; this.dataContext.DataSet.Role.RoleRowChanged += this.RoleOnRowChanged; this.dataContext.DataSet.Role.RoleRowDeleted += this.RoleOnRoleRowDeleted; --- Code omitted --- } private void RoleOnRowChanged(object sender, UserRoleDataSet.RoleRowChangeEvent roleRowChangeEvent) { switch (roleRowChangeEvent.Action) { case DataRowAction.Change: this.UpdateRoleColumn(roleRowChangeEvent.Row); break; case DataRowAction.Add: this.AddRoleColumn(roleRowChangeEvent.Row); break; } } private void RoleOnRoleRowDeleted(object sender, UserRoleDataSet.RoleRowChangeEvent roleRowChangeEvent) { if (roleRowChangeEvent.Action == DataRowAction.Delete) { this.DeleteRoleColumn(roleRowChangeEvent.Row); } } }
用户数据网格列定义存储在UserRolesColumns集合中。这意味着默认列,用户的名字和姓氏,也必须在此集合中。DataGridTextColumns为名字和姓氏实例化了两个,单元格内容通过绑定到行的各自字段来绑定到数据行。
public class MainViewModel { public MainViewModel() { this.GenerateDefaultColumns(); --- Code omitted --- } private void GenerateDefaultColumns() { this.UserRoleColumns.Add(new DataGridTextColumn { Header = "First Name", Binding = new Binding("FirstName") }); this.UserRoleColumns.Add(new DataGridTextColumn { Header = "Last Name", Binding = new Binding("LastName") }); } }
动态列处理分为 3 种操作类型:
public class MainViewModel { private void AddRoleColumn(UserRoleDataSet.RoleRow role) { 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 = role.Name, Binding = binding, IsThreeState = false, CanUserSort = false, ElementStyle = checkBoxColumnStyle, }; ObjectTag.SetTag(dataGridCheckBoxColumn, role); this.UserRoleColumns.Add(dataGridCheckBoxColumn); } private void UpdateRoleColumn(UserRoleDataSet.RoleRow role) { if (role != null) { foreach (var userRoleColumn in this.UserRoleColumns) { var roleScan = ColumnTag.GetTag(userRoleColumn) as UserRoleDataSet.RoleRow; if (roleScan == role) { userRoleColumn.Header = role.Name; break; } } } } private void DeleteRoleColumn(UserRoleDataSet.RoleRow role) { if (role != null) { foreach (var userRoleColumn in this.UserRoleColumns) { var roleScan = ColumnTag.GetTag(userRoleColumn) as UserRoleDataSet.RoleRow; if (roleScan == role) { this.UserRoleColumns.Remove(userRoleColumn); break; } } } } }
将DataGridCheckBoxColumn复选框控件绑定到它正在显示的行中数据的(可为空的)布尔属性。在这种情况下,它将是用户数据行中的布尔属性,表示用户到角色分配。由于定义中没有这样的属性,因此UserTable必须实施另一种解决方案。不是绑定到复选框控件,而是实例化一个值转换器并将其绑定到DataGridCell将包含该控件的CheckBox控件。上面显示的方法中的Binding定义AddRoleColumn包含对值转换器的赋值。绑定控件的相对源设置为DataGridCell,作为CheckBox控件的祖先找到(绑定在CheckBox级别上定义)。
Convert每次DataGrid最初修改单元格或失去焦点时,都会调用值转换器的方法。在这两种情况下,都会检索用户和角色角色并返回转换结果(如果用户具有分配的角色)。用户行从DataGridCell's中获取DataContext,其中包含DataRowView在其Row属性中具有用户行的实例。角色是从ColumnTag添加时分配给列的 中检索的。
CheckBox控件的事件Checked在DataGridCell处于编辑模式时被订阅,在不处于编辑模式时被取消订阅。
public class UserRoleValueConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { bool result = false; var dataGridCell = value as DataGridCell; if (dataGridCell != null) { var dataRowView = dataGridCell.DataContext as DataRowView; if (dataRowView != null) { var user = dataRowView.Row as UserRoleDataSet.UserRow; var role = ColumnTag.GetTag(dataGridCell.Column) as UserRoleDataSet.RoleRow; if (user != null && role != null) { var checkBox = dataGridCell.Content as CheckBox; if (checkBox != null) { if (dataGridCell.IsEditing) { checkBox.Checked += this.CheckBoxOnChecked; } else { checkBox.Checked -= this.CheckBoxOnChecked; } } result = DatabaseContext.Instance.DataSet.UserRole.Any( x => x.UserRow == user && x.RoleRow == role); } } } return result; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); }
CheckedBoxOnChecked每当修改复选框状态时,都会调用该方法。逻辑搜索CheckBox'sDataGridCell并获取属于它的用户和角色实例。它将根据CheckBox.IsChecked状态以及 UserRoleRow是否已经存在来添加或删除用户角色条目。
private void CheckBoxOnChecked(object sender, RoutedEventArgs routedEventArgs) { var checkBox = sender as CheckBox; var dataGridCell = ControlHelper.FindVisualParent(checkBox); if (dataGridCell != null) { var dataRowView = dataGridCell.DataContext as DataRowView; if (checkBox != null && dataRowView != null) { var user = dataRowView.Row as UserRoleDataSet.UserRow; var role = ObjectTag.GetTag(dataGridCell.Column) as UserRoleDataSet.RoleRow; if (user != null && role != null) { if (checkBox.IsChecked == true && DatabaseContext.Instance.DataSet.UserRole.Any( x => x.UserRow == user && x.RoleRow == role) == false) { DatabaseContext.Instance.DataSet.UserRole.AddUserRoleRow(user, role); } else { var userRole = DatabaseContext.Instance.DataSet.UserRole.FirstOrDefault( x => x.UserRow == user && x.RoleRow == role); if (userRole != null) { userRole.Delete(); } } } } } } }
作为额外的奖励(并防止额外的状态逻辑),我添加了CheckBox控件未显示在用户数据网格新项目行中的功能。DataGridCheckBoxColumn必须修改样式,并且必须设置 Visibility的标志,CheckBox具体取决于DataGridCell. 如果数据行是新的项目行,那么它有一个NewItemPlaceHolder. 转换器用于获取此信息并将其映射到CheckBox的Visibility标志。可以在此处找到此问题的解决方案。
CheckBox样式在应用程序层的Style.xaml文件中定义。它被附加到合并字典中的应用程序资源中。ViewModel 层中称为的帮助器类ResourceDictionaryResolver遍历合并字典容器中的字典并搜索具有给定名称的字典(名称在字典Source属性中)。然后可以通过其键名从资源字典中提取复选框样式。
标准 WPFDataColumn不允许对象标记。对象标记是允许将对象标记到控件的功能。这可以在控件可用但无法使用标准应用程序逻辑访问对象的情况下使用。在此示例中,可用控件位于CheckBox中DataGridCell,所需对象是与列对应的角色。该角色被标记到该列,并且可以在以后检索。ObjectTag本身是一个DependencyProperty可以附加到从 DependencyObject 派生的任何类型的控件。可以在此处找到此问题的解决方案。
作为第二个小奖励,当数据被修改时,我实现了数据库保存。该命令绑定到保存工具栏按钮,并检查数据库上下文的更改。具有测试其内容以进行修改的DataSet内置功能。当 DataSet发生变化时该按钮被启用,否则它被禁用。Command的CanExecuteChanged在订阅时连接CommandManager.RequerySuggested。然后在应用程序主线程上下文中空闲时通过调用CanExecute的方法自动检查按钮状态。
本文展示了 WPFDataGrid控件的动态列处理的实现。这是一个直接的 MVVM 实现,其中动态列处理在视图模型层中完成。此解决方案的缺点是 GUI 组件溢出到 ViewModel 层。在下一篇文章中,我将展示一个实现相同应用程序的解决方案,但将业务逻辑和 GUI 控件更严格地分离到各自的层中。