2

I have a DataGrid bound to an ICollectionView in my ViewModel. The DataGrid is inside a UserControl which is used in a few different data scenarios, some of which require certain DataGrid columns while others don't.

I just want to bind the DataGridTemplateColumn's Visibility property to the inner label's Content property so if none of the rows contain a value, it will be hidden. I have a String to Visibility converter set, but can't figure out how to find the inner lable's Content property.

<DataGridTemplateColumn Header="Groups" Width="*" CanUserSort="True" SortMemberPath="Groups" Visibility="{Binding ElementName=lbl, Path=Content, Converter={StaticResource StringToVisibilityConverter}}">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Label Name="lbl" Content="{Binding Path=Groups}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>

Any suggestions?

SliverNinja - MSFT
  • 31,051
  • 11
  • 110
  • 173
Aaron
  • 93
  • 2
  • 2
  • 6
  • Anyone looking for answer, refer to http://stackoverflow.com/questions/7955318/bind-datagridtemplatecolumn-visibility – Milan Raval Feb 11 '15 at 15:09

5 Answers5

4

I read somewhere on Stack Overflow(can't find exact post) that the DataGridColumn's aren't assigned a data context because they aren't a FrameworkElement. To get around this, I had to use code similiar to this:

    <DataGridTemplateColumn 
         Header="Groups" 
         Width="*" 
         CanUserSort="True" 
         SortMemberPath="Groups" 
         Visibility"{Binding RelativeSource={x:Static RelativeSource.Self}, 
                        Path=(FrameworkElement.DataContext).IsGroupsVisible, 
                        Converter={StaticResource booleanToVisiblityConverter}}">
         <DataGridTemplateColumn.CellTemplate>         
              <DataTemplate>             
                   <Label Name="lbl" Content="{Binding Path=Groups}" />         
              </DataTemplate>     
         </DataGridTemplateColumn.CellTemplate> 
    </DataGridTemplateColumn> 

Where 

<UserControl.Resources>
    <BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter" /> 
</UserControl.Resources>
Andy.Streeval
  • 49
  • 1
  • 5
  • 2
    Hi Andy, interesting. Not getting any binding errors now but it's still not working. The Converter isn't even being called... – Aaron Mar 17 '11 at 17:24
  • Is Groups a property on the ViewModel that the DataGrid is bound to? Or is it a property on the individual items that the rows in the DataGrid are bound to? I assume it is both, since you would want to display the text in the individual cells, and the Visibility for the column in the grid can only be bound to one property. – Andy.Streeval Mar 17 '11 at 17:36
  • It is a property on the individual items in an ObservableCollection which is set to the Source of a CollectionViewSource and is returned to the DataGrid as an ICollectionView. I have also tried binding it to a property right on the ViewModel called IsGroupsVisible which is less elegant, but it doesn't pick that up either. – Aaron Mar 17 '11 at 17:39
  • I've changed the above code to use the IsGroupsVisible property. – Andy.Streeval Mar 17 '11 at 17:44
  • It's not even calling the ViewModel property! I'm starting to think this is a WPF bug. When I place the same code: Visibility="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(FrameworkElement.DataContext).IsGroupVisible, Converter={StaticResource BoolToVisibilityConverter}}" on any other control in my View, it works perfectly. – Aaron Mar 17 '11 at 17:52
  • Yeah, you may want to download a tool like Snoop at http://snoopwpf.codeplex.com/. If I can't troubleshoot the binding error(which you don't have) from the Output window. Usually my next step is putting break points in the Getter's of the property to make sure they are getting hit. After that I fallback to the Snoop utility, which allows you to see DataContext's of your UserControl/Controls. – Andy.Streeval Mar 17 '11 at 17:59
  • Thanks for the tip Andy. Interestingly, Snoop is saying that the DataContext for this column is equal to "Groups", which is what the Header property is set to. So I have no clue how it's getting that as it's DataContext and since you can't directly define a DataContext for DataGridTemplateColumn, I'm at a loss. – Aaron Mar 17 '11 at 20:15
  • like Andy said, DataGridColumns you are define are not inherit the datacontext. so if your datagrid (<-- DATAGRID!) has the the datacontext (<-- not itemssource:) you want, with your property with the information for visibility, then {Binding RelativeSource={x:Static RelativeSource.Self},Path=(FrameworkElement.DataContext).YOURPROPERTYFORVISIBILITY, Converterstuff...} is correct. for testing you can put this binding to your Header Property: Header={Binding...} so you can where your binding goes to. – blindmeis Mar 18 '11 at 12:01
  • If the Converter isn't being called, you need to include the [`DataGridContextHelper`](http://blogs.msdn.com/b/jaimer/archive/2008/11/22/forwarding-the-datagrid-s-datacontext-to-its-columns.aspx) – SliverNinja - MSFT Jan 16 '12 at 21:44
2

To use RelativeSource.Self as a RelativeSource binding for a DataGridTemplateColumn - you need to add the DataGridContextHelper to your application. This is still required for the WPF 4 DataGrid.

<DataGridTemplateColumn 
         Header="Groups" 
         Width="*" 
         CanUserSort="True" 
         SortMemberPath="Groups" 
         Visibility"{Binding RelativeSource={x:Static RelativeSource.Self}, 
                        Path=(FrameworkElement.DataContext).IsGroupsVisible, 
                        Converter={StaticResource booleanToVisiblityConverter}}">
         <DataGridTemplateColumn.CellTemplate>         
              <DataTemplate>             
                   <Label Name="lbl" Content="{Binding Path=Groups}" />         
              </DataTemplate>     
         </DataGridTemplateColumn.CellTemplate> 
    </DataGridTemplateColumn> 
