锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 开源技术 / WPF / WPF DataGrid动态列
服务方向
人工智能数据处理
人工智能培训
kaldi数据准备
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
kaldi开发技术服务
软件开发
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft

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

WPF DataGrid动态列


动态列在动态表显示时非常有用,常见的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 编写
  • DataGridColumnsBehavior:允许修改附加控件的列的附加行为DataGrid。
  • 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是克服此限制的附加行为。原始文章和来源可以在这里找到。

注:要点。

              

<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可以直接更新数据。

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

兴趣点

复选框列样式处理

作为额外的奖励(并防止额外的状态逻辑),我添加了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 控件更严格地分离到各自的层中。

 

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州大学北校区院(文化路97号院)内