Home > Back-end >  WPF datagrid, is there a way to create a new row for an object that has an array of strings
WPF datagrid, is there a way to create a new row for an object that has an array of strings

Time:02-02

I am working on a datagrid, the object I am putting in has multiple properties including a String array.

When binding to the grid you can only get one row whereas i need to be able to have a row per item in the array any help?

I have the data grid working using Command="{Binding PropX}" however it only gives single rows

<DataGridTextColumn Header="Names"
                            Binding="{Binding Names}"
                            Width="Auto" />
    <DataGridTextColumn Header="ID"
                            Binding="{Binding ID}"
                            Width="Auto" />
    <DataGridTextColumn Header="Code"
                            Binding="{Binding Code}"
                            Width="Auto" />
    <DataGridTextColumn Header="Contract"
                            Binding="{Binding Contract}"
                            Width="Auto" />
    <DataGridTextColumn Header="Wants Data File"
                            Binding="{Binding WantsDataFile}"
                            Width="Auto" />

Names in the instance is an array of names, so for each name i need a new row with the rest of the data still the same.

Datagrid is bound to an observable collection of the object

public class Customer : ObservableObject{
    public   ObservableCollection<string> names {get;set;}
    public int ID {get;set;}
    public.......
}

THE GRID IS BOUND TO AN OBSERVABLE COLLECTION OF CUSTOMER

CodePudding user response:

Two options.

1. Construct a Flat List of Customer-By-Name View Models

If you want one row per name (not just per Customer) you will need to do a one-to-many transformation because you ultimately need a flat array to provide to ItemsSource. There are plenty of ways this could be done, but here's one:

public class CustomerByNameViewModel : ObservableObject
{
    public CustomerByNameViewModel (Customer customer, int nameIndex)
    {
        _nameIndex = nameIndex;
        this.Customer = customer;
    }           

    // what you bind to
    public int ID => Customer.ID;
    public string Name => Customer.names[_nameIndex]; 

    // not what you bind to; instead add public 
    // accessors for any other Customer properties 
    // you need
    internal Customer Customer {get; set;} 
    private int _nameIndex;
}

In your main view model:

CustomerByNameViewModel[] _FlatCustomerList; 
public CustomerByNameViewModel[] FlatCustomerList
{
    get
    {
        if (_FlatCustomerList == null) 
        {
            _FlatCustomerList = Customers.SelectMany(c => 
            {
               int i = 0;
               return c.Select(c2 => new CustomerByNameViewModel(c2, i  ));
            }).ToArray(); // don't omit this!
        }
        return _FlatCustomerList;
    }
 }

Then set ItemsSource to FlatCustomerList.

Big drawback to this - you lose the benefits of ObservableCollection. If the either the collection of Customers OR their individual names change you need to null out _FlatCustomerList and trigger a property changed notification to rebuild the flat list. Not the most efficient thing, but it does do the job.

Edit - I changed this to wrap Customer in its own view model, which would be a much better practice so as to keep the data concern separated from its visualization, and avoid creating new Customer objects just to generate the flat list.

If you're feeling ambitious you could extend this further to have the VM listen to changes on the Customer object and update appropriately.

2. Use DataGrid Details

The other option is to use DataGrid details. In my experience this is rarely used but it's a pretty powerful feature. A very good example is here. The idea is you'd keep your current collection intact (though I would still add a new derived property that at least always gives the first name in the list to use in the main row) and use it to populate the main row.

Then in the RowDetailsTemplate you can put whatever you want; you have the Customer as the DataContext so you could bind Names to another ItemsControl like a ListBox to display all the names. You might find this to be a better design choice than a separate row for each customer name.

  • Related