【问题标题】:Filling a Datagrid with dynamic Columns用动态列填充数据网格
【发布时间】:2013-08-29 09:58:11
【问题描述】:

我有一个需要动态填充的 Datagrid。

表格布局如下:

id | image | name | Description | Name-1 | Name-N

前 4 列是静态的,其他列是动态的。用户应该能够添加任意数量的用户。

我尝试通过将多个用户的数据放在表格中彼此相邻的方式来比较它们。

现在我有一个列表框,其中包含动态生成的列的名称和填充静态列的方法。我还可以为每个用户加载数据。现在我需要将它们合并到一个大表中。

现在的主要问题是:如何将“用户数据”和静态内容放在一个数据网格中。

【问题讨论】:

  • 这是显示此信息的唯一方式吗?
  • 我不确定您的要求。
  • Johannes 看到我的回答,这样你就可以明白我为什么要问这个问题了。

标签: c# .net wpf datagrid


【解决方案1】:

至少有三种方法可以做到这一点:

  1. 从代码隐藏中手动修改 DataGrid 的列
  2. 使用 DataTable 作为 ItemsSource *
  3. 使用自定义类型描述符

    *为简单起见推荐


第一种方法:使用代码隐藏在运行时生成 DataGrid 的列。这实现起来很简单,但可能会让人觉得有点骇人听闻,尤其是在您使用 MVVM 的情况下。因此,您的 DataGrid 将具有固定列:

<DataGrid x:Name="grid">
    <DataGrid.Columns>
        <DataGridTextColumn Binding="{Binding id}" Header="id" />
        <DataGridTextColumn Binding="{Binding image}" Header="image" />
    </DataGrid.Columns>
</DataGrid>

当您准备好“名称”后,通过添加/删除列来修改网格,例如:

// add new columns to the data grid
void AddColumns(string[] newColumnNames)
{
    foreach (string name in newColumnNames)
    {
        grid.Columns.Add(new DataGridTextColumn { 
            // bind to a dictionary property
            Binding = new Binding("Custom[" + name + "]"), 
            Header = name 
        });
    }
}

您需要创建一个包装类,它应该包含原始类,以及一个包含自定义属性的字典。假设您的主行类是“用户”,那么您需要一个类似这样的包装类:

public class CustomUser : User
{
    public Dictionary<string, object> Custom { get; set; }

    public CustomUser() : base()
    {
        Custom = new Dictionary<string, object>();
    }
}

用这个新的“CustomUser”类的集合填充ItemsSource

void PopulateRows(User[] users, Dictionary<string, object>[] customProps)
{
    var customUsers = users.Select((user, index) => new CustomUser {
        Custom = customProps[index];
    });
    grid.ItemsSource = customUsers;
}

因此将其捆绑在一起,例如:

var newColumnNames = new string[] { "Name1", "Name2" };
var users = new User[] { new User { id="First User" } };
var newProps = new Dictionary<string, object>[] {
    new Dictionary<string, object> { 
        "Name1", "First Name of First User",
        "Name2", "Second Name of First User",
    },
};
AddColumns(newColumnNames);
PopulateRows(users, newProps);

第二种方法:使用DataTable。这在后台使用了自定义类型的基础架构,但更易于使用。只需将 DataGrid 的 ItemsSource 绑定到 DataTable.DefaultView 属性即可:

<DataGrid ItemsSource="{Binding Data.DefaultView}" AutoGenerateColumns="True" />

然后你可以随意定义列,例如:

Data = new DataTable();

// create "fixed" columns
Data.Columns.Add("id");
Data.Columns.Add("image");

// create custom columns
Data.Columns.Add("Name1");
Data.Columns.Add("Name2");

// add one row as an object array
Data.Rows.Add(new object[] { 123, "image.png", "Foo", "Bar" });

第三种方法:利用 .Net 类型系统的可扩展性。具体来说,使用CustomTypeDescriptor。这允许您在运行时创建自定义类型;这反过来又使您能够告诉 DataGrid 您的类型具有属性“Name1”、“Name2”、...“NameN”,或者您想要的任何其他属性。有关此方法的简单示例,请参阅 here

