views:

14906

answers:

3

My WPF application generates sets of data which may have a different number of columns each time. Included in the output is a description of each column that will be used to apply formatting. A simplified version of the output might be something like:

class Data
{
    IList<ColumnDescription> ColumnDescriptions { get; set; }
    string[][] Rows { get; set; }
}

This class is set as the DataContext on a WPF DataGrid but I actually create the columns programmatically:

for (int i = 0; i < data.ColumnDescriptions.Count; i++)
{
    dataGrid.Columns.Add(new DataGridTextColumn
    {
        Header = data.ColumnDescriptions[i].Name,
        Binding = new Binding(string.Format("[{0}]", i))
    });
}

Is there any way to replace this code with data bindings in the XAML file instead?

A: 

You might be able to do this with AutoGenerateColumns and a DataTemplate. I'm not positive if it would work without a lot of work, you would have to play around with it. Honestly if you have a working solution already I wouldn't make the change just yet unless there's a big reason. The DataGrid control is getting very good but it still needs some work (and I have a lot of learning left to do) to be able to do dynamic tasks like this easily.

Bryan Anderson
My reason is that coming from ASP.Net I'm new to what can be done with decent data binding and I'm not sure where it's limits are. I'll have a play with AutoGenerateColumns, thanks.
Generic Error
+7  A: 

I've continued my research and have not found any reasonable way to do this. The Columns property on the DataGrid isn't something I can bind against, in fact it's read only.

Bryan suggested something might be done with AutoGenerateColumns so I had a look. It uses simple .Net reflection to look at the properties of the objects in ItemsSource and generates a column for each one. Perhaps I could generate a type on the fly with a property for each column but this is getting way off track.

Since this problem is so easily sovled in code I will stick with a simple extension method I call whenever the data context is updated with new columns:

public static void GenerateColumns(this DataGrid, IEnumerable<ColumnSchema> columns)
{
    dataGrid.Columns.Clear();

    int index = 0;
    foreach (var column in columns)
    {
        dataGrid.Columns.Add(new DataGridTextColumn
        {
            Header = column.Name,
            Binding = new Binding(string.Format("[{0}]", index++))
        });
    }
}

// E.g. myGrid.GenerateColumns(schema);
Generic Error
+1  A: 

You can create a usercontrol with the grid definition and define 'child' controls with varied column definitions in xaml. The parent needs a dependency property for columns and a method for loading the columns:

Parent:


{ // public ObservableCollection gridColumns {
get { return (ObservableCollection)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); }

    }

    public static readonly DependencyProperty ColumnsProperty =
    DependencyProperty.Register("gridColumns", typeof(ObservableCollection<DataGridColumn>),
        typeof(parentControl), new PropertyMetadata(new ObservableCollection<DataGridColumn>()));

public void LoadGrid() { if (gridColumns.Count > 0) myGrid.Columns.Clear();

        foreach (DataGridColumn c in gridColumns)
        {
            myGrid.Columns.Add(c);
        }
    }

Child Xaml:


        <local:parentControl.gridColumns>

            <toolkit:DataGridTextColumn Width="Auto" Header="Child1"   Binding="{Binding Path=.}" />
            <toolkit:DataGridTextColumn Width="Auto" Header="Child2"   Binding="{Binding Path=.}" />
        </local:parentControl.gridColumns>


    </local:parentControl>


And finally, the tricky part is finding where to call 'LoadGrid'. I am struggling with this but got things to work by calling after 'InitalizeComponent' in my window constructor: (childGrid is x:name in window.xaml)

childGrid.deGrid.LoadGrid();

related blog entry

Andy