添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
首发于 CSharp项目
C# WPF 使用代码动态创建DataGrid

C# WPF 使用代码动态创建DataGrid

本篇为在WPF中使用代码创建完整DataGrid的一个示例。

在实际生产中,公用、动态的DataGrid表格会减少很多额外重复的工作,避免很多不必要的麻烦。下面我们来一起学习一下完全使用代码创建 一个动态生成带数据绑定和操作的DataGrid。

幼儿班:使用代码在Grid中添加一个DataGrid

要完成的效果

<DataGrid x:Name="list"/>

实现代码

new DataGrid() { Name="list" };

恭喜,幼儿园毕业了!

小学班:向DataGrid中添加自定义列

要完成的效果

<DataGrid x:Name="list">
     <DataGrid.Columns>
          <DataGridTextColumn Header="编号" Width="150" />
          <DataGridTextColumn Header="姓名" Width="150" />
      </DataGrid.Columns>
</DataGrid>

实现代码

DataGrid dataGrid = new DataGrid() { Name="list" };
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "编号", Width = 150 });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "姓名", Width = 150 });

通过上述方法可以使用代码设置列的所有属性,如

new DataGridTextColumn().Width = new DataGridLength(1, DataGridLengthUnitType.Star);//宽度 为1*
new DataGridTextColumn().Width = DataGridLength.Auto;//宽度 为Auto

初中班:自定义列的数据绑定和ObservableCollection

经过上面两个简单的示例我们已经可以使用代码创建并赋予DataGrid更多的属性以调整其样式和行为了,下面我们要来做最实际的操作,数据绑定,这里我们先创建一个List集合,用于绑定以上的数据

        public class User
            public int Id { get; set; }//编号
            public string Name { get; set; }//姓名
        public List<User> userList = new List<User>()
            new User(){ Id=1,Name="1-name" },
            new User(){ Id=2,Name="2-name" },
            new User(){ Id=3,Name="3-name" },
            new User(){ Id=4,Name="4-name" },
            new User(){ Id=5,Name="5-name" },

然后开始处理DataGrid

要完成的效果

    <Grid x:Name="main">
        <DataGrid x:Name="list">
            <DataGrid.Columns>
                <DataGridTextColumn Header="编号" Width="150" Binding="{Binding Id}" />
                <DataGridTextColumn Header="姓名" Width="150"  Binding="{Binding Name}"/>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

实现代码

DataGrid dataGrid = new DataGrid() { Name = "list" };
dataGrid.AutoGenerateColumns = false;//使用这一句禁止创建新列,不然的话会将绑定列重新创建一遍
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "编号", Width = 150, Binding = new Binding("Id") });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "姓名", Width = 150, Binding = new Binding("Name") });
dataGrid.ItemsSource = userList;
main.Children.Add(dataGrid);

至此,列的数据就成功的绑定了,但绑定后我们会发现,不管上文userList的值如何变化,只要不手动设置ItemsSource 前台的显示就不会更新,而且再次更新时,对于已经绑定数据源的DataGrid,再次绑定需要先清空数据源后才能更新

dataGrid.ItemsSource = null;//清空数据源
dataGrid.ItemsSource = userList;//重新绑定集合

这种方式的绑定对我们很不友好,所以就有了ObservableCollection类,它提供了一种双向绑定的通知机制,用法和List几乎没有区别,但它的ItemsSource绑定一次即可,当集合中的数据有增减dataGrid也会收到通知增减数据。

将上面的userList更改为ObservableCollection集合类型,就可以将操作的重心放在userList中,而不用担心页面的及时更新

public ObservableCollection<User> userList = new ObservableCollection<User>()
     new User(){ Id=1,Name="1-name" },
     new User(){ Id=2,Name="2-name" },
     new User(){ Id=3,Name="3-name" },
     new User(){ Id=4,Name="4-name" },
     new User(){ Id=5,Name="5-name" },

在这里延伸一点点,使用ObservableCollection类时,我们会收到数据增加和删除的通知,但单项更改时列表不会更新,比如在绑定数据后执行

userList[0].Name = "新的1name";

列表不会收到更新,这时我们要使用INotifyPropertyChanged接口来通知客户端属性的更改,我们改造一下User类如下

        public class User : INotifyPropertyChanged
            private int id;
            public int Id //编号
                get { return id; } 
                    id = value;
                    NotifyPropertyChanged("Id");//通知客户端 Id这个属性发生了更改 然它重新get一下
            private string name;
            public string Name //姓名
                get { return name; }
                    name = value;
                    NotifyPropertyChanged("Name");//通知客户端 Name这个属性发生了更改 然它重新get一下
            public event PropertyChangedEventHandler PropertyChanged;
            /// <summary>
            /// 通知属性更改(列表会自动更新)
            /// </summary>
            /// <param name="propertyName"></param>
            public void NotifyPropertyChanged(string propertyName)
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

