Filling a Datagrid with dynamic Columns

后端 未结 4 1178
清歌不尽
清歌不尽 2020-12-12 20:40

I have an Datagrid which needs to get filled dynamicly.

The tablelayout is like:

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

相关标签:
4条回答
  • 2020-12-12 20:48

    If you are not required to show that into a single big DataGrid(table) then you could have a DataGrid with id,image,name,Description and when one of the records is selected on that DataGrid then you show/refresh a ListBox with the name of images that are related to that selected record

    0 讨论(0)
  • 2020-12-12 21:00

    2nd approach: use a DataTable. This makes use of the custom-type infrastructure under the hood, but is easier to use. Just bind the DataGrid's ItemsSource to a DataTable.DefaultView property:

    This almost worked but instead of binding to the DataTable.DefaultView property property I created a property of type DataView and bound to that.

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

    This allows the binding to be two way, binding to the DataTable.DefaultView cannot be a TwoWay binding. In the View Model

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

    With this setup I could not only define the columns dynamically when the View Model is initialized, but could update and change the data table dynamically at any time. In using the approach as defined by McGarnagle above, the view schema was not refreshing when the DataTable was updated with a new data source.

    0 讨论(0)
  • 2020-12-12 21:06

    There are at least three ways of doing this:

    1. Modify the DataGrid's columns manually from code-behind
    2. Use a DataTable as the ItemsSource *
    3. Use a CustomTypeDescriptor

      *recommended for simplicity


    1st approach: use code-behind to generate the DataGrid's columns at runtime. This is simple to implement, but maybe feels a bit hackish, especially if you're using MVVM. So you'd have your DataGrid with fixed columns:

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

    When you have your "Names" ready, then modify the grid by adding/removing columns, eg:

    // 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 
            });
        }
    }
    

    You'll want to create a wrapper class, which should contain the original class, plus a dictionary to contain the custom properties. Let's say your main row class is "User", then you'd want a wrapper class something like this:

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

    Populate the ItemsSource with a collection of this new "CustomUser" class:

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

    So tying it together, for example:

    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);
    

    2nd approach: use a DataTable. This makes use of the custom-type infrastructure under the hood, but is easier to use. Just bind the DataGrid's ItemsSource to a DataTable.DefaultView property:

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

    Then you can define the columns however you like, eg:

    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" });
    

    3rd approach: make use of the extensibility of .Net's type system. Specifically, use a CustomTypeDescriptor. This allows you to create a custom type at runtime; which in turn enables you to tell the DataGrid that your type has the properties "Name1", "Name2", ... "NameN", or whatever others you want. See here for a simple example of this approach.

    0 讨论(0)
  • 2020-12-12 21:07

    I'm currently using another Approach, i am not sure tho if it is right to do it like this, but it works. I made a small sample.

    Keep in mind that for this to work every entry in the Datagrid Need to have the same dynamic columns, making it a bit less flexible. But if u have entrys with diffrent amount of columns in each entry a Datagrid is probably the wrong Approach anyways.

    These are my classes

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

    And this is Code Behind:

    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++;
                }
    
            }
    

    And in .XAML simply:

      <DataGrid Name="MyGrid" ItemsSource="{Binding Community}" AutoGenerateColumns="False"/>
    
    0 讨论(0)
提交回复
热议问题