Hooking up a datacontext to WPF DataGrid colums

Filed in Uncategorized Leave a comment

If you have ever wanted to do any data binding on the columns defined within a wpf data grid, you have probably quickly realized that it is not possible.  The reason why your data bindings don’t work is because the DataGrid.Columns property is attached, and therefore not part of the visual tree.  What that means is that the data context is not automatically passed down from the data grid to the grid columns.  We can, however, emulate the columns being a part of the visual tree by passing the data context down manually.   My initial source of information on how to do this lies here, however, this solution forces you to re-use your data grid’s data context which may not be desirable in all situations.  So instead i employed a slightly more flexible solution so that i could apply any data context i wanted.

I started off by creating a static extension class for the data grid modifications i need to make.  Then i add an attached property to hold the column data context.  Then i created a static constructor so that i could attach the FrameworkElement.DataContextProperty to the grid column class.  Finally, create a property changed handler for the attached property, loop through the grid columns and set the FrameworkElement.DataContextProperty to the new data context on each column.

Now in your xaml, you just need to add a binding to your attached property BELOW your column definitions.  It is imperative that you add this binding below your column definitions, or else your grid won’t contain any columns to update.  The binding syntax on your grid columns is a little more complicated than usual, just take a look at the sample code below to get an idea how to use it.  The only real difference is that you must source yourself as well as path the FrameworkElement.DataContextProperty.

After you assign the column data context property to a binding, your data grid columns will now be able to function just like any other bindable element in the visual tree.   If you just want to push the data grid data context to the columns, simple use an empty binding element instead of specifying the element name, source, or path.

Code

namespace DataGrid.Extensions
{
    using System.Windows;
    using Microsoft.Windows.Controls;

    static class DataGridExtensions
    {
        public static object GetColumnDataContext(DependencyObject obj)
        {
            return (object)obj.GetValue(ColumnDataContextProperty);
        }

        public static void SetColumnDataContext(DependencyObject obj, object value)
        {
            obj.SetValue(ColumnDataContextProperty, value);
        }

        // Using a DependencyProperty as the backing store for ColumnDataContext.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty ColumnDataContextProperty =
            DependencyProperty.RegisterAttached("ColumnDataContext", 
            typeof(object),
            typeof(DataGridExtensions), 
            new UIPropertyMetadata(null, ColumnDataContextChanged));

        static DataGridExtensions()
        {
            FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn));
        }

        private static void ColumnDataContextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            UpdateColumnsDataContext(d as DataGrid, e.NewValue);
        }

        private static void UpdateColumnsDataContext(DataGrid grid, object dataContext)
        {
            if (grid != null)
            {
                foreach (var col in grid.Columns)
                {
                    col.SetValue(FrameworkElement.DataContextProperty, dataContext);
                }
            }
        }
    }
}

XAML

<wpf:DataGrid ItemsSource="{Binding Path=Channels, Mode=OneWay}" AutoGenerateColumns="False">
    <wpf:DataGrid.Columns>
        <wpf:DataGridTextColumn Header="{Binding RelativeSource={RelativeSource Self}, Path=(FrameworkElement.DataContext).DataHeader" Binding="{Binding Path=Data, Mode=OneWay}"/>
    </wpf:DataGrid.Columns>
    <l:DataGridExtensions.ColumnDataContext>
        <Binding ElementName="control" Mode="OneWay" Path="HeaderNames"/>
    </l:DataGridExtensions.ColumnDataContext>
</wpf:DataGrid>

TOP