这样,我们的属性和类集合都具备了通知功能,当数据发生变化的时候,前台会收到通知自动更新

userList[0].Name = "新的1name";//再次执行后 列表值直接被更改了

高中班:动态创建 DataGridTemplateColumn(数据模板列)

在实际应用中,我们经常需要对当前行的数据进行数据操作,我们需要向列中加入按钮,此处仅以编辑和删除按钮为例

要完成的效果

    <Grid x:Name="main">
        <DataGrid x:Name="list">
            <DataGrid.Columns>
                <DataGridTextColumn Header="编号" Width="150" Binding="{Binding Id}" />
                <DataGridTextColumn Header="姓名" Width="150"  Binding="{Binding Name}"/>
                <DataGridTemplateColumn Header="操作" Width="240" >
                    <DataGridTemplateColumn.CellTemplate>
                        <DataTemplate>
                            <StackPanel Orientation="Horizontal">
                                <Button Content="编辑"  Margin="20,0,0,0" />
                                <Button Content="删除" Margin="20,0,20,0"/>
                            </StackPanel>
                        </DataTemplate>
                    </DataGridTemplateColumn.CellTemplate>
                </DataGridTemplateColumn>
            </DataGrid.Columns>
        </DataGrid>
    </Grid>

实现代码

 DataGrid dataGrid = new DataGrid() { Name = "list" };
dataGrid.AutoGenerateColumns = false;//使用这一句禁止创建新列,不然的话会将绑定列重新创建一遍
dataGrid.CanUserAddRows = false;//使用这句禁止创建行,不然的话 数据会多一空行
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "编号", Width = 150, Binding = new Binding("Id") });
dataGrid.Columns.Add(new DataGridTextColumn() { Header = "姓名", Width = 150, Binding = new Binding("Name") });
DataGridTemplateColumn dataGridTemplateColumn = new DataGridTemplateColumn() { Header = "操作", Width = 240 };//创建模板列
DataTemplate dataTemplate = new DataTemplate();//创建数据模板
FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));//加入一个StackPanel
factory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal); //将StackPanel 横向排列
FrameworkElementFactory buttonEdit = new FrameworkElementFactory(typeof(Button));//创建编辑按钮
buttonEdit.SetValue(MarginProperty, new Thickness(20, 0, 0, 0));//Margin
buttonEdit.SetValue(ContentProperty, "编辑");//Content
factory.AppendChild(buttonEdit);//将编辑按钮加入StackPanel
FrameworkElementFactory buttonDel = new FrameworkElementFactory(typeof(Button));//创建一个删除按钮
buttonDel.SetValue(ContentProperty, "删除");
buttonDel.SetValue(MarginProperty, new Thickness(20, 0, 0, 0));
factory.AppendChild(buttonDel);//将删除按钮加入StackPanel
dataTemplate.VisualTree = factory;//将StackPanel 加入数据模板
dataGridTemplateColumn.CellTemplate = dataTemplate;//将数据模板加入模板列
dataGrid.Columns.Add(dataGridTemplateColumn);//向DataGrid中加入模板列
dataGrid.ItemsSource = userList;
main.Children.Add(dataGrid);

在数据模板中我们几乎可以加入任何我们想要的布局、控件和效果 ,通过对相应元素的绑定做出相应的处理,同理,我们可以像使用基础属性的方式,使用一些第三方的样式库,通过准确的数据类型对属性进行赋值,应用到DataGrid上。

关于加入元素的事件处理,以删除按钮为例

buttonDel.AddHandler(Button.ClickEvent,new RoutedEventHandler(OnDelClick));//绑定删除按钮点击事件
private void OnDelClick(object sender, RoutedEventArgs e)
   Button button = sender as Button;
   MessageBox.Show("删除按钮被点击");

我们可以将加入的元素进行封装,使列表依据固定的规则或实体类自动生成,并甩出相应的事件或属性。

下面是我带入Panuon.UI.Silver样式后的DataGrid

扩展班:通过类的自定义属性动态生成表格列

