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 加入数据模板