Constantine Georgiou
Constantine Georgiou

Reputation: 3401

Binding DataGrid Cells to an Indexed Property

I have created an indexed property in a class, and I need to set bindings for each cell of a DataGridTemplateColumn in a DataGrid (each cell to its corresponding item of the indexed property).

It is for a work-schedule generating application. Each cell in the grid will be displaying data relevant to an employee and shift - more employees in the shift will be growing the grid rightwards, ie adding more columns. The grid displays data structures created and filled programmatically, and not bound to a database table. For the purposes of easier presentation and code inspections in this forum, I have reduced everything to just the essentials.

Here is a pic:

enter image description here

This is obviously wrong, as I can't set the bindings properly. I can bind the fields in the StackPanel to the first (or any other) item of the indexed property, but not in an indexed/parameterized setting.

Here is my xaml (part of):

<Grid HorizontalAlignment="Left">
    <DataGrid x:Name="grdSch" HorizontalAlignment="Left" Height="400" VerticalAlignment="Top" Margin="8,0,0,0" AutoGenerateColumns="False" SelectionUnit="Cell"/>
</Grid>
<Page.Resources>
    <DataTemplate x:Key="SPI_Template">
        <StackPanel>
            <TextBlock Text="{Binding Path=shftDate}"/>
            <TextBlock Text="{Binding Path=shftKind}"/>
        </StackPanel>
    </DataTemplate>
    <DataTemplate x:Key="SEI_Template">
        <StackPanel>
            <TextBlock Text="{Binding Path=data[0].emplName}"/>
            <TextBlock Text="{Binding Path=data[0].shftTime}"/>
        </StackPanel>
    </DataTemplate>
</Page.Resources>

And the code generating the columns:

    private void Page_Loaded(object sender, RoutedEventArgs e)
    {
        // Create the grid columns
        DataGridTemplateColumn col = new DataGridTemplateColumn();
        col.Header = "Shift";
        col.CellTemplate = (DataTemplate)FindResource("SPI_Template");
        grdSch.Columns.Add(col);
        for (int i = 0; i < 30; i++)
        {
            col = new DataGridTemplateColumn();
            col.Header = "Employee " + (i + 1);
            col.Width = 100;
            col.CellTemplate = (DataTemplate)FindResource("SEI_Template");
            // for now show only the 5 first elements
            col.Visibility = i >= 5 ? Visibility.Hidden : Visibility.Visible;
            grdSch.Columns.Add(col);
        }
    }

The data[] property is indexed. The bindings as they are now (eg "{Binding Path=data[0].emplName}") work for the 1st item of the property. If I change it to "{Binding Path=data[1].emplName}" it will bind to the 2nd one and so on. But this is not what I need of course. Is there some way to change the binding to something like.... "{Binding Path=data[Grid.ColumnIndex-1].emplName}"? Searched and found some posts about "parameterized binding" etc, but couldn't find something quite obvious or straightforward. Of course I could go the dumb way and add 30 similar templates in the xaml (differing only in the index of the indexed property) - and this will definitely work, but I'm looking into making it in a better way.

Could someone help please?

Thank you in advance

Upvotes: 3

Views: 1208

Answers (1)

Grx70
Grx70

Reputation: 10349

One solution is to create custom column type (deriving from DataGridTemplateColumn) which exposes an additional Binding property, which we will use to bind cell content. Here's the column code:

public class DataGridBoundColumn : DataGridTemplateColumn
{
    public BindingBase Binding { get; set; }

    protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
    {
        var element = base.GenerateEditingElement(cell, dataItem);
        if (element != null && Binding != null)
            element.SetBinding(ContentPresenter.ContentProperty, Binding);
        return element;
    }

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var element = base.GenerateElement(cell, dataItem);
        if (element != null && Binding != null)
            element.SetBinding(ContentPresenter.ContentProperty, Binding);
        return element;
    }
}

Then we need to slightly modify the column generation code:

private void Page_Loaded(object sender, RoutedEventArgs e)
{
    var col = new DataGridTemplateColumn
    {
        Header = "Shift",
        CellTemplate = (DataTemplate)FindResource("SPI_Template")
    };
    grdSch.Columns.Add(col);
    for (int i = 0; i < 30; i++)
    {
        col = new DataGridBoundColumn
        {
            Header = "Employee " + (i + 1),
            Binding = new Binding($"data[{i}]"),
            Width = 100,
            CellTemplate = (DataTemplate)FindResource("SEI_Template"),
        };
        grdSch.Columns.Add(col);
    }
}

Lastly, since our generated columns are already displaying data[i], we need to modify the SEI_Template accordingly:

<DataTemplate x:Key="SEI_Template">
    <StackPanel>
        <TextBlock Text="{Binding emplName}" />
        <TextBlock Text="{Binding shftDate}" />
    </StackPanel>
</DataTemplate>

Upvotes: 2

Related Questions