views:

336

answers:

3

Is there a way to specify DataGrid columns declaratively using a binding? (And to set the columns' properties using this binding?) Idealy, I would have a list of objects (e.g. rows) databound to the ItemsSource and one of their properties would be a dictionary (or a list of objects of a certain class or whatever) with a name and a value. I would like the DataGrid to automatically create these additional columns without having some code behind. Is that even possible? Or how would you get around this?

The array holding the extra values can change over time but would be the same for all the items in the list.

It would be possible (and clean) to supply the DataGrid with a different list just for it to create the columns from. But for that I would need something like ColumnsSource or something...

The only thing that I could come up with was creating a subclass of the DataGrid...

Any ideas?

EDIT: The point is achieving this without any code behind...

A: 

In your XAML bind your datagrid to a ObservableCollection of objects of a certain class that has properties.

XAML:

<WpfToolkit:DataGrid
        x:Name="MyDataGrid"
        ItemsSource="{Binding Path=Collection}"
        HorizontalScrollBarVisibility="Hidden" SelectionMode="Extended"
        CanUserAddRows="False" CanUserDeleteRows="False"
        CanUserResizeRows="False" CanUserSortColumns="False"
        AutoGenerateColumns="False"
        RowHeaderWidth="25" RowHeight="25"/>

Next you can create your columns programmatically in C#/VBA and bind each individual column to a property of the class to which the ObservableCollection contains objects of it. By adding objects of the class you will be adding rows to the datagrid. In other words, each object of the class in the ObservableCollection will be a row and the properties of the class will be the columns.

Here is an example to how you can bind your columns programmatically... C#:

ObservableCollection<IData> datagridData = new ObservableCollection< IData >();
Binding items = new Binding();
PropertyPath path = new PropertyPath("Name"); // 'Name' is actually the name of the variable representing the property
items.Path = path;

MyDataGrid.Columns.Add(new DataGridTextColumn()
{
   Header = "Names",
   Width =  275,
   Binding = items
});
//repeat the creation of columns
//...
//- Add some objects to the ObservableCollection
//- Then bind the ItemsSource of the datagrid to the ObservableCollection
datagridData .Add(new Data("Bob", string.Empty));
MyDataGrid.DataContext = new DataModel{ MyData = datagridData };

*Edit: Sorry about that! Here is how you can achieve the same thing entirely in XAML:

<WpfToolkit:DataGrid
        x:Name="MyDataGrid"
        ItemsSource="{Binding Path=Collection}"
        HorizontalScrollBarVisibility="Hidden" SelectionMode="Extended"
        CanUserAddRows="False" CanUserDeleteRows="False"
        CanUserResizeRows="False" CanUserSortColumns="False"
        AutoGenerateColumns="False"
        RowHeaderWidth="25" RowHeight="25">

             <WpfToolkit:DataGridTextColumn
                        Header="Names" Width="2*"
                        Binding="{Binding Path=Name}"/>
             <WpfToolkit:DataGridTextColumn
                        Header="Names" Width="2*"
                        Binding="{Binding Path=Age}"/>

        </WpfToolkit:DataGrid.Columns>
</WpfToolkit:DataGrid>

Edit 2: Here is what the code of the ObservableCollection and class may look like in C#:

public class DataModel
    {
        public ObservableCollection<IData> MyData{ get; set; }
    }

public interface IData
    {
        string Name{ get; set; }
        string Age{ get; set; }
    }

public class Data : IData
    {
        public Data(string name, string age)
        {
            Name= name;
            Age= age;
        }

        public string Name{ get; set; }
        public string Age{ get; set; }
    }
Partial
Yeah that is nearly perfect with just one tiny exception... that I didn't want to write it programatically. That's why there is "declaratively" in the title. I will update the question to make it more clear. But thanks for your input!
karel_evzen
Near miss again I am afraid. The columns in this example are hardcoded, not generated by a binding. (The content of the columns is bound to a property, that is fine, but the columns themselves are not...)Also, the grid may have some hardcoded columns but the rest of the columns should be generated from a list. (The IData would have another property, lets say a Disctionary and the extra columns would be the keys of that disctioary. And I don't think that is possible so I am basically looking for a way to get around it)
karel_evzen
A: 

i think you would have to subclass the grid. Columns is not a bindable property. if you cant bind to it, you cant dynamically fiddle with it in the xaml.

As for using the items to create the columns, does that mean if there were no items in the grid there would be no columns?

If you have a subclass of the grid that had a columns property you could bind to, then the place to dynamically change the columns would be in your view model.

Aran Mulholland
You can bind the columns!
Partial
yeah..how? it is a public ObservableCollection<DataGridColumn> Columns { get; } property. if you cant set it how can you bind it?
Aran Mulholland
id like to see an example if you have one...if not id like an upvote
Aran Mulholland
+1 to negate the downvote... You absolutely cannot bind to the Columns property on a DataGrid
IanR
Simply look at my example...
Partial
You can bind the DataGrid to a ObservableCollection that contains instances of a class which in turn itself contains properties and you bind each individual column that you create to the properties of the class..
Partial
You never play directly with the DataGrid. You play with its ItemsSource.
Partial
Sure - but in this instance the OP is looking to generate those columns 'on the fly' - they want to be able to write something like <DataGrid Columns="{Binding MyColumnDefinitions}" and this cannot be done...
IanR
thanks for the upvote ian, i got quite excited when Partial said that you could bind the columns collection, then i went and checked the code. i guess he got columns and rows confused, an easy thing to do i suppose.
Aran Mulholland
+1  A: 

At the risk of sounding controversial...

I think this is one example where the View really does need a bit of a code "boost", and the 'no code-behind' guideline can be put to one side - as long as you remember to keep your concerns separated.

I have, in the past, dynamically created DataGrid controls by grabbing the relevant data from the ViewModel, and writing a 'BuildDataGrid' method, similar to Partial's answer, in the code-behind. I believe this is justified, because the code was purely concerned with supplementing the View, and I did not mix concerns by having it implement business rules - it just put the columns together, and created the column Bindings, as appropriate.

But if it is more important to keep code-behind clean, then I would recommend your original thought of creating a User Control and using DP's to 'sub-class' the control.

IanR
I guess this is the only way, I'll keep the question opened for a while, maybe someone will come up with something, otherwise accept this.Just a sidenote: The grid is capable of generating extra columns automatically (AutoGenerateColumns property). But that wouldn't work or lists of course. The question is, would the whole binding thing work with annonymous classes? Or better - is it using reflection to generate the extra columns? I could create a class on the fly for the data, but that may be even worse it terms of maintainability and readability...
karel_evzen
I know it's not ideal... M-V-VM has grown so substantially since this control was originally authored that I'm sure there will be a version at some point in the future that exposes Columns as a DP. Until then I guess we have to choose the most pragmatic solution.... cheers,
IanR