这里我们做个扩展,自定义一个属性,用于标识实体类中的哪列对应的是DataGrid中的哪列数据,通过读取数据源集合实体中的自定义属性来按上述方式生成DataGrid的列及操作按钮,我们先来添加一个自定义属性DataSourceBindingAttribute用于记录数据列在DataGrid中的表现:

    [AttributeUsage(AttributeTargets.Property, Inherited = true)]
    public class DataSourceBindingAttribute : Attribute
        /// <summary>
        /// 列标题
        /// </summary>
        public string ColumnHeader = "";
        /// <summary>
        /// 宽度 数字 -1=*  | 0=Auto 
        /// </summary>
        public int Width = 0;
        /// <summary>
        /// 数据源绑定
        /// </summary>
        /// <param name="_columnHeader">列标题</param>
        /// <param name="_width">宽度 只能是数字 -1=*  | 0=Auto   </param>
        public DataSourceBindingAttribute(string _columnHeader, int _width = -1)
            ColumnHeader = _columnHeader;
            Width = _width;

修改一下实体类User

        public class User : INotifyPropertyChanged
            private int id;
            [DataSourceBinding("编号", -1)]//加入 标题为编号的列 宽度为* 
            public int Id //编号
                get { return id; }
                    id = value;
                    NotifyPropertyChanged("Id");//通知客户端 Id这个属性发生了更改 然它重新get一下
            private string name;
            [DataSourceBinding("姓名", -1)]//加入 标题为姓名的列 宽度为* 
            public string Name //姓名
                get { return name; }
                    name = value;
                    NotifyPropertyChanged("Name");//通知客户端 Name这个属性发生了更改 然它重新get一下
            public event PropertyChangedEventHandler PropertyChanged;
            /// <summary>
            /// 通知属性更改(列表会自动更新)
            /// </summary>
            /// <param name="propertyName"></param>
            public void NotifyPropertyChanged(string propertyName)
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

实现代码更改为

            DataGrid dataGrid = new DataGrid() { Name = "list" };
            dataGrid.AutoGenerateColumns = false;//使用这一句禁止创建新列,不然的话会将绑定列重新创建一遍
            dataGrid.CanUserAddRows = false;//使用这句禁止创建行,不然的话 数据会多一空行
            //dataGrid.Columns.Add(new DataGridTextColumn() { Header = "编号", Width = 150, Binding = new Binding("Id") });
            //dataGrid.Columns.Add(new DataGridTextColumn() { Header = "姓名", Width = 150, Binding = new Binding("Name") });
            //将上述代码替换为读取属性添加列
            PropertyInfo[] properties = new User().GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public);//获取对象所有属性
            if (properties.Length <= 0) return; //如果对象没有属性 直接退出
            foreach (PropertyInfo item in properties)
                var bindingAtt = typeof(User).GetProperty(item.Name).GetCustomAttribute<DataSourceBindingAttribute>(); // 获取指定属性的属性描述
                if (bindingAtt != null)//没有标记属性的值不显示
                    var column = new DataGridTextColumn() { Header = bindingAtt.ColumnHeader, Width = 150, Binding = new Binding(item.Name) };//添加一列 标题为属性的ColumnHeader 绑定数据为当前属性
                    //宽度判断
                    if (bindingAtt.Width == -1)
                        column.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
                    else if (bindingAtt.Width == 0)
                        column.Width = DataGridLength.Auto;
                        column.Width = bindingAtt.Width;
                    //加入列
                    dataGrid.Columns.Add(column);
            DataGridTemplateColumn dataGridTemplateColumn = new DataGridTemplateColumn() { Header = "操作", Width = 240 };//创建模板列
            DataTemplate dataTemplate = new DataTemplate();//创建数据模板
            FrameworkElementFactory factory = new FrameworkElementFactory(typeof(StackPanel));//加入一个StackPanel
            factory.SetValue(StackPanel.OrientationProperty, Orientation.Horizontal); //将StackPanel 横向排列
            FrameworkElementFactory buttonEdit = new FrameworkElementFactory(typeof(Button));//创建编辑按钮
            buttonEdit.SetValue(MarginProperty, new Thickness(20, 0, 0, 0));//Margin
            buttonEdit.SetValue(ContentProperty, "编辑");//Content
            factory.AppendChild(buttonEdit);//将编辑按钮加入StackPanel
            FrameworkElementFactory buttonDel = new FrameworkElementFactory(typeof(Button));//创建一个删除按钮
            buttonDel.SetValue(ContentProperty, "删除");
            buttonDel.SetValue(MarginProperty, new Thickness(20, 0, 0, 0));
            factory.AppendChild(buttonDel);//将删除按钮加入StackPanel
            dataTemplate.VisualTree = factory;//将StackPanel 加入数据模板