WPF DataGrid动态列

#程序员##万众创业##IT教育##.net##IT#

锐英源精品原创,禁止全文或局部转载,禁止任何形式的非法使用,侵权必究。点名“简易百科”和闲暇巴盗用锐英源原创内容。


动态列在动态表显示时非常有用,常见的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 文件中,在本例中用作持久性机制。

组件和类图

下一个组件图显示了应用程序的分层:

  • 应用程序:包含 GUI 元素
  • ViewModel:包含业务逻辑
  • DataModel:包含数据定义和持久性

应用

  • MainWindow: GUI 定义,用 XAML 编写
  • 【闲暇巴防盗用,具体内容联系作者】
  • UserRoleValueConverter: 定义当用户选中或取消选中复选框时会发生什么的值转换器实现

视图模型

  • MainViewModel:包含视图的显示数据表属性和动态列处理的数据逻辑
  • ColumnTag: 用于将对象标记到派生自 的实例的附加属性DependencyObject,在这种情况下DataGridColumn

数据模型

  • DatabaseContext: 单例实例,包含UserRoleDataSet
  • UserRoleDataSet:数据库实现,基于DataSet

执行

数据绑定

该应用程序是使用 MVVM 设计模式编写的。这意味着主窗口绑定到主视图模型,视图控件绑定到主视图模型的属性。

参考

查看控件属性

ViewModel 属性

1

MainWindow:DataGridRoles.ItemsSource

MainViewModel.Roles

2

MainWindow:DataGridUsers.ItemsSource

MainViewModel.Users

3

MainWindow:DataGridUsers.Column

MainViewModel.UserRoleColumns

要点1:将数据库角色表绑定到角色数据网格控件

要点2:将数据库用户表绑定到用户数据网格控件

要点 3:将列的可观察集合绑定到用户网格控件的列属性。动态列行为是通过此属性实现的,因为视图模型中的逻辑在此集合中添加和删除列。

数据网格控件的列属性被声明为只读,因此它不能绑定到视图模型属性。这DataGridColumnsBehavior是克服此限制的附加行为。原始文章和来源可以在这里找到。

注:要点。

              
               

  

  
               

  

  

数据处理

数据保存在UserRoleDataSet(Role、User和RoleUser)中的三个表中。Role和User表通过DataView 绑定到数据网格控件。允许修改、插入和删除行以及阻止这些操作。DataView上也可以过滤和排序。数据网格控件可以使用DataView控制数据. 可以在数据网格控件中插入、修改和删除行(网格底部有一个新的项目行,按下删除键时删除行),通过DataView可以直接更新数据。

C#

public class MainViewModel
{
    public MainViewModel()
    {
        --- Code omitted ---
        this.UserRoleColumns = new ObservableCollection();  
        --- Code omitted ---
    }

    public DataView Users
    {
        get
        {
            return this.dataContext.DataSet.User.DefaultView;
        }
    }

    public DataView Roles
    {
        get
        {
            return this.dataContext.DataSet.Role.DefaultView;
        }
    }

    public ObservableCollection UserRoleColumns { get; private set; }
}

DataSet可以与数据库连接一起使用,以从 SQL 服务器等存储和检索数据。在这个应用程序中,我使用持久性机制在 XML 文件中存储和检索数据。

每个DataSet表都有一组事件,可用于在数据修改时获得通知。此机制用于在修改角色表时添加、删除和更新动态列。

C#

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为名字和姓氏实例化了两个,单元格内容通过绑定到行的各自字段来绑定到数据行。

C#

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 种操作类型:

  • AddRoleColumnRole: 将角色添加到表时调用。它实例化一个新的DataGridCheckBoxColumn,分配CheckBoxColumnStyle和UserRoleValueConverter。后者实现了用户角色分配逻辑(见下文)。该列使用角色实例进行标记,以便分配逻辑可以工作。列的标题设置为角色名称。
  • UpdateRoleColumn: 当角色行的内容被修改时调用。该逻辑扫描动态列集合以查找标记有已修改角色实例的列。找到后,列的标题将更新为角色名称。绑定机制自动更新数据网格中的列标题。
  • DeleteRoleRole: 从表中删除角色时调用。该逻辑扫描动态列集合以查找标记有已删除角色实例的列并移除该列。

C#

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处于编辑模式时被订阅,在不处于编辑模式时被取消订阅。

C#

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是否已经存在来添加或删除用户角色条目。

C#

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();
                        }
                    }
                }
            }
        }
    }
}

兴趣点

  • 【闲暇巴防盗用,具体内容联系作者,3个架构要点,非常重要】

结论

本文展示了 WPFDataGrid控件的动态列处理的实现。这是一个直接的 MVVM 实现,其中动态列处理在视图模型层中完成。此解决方案的缺点是 GUI 组件溢出到 ViewModel 层。在下一篇文章中,我将展示一个实现相同应用程序的解决方案,但将业务逻辑和 GUI 控件更严格地分离到各自的层中。

发表评论
留言与评论(共有 0 条评论) “”
   
验证码:

相关文章

推荐文章