none
WPF DataGrid 如何增加列? RRS feed

  • 问题

  • 查了很多资料,都没能找到非常有用的信息。希望能在这儿得到帮助,先谢谢了。

    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的办法好像也不是太好,希望能找到更聪明点的办法。

    任何建议都非常感谢。

    2011年9月7日 14:41

答案

  • 你好,

    我不是很了解你是否了解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.


    2011年9月7日 17:59
    版主

全部回复

  • 你好,

    我不是很了解你是否了解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.


    2011年9月7日 17:59
    版主
  • 谢谢版主。第二种方法就是我试的那种,但我用成DataGrid.DataContext了,所以后来不能自动更新。

    另外,想问一下第一种方法里用Dynamic动态添加属性,那这个属性会被添加到数据库里吗?如果能,什么时候添加?如果不能,是不是就和用DataTable一样需要手动添加?我对Dynamic不了解,问题如果很Stupid,请见谅。

    还有就是,从Performance的角度来看,哪种方法更好?数据不是很多,不会到超过200行,但列会不断增加。

    再次谢谢!


    Vivian
    2011年9月8日 13:01
  • 从性能来看,前者的数据结构简单,数据量大有优势;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.

    2011年9月8日 15:16
    版主
  • 非常感谢!


    Vivian
    2011年9月8日 15:22
  • 急问:

    在这种情况下如果想使用Converter格式化改背景的话,要怎么做呢?

    前台没有绑定,无法写Converter={....}

    2013年12月17日 2:21