【讨论】:

  • 我发现 DataTable 方法是最简单、最有效的解决方案。
  • 有没有人使用方法 3 在 DataGrid 中成功地自动生成列?我已经定义了一个自定义类型描述符,我可以很好地绑定到它的属性,但是自动生成列不起作用。
  • 如果我们想在同一个数据网格中编辑和更新值怎么办
  • 这似乎只有在控件初始化之前添加数据列时才有效。如果稍后更改列,如何将其反映在网格中?
  • 很难,我不知道。可以调用 InvalidateVisual 吗?
【解决方案2】:

第二种方法:使用 DataTable。这在后台使用了自定义类型的基础架构,但更易于使用。只需将 DataGrid 的 ItemsSource 绑定到 DataTable.DefaultView 属性:

这几乎可行,但我没有绑定到 DataTable.DefaultView 属性,而是创建了一个 DataView 类型的属性并绑定到该属性。

<DataGrid ItemsSource="{Binding DataView, Mode=TwoWay}" AutoGenerateColumns="True" />

这允许绑定是双向的,绑定到 DataTable.DefaultView 不能是双向绑定。在视图模型中

    public DataView DataView
    {
        get { return _dataView; }
        set
        {
            _dataView = value;
            OnPropertyChanged("DataView");
        }
    }

通过这种设置,我不仅可以在初始化视图模型时动态定义列,还可以随时动态更新和更改数据表。在使用上述 McGarnagle 定义的方法时,当使用新数据源更新 DataTable 时,视图架构不会刷新。

【讨论】:

    【解决方案3】:

    我目前正在使用另一种方法。我不确定这样做是否正确,但它确实有效。我做了一个小样本。

    请记住,要使其正常工作,Datagrid 中的每个条目都需要具有相同的动态列,这使其灵活性降低了一些。但是,如果您在每个条目中都有不同数量的列,那么 Datagrid 可能无论如何都是错误的方法。

    这些是我的课程:

     public class Person
        {
            public ObservableCollection<Activity> Hobbys { get; set; }
            public string Name { get; set; }
        }
     public class Activity
        {
            public string Name { get; set; }
        }
    

    这是背后的代码:

    public MainWindow()
            {
                InitializeComponent();
                DataContext = this;
    
                ObservableCollection<Activity> hobbys = new ObservableCollection<Activity>();
                hobbys.Add(new Activity() { Name = "Soccer" });
                hobbys.Add(new Activity() { Name = "Basketball" });
    
                Community = new ObservableCollection<Person>();
                Community.Add(new Person() { Name = "James", Hobbys = hobbys });
                Community.Add(new Person() { Name = "Carl", Hobbys = hobbys });
                Community.Add(new Person() { Name = "Homer", Hobbys = hobbys });
    
                MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Name", Binding = new Binding("Name") });    //Static Column
                int x = 0;
                foreach (Activity act in Community[0].Hobbys)  //Create the dynamic columns
                {
                    MyGrid.Columns.Add(new DataGridTextColumn() { Header = "Activity", Binding = new Binding("Hobbys["+x+"].Name") });
                    x++;
                }
    
            }
    

    在 .XAML 中很简单:

      <DataGrid Name="MyGrid" ItemsSource="{Binding Community}" AutoGenerateColumns="False"/>
    

    【讨论】:

    • 我会尽快测试一下,但看起来它可以工作。我不喜欢您必须在后面的代码中访问 DataGrid,但由于大多数其他解决方案也这样做,因此似乎没有办法解决这个问题。我想虽然可以对其进行自定义控制。当我有时间的时候,我可能会尝试实现一个并在此处发布。
    • 如果你能用它制作一个自定义控件,我会非常感兴趣。这对我很有帮助。
    【解决方案4】:

    如果您不需要将其显示到单个大 DataGrid(table) 中,那么您可以拥有一个带有 id,image,name,DescriptionDataGrid 并且当一个在该 DataGrid 上选择记录,然后显示/刷新一个 ListBox,其中包含与该选定记录相关的图像名称

    【讨论】:

    • 好吧,我认为这不会解决我的问题。我有一个包含一些数据的大表(无论哪个),现在我需要将多个名称与记录进行比较。因此,对于每条记录,我需要“N”多个列来直接比较两个或更多人。 e. G。 : 记录 1: 1;tmp.png;Foo;bar;成功;未成功 记录 2: 2;tmp2.png;Foo1;bar2;未成功;成功
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-17
    • 1970-01-01
    • 1970-01-01
    • 2019-02-08
    • 1970-01-01
    相关资源
    最近更新 更多