SliverNinja - MSFT
  • 31,051
  • 11
  • 110
  • 173
1

One hundred thanks to SliverNinja and this article DataGridContextHelper. Links to source code already not working and was not able download, so i wrote my own Attached Proeprty to make it work for all possible cases (DataContext changed, Attached Property value changed, Column added)

My application use DataGrid with AutoGenerateColumns=False and use DataGridTemplateColumn, so DataContext was set before columns added to grid.

Here is Attached Property class:

public sealed class DataGridColumnDataContextForwardBehavior
{
    private DataGrid dataGrid = null;

    public DataGridColumnDataContextForwardBehavior(DataGrid dataGrid)
    {
        this.dataGrid = dataGrid;

        dataGrid.Columns.CollectionChanged += DataGridColumns_CollectionChanged;
    }

    private void DataGridColumns_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        var IsDataContextForwardingEnabled = GetIsDataContextForwardingEnabled(dataGrid);

        if (IsDataContextForwardingEnabled && dataGrid.DataContext != null)
        {
            if (e.Action == NotifyCollectionChangedAction.Add)
            {
                foreach (DataGridColumn column in e.NewItems)
                {
                    column.SetValue(FrameworkElement.DataContextProperty, dataGrid.DataContext);
                }
            }
        }
    }

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

    public static readonly DependencyProperty IsDataContextForwardingEnabledProperty = 
        DependencyProperty.RegisterAttached("IsDataContextForwardingEnabled", typeof(bool), typeof(DataGridColumnDataContextForwardBehavior), 
            new FrameworkPropertyMetadata(false, OnIsDataContextForwardingEnabledChanged));

    public static void OnDataContextChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        DataGrid dataGrid = obj as DataGrid;
        if (dataGrid == null) return;

        var IsDataContextForwardingEnabled = GetIsDataContextForwardingEnabled(dataGrid);
        if (IsDataContextForwardingEnabled)
        {
            foreach (DataGridColumn col in dataGrid.Columns)
            {
                col.SetValue(FrameworkElement.DataContextProperty, e.NewValue);
            }
        }
    }

    static void OnIsDataContextForwardingEnabledChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        var dataGrid = obj as DataGrid;
        if (dataGrid == null) return;

        new DataGridColumnDataContextForwardBehavior(dataGrid);

        if (!(e.NewValue is bool)) return;

        if ((bool)e.NewValue && dataGrid.DataContext != null)
            OnDataContextChanged(obj, new DependencyPropertyChangedEventArgs(FrameworkElement.DataContextProperty, dataGrid.DataContext, dataGrid.DataContext));
    }

    public static bool GetIsDataContextForwardingEnabled(DependencyObject dataGrid)
    {
        return (bool)dataGrid.GetValue(IsDataContextForwardingEnabledProperty);
    }

    public static void SetIsDataContextForwardingEnabled(DependencyObject dataGrid, bool value)
    {
        dataGrid.SetValue(IsDataContextForwardingEnabledProperty, value);
    }
}

Another non obvious things is how to properly use binding for DataGridTemplateColumn:

<DataGrid bhv:DataGridColumnDataContextForwardBehavior.IsDataContextForwardingEnabled="True">
<DataGrid.Columns>
<DataGridTemplateColumn Visibility="{Binding Path=DataContext.Mode, RelativeSource={RelativeSource Self}, Converter={StaticResource SharedFilesModeToVisibilityConverter}, ConverterParameter={x:Static vmsf:SharedFilesMode.SharedOut}}"/>

It is important to use Path=DataContext.MyProp and RelativeSource Self

Only thing i don't like in current implementation - to handle DataGrid.Columns.CollectionChanged event i create instance of my class and do not keep reference for it. So in theory GC may kill it within the time, not sure how to handle it correctly at present moment, will think on it and update my post later. Any ideas and critique are welcome.

1

This would be better achieved going through the Groups property on the ViewModel; since that is ultimately what the Label is using anyways.

<DataGridTemplateColumn Header="Groups" Width="*" CanUserSort="True" SortMemberPath="Groups" Visibility="{Binding Groups, Converter={StaticResource SomeConverter}}">
    <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
            <Label Name="lbl" Content="{Binding Path=Groups}" />
        </DataTemplate>
    </DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
Aaron McIver
  • 24,527
  • 5
  • 59
  • 88
  • Hi Guys, I already tried that and get this error at runtime: System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Groups; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=38907373); target property is 'Visibility' (type 'Visibility') – Aaron Mar 17 '11 at 16:04
  • Frustratingly, this actually worked when I was using a CollectionViewSource defined in XAML by setting the Visibility Binding Source to the ColletionViewSource. I had to move the CollectionViewSource into the ViewModel to deal with sorting/filtering and it broke this Visibility binding. Any other ideas? – Aaron Mar 17 '11 at 16:12
  • CollectionViewSource is the XAML proxy of a CollectionView. In your ViewModel return a ICollectionView ala...CollectionViewSource.GetDefaultView(your collection) – Aaron McIver Mar 17 '11 at 16:21
  • Sorry, my mistake, that's what I am returning from my ViewModel for the ItemsSource of my DataGrid, and it's throwing that "System.Windows.Data Error: 2" on the Visibility Binding. I have even tried binding to a dedicated property in the ViewModel and it still can't find it. – Aaron Mar 17 '11 at 16:36
  • This is at compile time...or runtime? Is it hitting your Converter? – Aaron McIver Mar 17 '11 at 16:40
0

You can't do this. Binding/name resolution doesn't work this way. Why not, instead of a StringToVisibilityConverter create a CollectionToVisibilityConverter which examines the data source (possibly passing in the column/property to examine), looks to see if that column/property is completely empty, and then convert that to a Visibility?