views:

1152

answers:

4

The column header of the DataGrid is not a FrameWork element for some reason, and so you cannot use bindings to set things like the header text. Please correct me if that is wrong of if that has changed with .NET 4.0 (I am using the latest WPFToolkit from CodePlex now).

I am trying to use the DataGrid for a time sheet presentation where the day's date should be part of the header text (ie, "Sun, Nov 01"), and I have the following in my XAML:

        <dg:DataGrid.Columns>
        <dg:DataGridTextColumn Header="Description" Width="Auto" Binding="{Binding Description}" IsReadOnly="True"/>
        <dg:DataGridTextColumn Header="Mon" Width="50" Binding="{Binding Allocations[0].Amount}"  />
... every other day of the week ....
        <dg:DataGridTextColumn Header="Sun" Width="50" Binding="{Binding Allocations[6].Amount}"  />
        <dg:DataGridTextColumn Header="Total" MinWidth="50" Binding="{Binding TotalAllocatedAmount}" IsReadOnly="True" />
    </dg:DataGrid.Columns>

I'd like to use the same AllocationViewModel I am using for data (ie, "{Binding Allocations[0].Amount}" and bind it's DisplayName property to the header text. Can someone show me how to do that? If I have to use a static resource, how can I get the DataContext in there?

EDIT ---------------- PREFERRED WORK-AROUND

Josh Smith had posted about a DataContextSpy awhile back, and it is the cleanest workaround I have come across to this problem. Here is the class that makes it work:

/// <summary>
/// Workaround to enable <see cref="DataContext"/> bindings in situations where the DataContext is not redily available. 
/// </summary>
/// <remarks>http://blogs.infragistics.com/blogs/josh_smith/archive/2008/06/26/data-binding-the-isvisible-property-of-contextualtabgroup.aspx&lt;/remarks&gt;
public class DataContextSpy : Freezable
{
    public DataContextSpy()
    {
        // This binding allows the spy to inherit a DataContext.
        BindingOperations.SetBinding(this, DataContextProperty, new Binding());
    }

    public object DataContext
    {
        get { return GetValue(DataContextProperty); }
        set { SetValue(DataContextProperty, value); }
    }

    // Borrow the DataContext dependency property from FrameworkElement.
    public static readonly DependencyProperty DataContextProperty = FrameworkElement
        .DataContextProperty.AddOwner(typeof (DataContextSpy));

    protected override Freezable CreateInstanceCore()
    {
        // We are required to override this abstract method.
        throw new NotImplementedException();
    }
}

With this in place, I can hijack the DC I need in xaml:

    <dg:DataGrid.Resources>
        <behavior:DataContextSpy x:Key="spy" DataContext="{Binding Allocations}" />
    </dg:DataGrid.Resources>

And then apply as needed via binding:

            <dg:DataGridTextColumn Header="{Binding Source={StaticResource spy}, Path=DataContext[0].DisplayName}" 
                               Width="50" Binding="{Binding Allocations[0].Amount}"  />

Suh-weet!

+1  A: 

**EDIT :-

You can style the DataGridColumnHeader and do some funky bindings. try here and download the ColumnHeaderBindings.zip, it has a little test project, that is a bit of a hack, but it works

**End Edit

The Binding on the column happens on a per row basis, the column is not part of the visual tree, the binding gets applied to each item in the grid, from the grids source code you can see that the property Binding has these comments

    /// <summary>
    ///     The binding that will be applied to the generated element.
    /// </summary>
    /// <remarks>
    ///     This isn't a DP because if it were getting the value would evaluate the binding.
    /// </remarks>

So binding to the columns does not make much sense, because as you have found out, when you are not part of the visual tree you have no data context.

The same problem exists with the ComboBoxColumn when you want to bind to the items source. You can bind to a StaticResource, but StaticResources dont have a data context either. You could use an object data provider or just instantiate directly in xaml.

but i would just create the columns in code, and set the header. this problem would just go away then.

there is a good article here on the visual layout.

Aran Mulholland
Hi Aran. I saw this [post](http://stackoverflow.com/questions/652240/wpf-toolkit-bind-datagrid-column-header-to-dynamicresource) which seemed to imply you can have dynamic content in a static resource. Would that also solve the problem?
Berryl
have provided a work around solution, see the edit above
Aran Mulholland
How cool! I will need to go over that a few times before I really get it but it obviously does work. Cheers
Berryl
should have put more comments in but the trick is in the multi binding, and its converter, and setting the data context of the datagrid to something meaningful
Aran Mulholland
A: 

Aron's solution works. A (much) more compact and reuseable solution is the one I added at the end of my original post.

HTH,
Berryl

Berryl
+1  A: 

BTW, in Silverlight (tested with SL 3.0) you can simply use the Header property as the DataContext for the ControlTemplate set via HeaderStyle (see my related question on SO).

I just tried this solution in WPF 3.5 using the WPF Toolkit DataGrid and it works!

Philipp Schmid
This is **great** solution!
Lukas Cenovsky
A: 

an even better solution would be to set the binding in the header's style and pass the column as the header's dataContext... (or even better: to set up an object representing the header's dataContext and pass it)

see there for a way to do this :

http://stackoverflow.com/questions/1786773/how-to-set-the-datacontext-on-a-datagrid-column-header

David