积极答复者
WPF DataGrid 如何增加列?

问题
-
查了很多资料,都没能找到非常有用的信息。希望能在这儿得到帮助,先谢谢了。
WPF的项目,客户希望给DataGrid添加列(当然,也要能添加行了),而且不是只添一次,而是不断的增加列。例如,现有的列名是“规则一”、“规则二”,他们希望能够不断添加新的规则(每按一次键添加新的一列),然后再在DataGrid里输入、修改。就是要像Access一样。
一般的DataGrid是绑定到指定的ObservableCollection, 而每一列实际是绑定到the property of the data source. 像现在这样的要求,该如何绑定才行呢?
我试着先把数据放到一个DataTable里,然后DatraGrid binding to this DataTable,make "AutomaticGenerateColumn = True"。然后每次用户选择添加,就添加到DataTable里,希望DataGrid能根据DataTable的变化自动产生新列,但是DataGrid不能自动更新,不知道为什么,另外这种在中间加DataTable的办法好像也不是太好,希望能找到更聪明点的办法。
任何建议都非常感谢。
答案
-
你好,
我不是很了解你是否了解WPF ItemsControl 的一维特性。就是说WPF中所有的ItemsControl支持一维的数据结构,简单理解,我们的ListBox,ListView, ComboBox, 甚至DataGrid 都是仅支持一个维度的集合。这里你要问了,为啥DataGrid有航行和列? 其实我们这么设计的,一个维度嵌套在另一个维度中,就能够形成二维的集合结构,但是对于DataGrid来说,对于他能够直接操作的还是第一维的DataGridRow。 我们集合中每一行元素或者讲每一个元素都是会被一个 DataGridRow包装好放入DataGrid。 然后在DataGridRow还会有进一步的每个元素的各个属性的包装 DataGridCell。 为什么我要讲这个,是因为我要告诉你,在WPF中,一旦这个DataGrid显示出来的,他的可视树中是根本没有所谓列的概念的,他的可视树里面只有行和单元格的概念。所以你的第一个问题,要动态添加列,只有在DataGrid没有显示前增加,即在DataGrid的逻辑树里面增加。 或者我们可以动态增加DataGrid所绑定的DataTable的列,然后重新设置DataGrid的绑定,让DataGrid重新根据数据源来自动生成列。
所以,我上面已经回答了你的 “一般的DataGrid是绑定到指定的ObservableCollection, 而每一列实际是绑定到the property of the data source. 像现在这样的要求,该如何绑定才行呢?” 这个问题,是不能实现的。WPF中的DataGrid在绑定后显示出来了,就不能再加列了,每个明确的类型的属性是不能随意增加的。
除非,你的ObservableCollection集合里面是一个dynamic类型,他可以动态的添加属性,这种类型你可以添加好类型之后,重新设置DataGrid的ItemsSource绑定即可(注:此方法只能用在WPF 4 .Net 4 Framework里面,参考此文:http://blogs.msdn.com/b/llobo/archive/2009/11/02/new-wpf-feature-binding-to-dynamic-objects.aspx)
代码片段:
ObservableCollection<dynamic> items = new ObservableCollection<dynamic>(); public MainWindow() { InitializeComponent(); for (int i = 0; i < 5; i++) { dynamic item = new DynamicObjectClass(); item.A = "Property A value - " + i.ToString(); item.B = "Property B value - " + i.ToString(); items.Add(item); } dataGrid.Columns.Add(new DataGridTextColumn() {Header="A", Binding = new Binding("A") }); dataGrid.Columns.Add(new DataGridTextColumn() {Header="B", Binding = new Binding("B") }); dataGrid.ItemsSource = items; } private void AddData_Click(object sender, RoutedEventArgs e) { dynamic item = new DynamicObjectClass(); item.A="New Item - A"; item.B="New Item - B"; items.Add(item); } int newColumnIndex = 1; private void AddColumn_Click(object sender, RoutedEventArgs e) { foreach (DynamicObjectClass item in items) { item.TrySetMember(new SetPropertyBinder("NewColumn" + newColumnIndex), "New Column Value - " + newColumnIndex.ToString()); } dataGrid.Columns.Add(new DataGridTextColumn() { Header = "New Column" + newColumnIndex, Binding = new Binding("NewColumn" + newColumnIndex) }); newColumnIndex++; }
例子比较复杂,你下载看一下: https://skydrive.live.com/#!/?cid=51b2fdd068799d15&sc=documents&uc=1&id=51B2FDD068799D15%21827
------------------------------------------------------
如果你是选择DataTable作为数据源,那就相对简单一点,你在DataTable里面动态增加了列之后,和新列的每一行数据之后,你可以先设置DataGrid.ItemsSource = null; 然后再重新设置ItemsSource到你的DataTable:
XAML:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <Button Content="Add Column" Click="AddColumn_Click" Margin="5"/> <Button Content="Add Data" Click="AddData_Click" Margin="5"/> </StackPanel> <DataGrid x:Name="dataGrid" AutoGenerateColumns="True" Grid.Row="1"/> </Grid>
C#:
public partial class MainWindow : Window { DataTable dt = new DataTable(); public MainWindow() { InitializeComponent(); dt.Columns.Add(new DataColumn("Column1")); dt.Columns.Add(new DataColumn("Column2")); DataRow dr; for (int i = 0; i < 5; i++) { dr = dt.NewRow(); for (int columIndex = 0; columIndex < dt.Columns.Count ; columIndex++) dr[columIndex] = i.ToString() + " - " + columIndex.ToString(); dt.Rows.Add(dr); } dataGrid.ItemsSource = dt.DefaultView; } private void AddData_Click(object sender, RoutedEventArgs e) { DataRow dr = dt.NewRow(); for (int columIndex = 0; columIndex < dt.Columns.Count; columIndex++) dr[columIndex] = "New Row - " + columIndex.ToString(); dt.Rows.Add(dr); } int newColumnIndex = 1; private void AddColumn_Click(object sender, RoutedEventArgs e) { dt.Columns.Add(new DataColumn("New Column" + newColumnIndex++)); for (int i = 0; i < dt.Rows.Count; i++) { dt.Rows[i][dt.Columns.Count - 1] = i.ToString() + " - New Column"; } dataGrid.ItemsSource = null; dataGrid.ItemsSource = dt.DefaultView; } }
下载:https://skydrive.live.com/#!/?cid=51b2fdd068799d15&sc=documents&uc=1&id=51B2FDD068799D15%21825分别看下我的例子吧,你应该会对WPF的DataGrid的使用有一定了解。
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
- 已编辑 Jie BaoModerator 2011年9月7日 18:21
- 已标记为答案 vivianwind 2011年9月8日 13:01
- 取消答案标记 vivianwind 2011年9月8日 13:02
- 已标记为答案 Jie BaoModerator 2011年9月8日 15:16
全部回复
-
你好,
我不是很了解你是否了解WPF ItemsControl 的一维特性。就是说WPF中所有的ItemsControl支持一维的数据结构,简单理解,我们的ListBox,ListView, ComboBox, 甚至DataGrid 都是仅支持一个维度的集合。这里你要问了,为啥DataGrid有航行和列? 其实我们这么设计的,一个维度嵌套在另一个维度中,就能够形成二维的集合结构,但是对于DataGrid来说,对于他能够直接操作的还是第一维的DataGridRow。 我们集合中每一行元素或者讲每一个元素都是会被一个 DataGridRow包装好放入DataGrid。 然后在DataGridRow还会有进一步的每个元素的各个属性的包装 DataGridCell。 为什么我要讲这个,是因为我要告诉你,在WPF中,一旦这个DataGrid显示出来的,他的可视树中是根本没有所谓列的概念的,他的可视树里面只有行和单元格的概念。所以你的第一个问题,要动态添加列,只有在DataGrid没有显示前增加,即在DataGrid的逻辑树里面增加。 或者我们可以动态增加DataGrid所绑定的DataTable的列,然后重新设置DataGrid的绑定,让DataGrid重新根据数据源来自动生成列。
所以,我上面已经回答了你的 “一般的DataGrid是绑定到指定的ObservableCollection, 而每一列实际是绑定到the property of the data source. 像现在这样的要求,该如何绑定才行呢?” 这个问题,是不能实现的。WPF中的DataGrid在绑定后显示出来了,就不能再加列了,每个明确的类型的属性是不能随意增加的。
除非,你的ObservableCollection集合里面是一个dynamic类型,他可以动态的添加属性,这种类型你可以添加好类型之后,重新设置DataGrid的ItemsSource绑定即可(注:此方法只能用在WPF 4 .Net 4 Framework里面,参考此文:http://blogs.msdn.com/b/llobo/archive/2009/11/02/new-wpf-feature-binding-to-dynamic-objects.aspx)
代码片段:
ObservableCollection<dynamic> items = new ObservableCollection<dynamic>(); public MainWindow() { InitializeComponent(); for (int i = 0; i < 5; i++) { dynamic item = new DynamicObjectClass(); item.A = "Property A value - " + i.ToString(); item.B = "Property B value - " + i.ToString(); items.Add(item); } dataGrid.Columns.Add(new DataGridTextColumn() {Header="A", Binding = new Binding("A") }); dataGrid.Columns.Add(new DataGridTextColumn() {Header="B", Binding = new Binding("B") }); dataGrid.ItemsSource = items; } private void AddData_Click(object sender, RoutedEventArgs e) { dynamic item = new DynamicObjectClass(); item.A="New Item - A"; item.B="New Item - B"; items.Add(item); } int newColumnIndex = 1; private void AddColumn_Click(object sender, RoutedEventArgs e) { foreach (DynamicObjectClass item in items) { item.TrySetMember(new SetPropertyBinder("NewColumn" + newColumnIndex), "New Column Value - " + newColumnIndex.ToString()); } dataGrid.Columns.Add(new DataGridTextColumn() { Header = "New Column" + newColumnIndex, Binding = new Binding("NewColumn" + newColumnIndex) }); newColumnIndex++; }
例子比较复杂,你下载看一下: https://skydrive.live.com/#!/?cid=51b2fdd068799d15&sc=documents&uc=1&id=51B2FDD068799D15%21827
------------------------------------------------------
如果你是选择DataTable作为数据源,那就相对简单一点,你在DataTable里面动态增加了列之后,和新列的每一行数据之后,你可以先设置DataGrid.ItemsSource = null; 然后再重新设置ItemsSource到你的DataTable:
XAML:
<Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Orientation="Horizontal"> <Button Content="Add Column" Click="AddColumn_Click" Margin="5"/> <Button Content="Add Data" Click="AddData_Click" Margin="5"/> </StackPanel> <DataGrid x:Name="dataGrid" AutoGenerateColumns="True" Grid.Row="1"/> </Grid>
C#:
public partial class MainWindow : Window { DataTable dt = new DataTable(); public MainWindow() { InitializeComponent(); dt.Columns.Add(new DataColumn("Column1")); dt.Columns.Add(new DataColumn("Column2")); DataRow dr; for (int i = 0; i < 5; i++) { dr = dt.NewRow(); for (int columIndex = 0; columIndex < dt.Columns.Count ; columIndex++) dr[columIndex] = i.ToString() + " - " + columIndex.ToString(); dt.Rows.Add(dr); } dataGrid.ItemsSource = dt.DefaultView; } private void AddData_Click(object sender, RoutedEventArgs e) { DataRow dr = dt.NewRow(); for (int columIndex = 0; columIndex < dt.Columns.Count; columIndex++) dr[columIndex] = "New Row - " + columIndex.ToString(); dt.Rows.Add(dr); } int newColumnIndex = 1; private void AddColumn_Click(object sender, RoutedEventArgs e) { dt.Columns.Add(new DataColumn("New Column" + newColumnIndex++)); for (int i = 0; i < dt.Rows.Count; i++) { dt.Rows[i][dt.Columns.Count - 1] = i.ToString() + " - New Column"; } dataGrid.ItemsSource = null; dataGrid.ItemsSource = dt.DefaultView; } }
下载:https://skydrive.live.com/#!/?cid=51b2fdd068799d15&sc=documents&uc=1&id=51B2FDD068799D15%21825分别看下我的例子吧,你应该会对WPF的DataGrid的使用有一定了解。
Sincerely,
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.
- 已编辑 Jie BaoModerator 2011年9月7日 18:21
- 已标记为答案 vivianwind 2011年9月8日 13:01
- 取消答案标记 vivianwind 2011年9月8日 13:02
- 已标记为答案 Jie BaoModerator 2011年9月8日 15:16
-
从性能来看,前者的数据结构简单,数据量大有优势;200行我推荐后者,他有极好的适应性,兼容性,从.Net 2.0以来的很多控件数据结构都可以支持。且我们比较熟悉,控制操作起来会顺手。
“想问一下第一种方法里用Dynamic动态添加属性,那这个属性会被添加到数据库里吗?如果能,什么时候添加?如果不能,是不是就和用DataTable一样需要手动添加?我对Dynamic不了解,问题如果很Stupid,请见谅。”
是不会动态添加到数据库的,我们知道数据库的结构需要预先定义好,我们不可能简单的修改数据库表的结构。 所以动态对象不熟悉的话,我还是建议你用后者吧。
Bob Bao [MSFT]
MSDN Community Support | Feedback to us
Get or Request Code Sample from Microsoft
Please remember to mark the replies as answers if they help and unmark them if they provide no help.