views:

117

answers:

2

Does anyone know how I can do the equivalent XAML binding in code?

<DataGrid ... >
    <DataGrid.Columns>
        <DataGridTextColumn 
            Binding="{Binding Description}"  <=== set in code **
        />
    </DataGrid.Columns>
</DataGrid>

Cheers,
Berryl

=== UPDATE ====

It looks like the method I have been looking for is DataGridColumn.GenerateElement

If so, then the focus of this question is now how to set the Binding correctly. The reason I want to do this code is that my grid has 7 columns that are identical visually and whose data can be known by an index.

So I want to be able to simplify the xaml by using a subclass DataGridTextColumn which has an index property, and just have:

<DataGrid ... >
    <DataGrid.Columns >
        <local:DayOfWeekColumn Index="0" />
        <local:DayOfWeekColumn Index="1" />
        ....
        <local:DayOfWeekColumn Index="7" />
    </DataGrid.Columns >
</DataGrid >

=== REVISED QUESTION ===

Assuming the Binding itself is logically and syntactically correct, what should the parameters to BindingOperations.SetBinding be??

    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem) {
        var activity = (ActivityViewModel)dataItem;
        var cellData = activity.Allocations[Index];
        var b = new Binding
                {
                    Source = cellData,
                    UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
                    Converter = new AllocationAmountConverter()
                };


        BindingOperations.SetBinding(??, ??, b);
        return ??;
    }

=== EDITS for ARAN =====

I am not overriding GenerateElement right now, but rather trying to get a static helper to set my binding for me. The helper is needed in any event to compensate for not being able to bind Header content in the current implementation of MSFT's DataGrid.

Basically the idea is to catch the DC from the grid and use it as necessary on each of the columns, which in this case would be the Header content, cell style, and Binding. Here is the code:

public class TimesheetDataGridColumnContextHelper
{

    static TimesheetDataGridColumnContextHelper() {
        FrameworkElement.DataContextProperty.AddOwner(typeof (DataGridTextColumn));
        FrameworkElement.DataContextProperty.OverrideMetadata(
            typeof (DataGrid), 
            new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.Inherits, OnDataContextChanged));
    }

    public static void OnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var grid = d as DataGrid;
        if (grid == null || !grid.Name.Equals("adminActivityGrid")) return;

        foreach (var col in grid.Columns) {

            var dowCol = col as DayOfTheWeekColumn;
            if (dowCol == null) continue;

            var context = (IActivityCollectionViewModelBase) e.NewValue;
            var index = Convert.ToInt32(dowCol.DowIndex);

            _setHeader(dowCol, context, index);

            var editStyle = (Style) grid.FindResource("GridCellDataEntryStyle");
            dowCol.CellStyle = editStyle;

            _setBinding(dowCol, index, context);

        }
    }

    private static void _setBinding(DayOfTheWeekColumn dowCol, int index, IActivityCollectionViewModelBase context) {
        dowCol.Binding = new Binding
                         {
                             Path = new PropertyPath(string.Format("Allocations[{0}]", index)),
                             UpdateSourceTrigger = UpdateSourceTrigger.LostFocus,
                             Converter = new AllocationAmountConverter()
                         };
    }

    private static void _setHeader(DataGridColumn col, IActivityCollectionViewModelBase context, int index)
    {
        var date = context.HeaderDates[index];
        var tb = new TextBlock
                 {
                     Text = date.ToString(Strings.ShortDayOfWeekFormat),
                     ToolTip = date.ToLongDateString()
                 };
        col.Header = tb;
    }

}

}

Everything works except for the Binding. I can't tell if it's because my binding is wrong somehow (although I get no obvious errors) or this is not a good place to set it. The grid columns are just empty when I run it.

Any idea??

Cheers,
Berryl

=== FIXED! ===

The logic in the last update was actually correct, but getting lost in the internals of the DataGrid I missed that my Binding.Path was missing the property to be bound to! Credit to Aran for understanding the issue, realizing that GenerateElement overrides were not necessary, and catching that the Binding Source should not have been set.

A: 

Based on MSDN, it sounds like the first parameter of SetBinding() should be the control that you want to display the binding in (this in this case, assuming that GenerateElement() is a member of the DayOfWeekColumn class), and the second property is the property to bind the data to. I haven't used the WPF DataGrid very much, but I didn't see anything like a text property to set.

I do see that the DataGridTextColumn does have a Binding property, though. Maybe it would work to set it to the binding you created manually above?

Andy
Great thought, and my original intention was to do just that, but it is somehow making the method get go into an infinite loop. What a PITA - thanks for the reply!
Berryl
+1  A: 

You're always doing the fiddly grid bits eh Beryl?

Do a couple of things. Use reflector to look at the implementation of GenerateElement in the DataGridTextColumn. (.NET programmers live in reflector)

Now for the answer:

In the datagrid each column is not part of the visual tree. The column has two methods GenerateElement and GenerateEditingElement. These methods return the viewer and the editor for the cell respectively. In your method above you are not creating the viewer, which will probably be a TextBlock.

from reflector, the implementation of GenerateElement is as below, notice the first thing they do is create the viewer for the cell.

protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
    TextBlock e = new TextBlock();
    this.SyncProperties(e);
    base.ApplyStyle(false, false, e);
    base.ApplyBinding(e, TextBlock.TextProperty);
    return e;
}

Once you have a textblock you can use the line below to set the binding on it.

BindingOperations.SetBinding(textBlock, TextBlock.TextProperty, binding);

I am not however convinced that you actually need to override the GenerateElement and GenerateEditingElement to get your desired effect. I think you could overide the Binding property of the base class and just modify the binding there with your extra field whenever it is set. This will mean everything else will just work and you wont end up removing functionality from your column. Once again a crawl through reflector looking at the class DataGridBoundColumn (the abstract base class) would be beneficial.

I do something similiar in one of our columns whenever a binding is set I modify the clipboard binding by adding an extra property so I can copy and paste effectively.

EDIT: Update...this should probably be another question but..

You are explicitly setting the source of the binding in your setBinding method. In the grid the source of the binding is the data contained in the row. You are setting it, which means it would be the same for each row. You can apply these funky bindings without the source property before the data context is set, the source becomes the item in each row, and your binding should reflect an index into the property held in each row.

Aran Mulholland
Hi Aran. Yes, my fiddle is getting a workout on this one but it seems worth having a go at it. Please see my last edit ("EDIT for ARAN") above for my latest approach. Cheers!
Berryl
Got it. Was a lame syntax error in my binding Path (should have been "Allocations[{0}].Amount") Thanks for your help!
Berryl