1

We all fased with problem when we need to hide column of DataGrid by some condition. There are at least two approaches to solve this problem. Bouth of approaches requires proxy element. I use bouth of these approaches. As you see, using of FreezableProxy as proxy element does't require ContentControl, but requires specify additional class (FreezableProxy). Using of FrameworkElement as proxy does't require specify additional class (like FreezableProxy), but requires adding ContentControl to markup.

XAML:

<Window.Resources>
    <SampleRadioBoxCheckedConverter:FreezableProxy x:Key="FreezableProxy" Data="{Binding}"/>
    <FrameworkElement x:Key="FrameworkElement" DataContext="{Binding}"/>
</Window.Resources>

<Grid Margin="10">
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <DataGrid AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name"/>
            <DataGridTextColumn Header="Type" 
                                Visibility="{Binding Data.IsPartnerColumnVisible, Source={StaticResource FreezableProxy}}"/>
            <DataGridTextColumn Header="Number" 
                                Visibility="{Binding DataContext.IsPartnerColumnVisible, Source={StaticResource FrameworkElement}}"/>

        </DataGrid.Columns>
    </DataGrid>
    <ContentControl Grid.Row="1" Content="{StaticResource FrameworkElement}" Visibility="Collapsed"></ContentControl>

</Grid>

Code-behind:

public class FreezableProxy : Freezable
{
    protected override Freezable CreateInstanceCore()
    {
        return new FreezableProxy();
    }

    public object Data
    {
        get { return (object)GetValue(DataProperty); }
        set { SetValue(DataProperty, value); }
    }

    public static readonly DependencyProperty DataProperty =
        DependencyProperty.Register("Data", typeof(object),
                                        typeof(FreezableProxy));
}

public partial class MainWindow : INotifyPropertyChanged
{
    private Visibility _isPartnerColumnVisible = Visibility.Hidden;
    public Visibility IsPartnerColumnVisible
    {
        get
        {
            return _isPartnerColumnVisible;
        }
        set
        {
            _isPartnerColumnVisible = value;
            RaisePropertyChanged("IsPartnerColumnVisible");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
    void RaisePropertyChanged(String prop)
    {
        if (PropertyChanged != null)
            PropertyChanged(this, new PropertyChangedEventArgs(prop));
    }

    public MainWindow()
    {
        InitializeComponent();
        DataContext = this;
    }
}

Bouth approaches look very similiar. Suggest we use second approach (using FrameworkElement). Then I have a question - why I need to specify additional ContentControl if Binding actually set to {StaticResource FrameworkElement}? What a magic is ContentControl doing? When we use FrezableProxy we need not specify any ContentControl-s or something else and it works fine.

UPDATE

<FrameworkElement x:Key="FrameworkElement" DataContext="{Binding}"/>
/////<ContentControl Grid.Row="1" Content="{StaticResource FrameworkElement}" Visibility="Collapsed"></ContentControl>

Why this doesn't work? ContentControl is commented but I set DataContext property of FrameworkElement explicitly.

monstr
  • 1,680
  • 1
  • 25
  • 44

2 Answers2

3

Issue is DataGridColumns doesn't lies in same Visual tree as that of its parent dataGrid. Hence, binding doesn't work because DataContext is not inherited can can't be found using RelativeSource because it depends on Visual Tree.

So, all the approaches mentioned are to pass on DataContext to columns.


Now, to difference between two approaches:

FreezableProxy approach:

Actual source reference for this is here. And magic behind Freezable is defined here, so quoting from the same link:

The primary purpose of Freezable class is to define objects that have a modifiable and a read-only state, but the interesting feature in our case is that Freezable objects can inherit the DataContext even when they’re not in the visual or logical tree.

So, you don't need any ContentControl or any proxy control because with freezable in place we get inherited DataContext automatically.


FrameworkElement + ContentControl approach:

DataContext property is declared with flag FrameworkMetadataOptions.Inherits. What it means that child control inherit it automatically from it's parent element unless set explicitly for child control.

So ContentControl inherit DataContext automatically from Grid and child element of ContentControl will inherit it from ContentControl.That's why FrameworkElement inherit it from ContentControl. (No need to bind DataContext manually). This will work:

<FrameworkElement x:Key="FrameworkElement"/>

Another approach is to use x:Reference as described here in my answer.

Community
  • 1
  • 1
Rohit Vats
  • 79,502
  • 12
  • 161
  • 185
  • Hm, I see `Freezable` class has no `DataContext` property at all. And as I see we don't use this property anyway. We use `Data` property, that added manually. As I understand, `DataContext` of `Window` is extracted from `Data` property and that is the trick. FrameworkElement+ContentControl - I got it, but still have a question about this approach, see update, plz. – monstr Aug 27 '14 at 10:14
  • `DataContext of Window is extracted from Data property and that is the trick` - Yes that's how it works. – Rohit Vats Aug 27 '14 at 11:31
  • `ContentControl is commented but I set DataContext property of FrameworkElement explicitly.` - FrameworkElement like I said inherit it's DataContext from parent(ContentControl) but when you don't apply this resource as child of some control then it doesn't inherit DataContext since it's not added in any Visual and logical tree. – Rohit Vats Aug 27 '14 at 11:34
  • hehe, `Yes that's how it works` - thus, we need `Data` property only to pass `DataContext` object into `Binding`, but if I try to inherit NOT from `Freezable`, just from `DependencyObject` it will not work. <-- this is I don't understand. – monstr Aug 27 '14 at 12:01
  • You need `Data` property to pass any thing you want to pass not only DataContext. As mentioned in answer `Freezable objects can inherit the DataContext even when they’re not in the visual or logical tree` that's why they get DataContext inherited and you can bind Data like this `Data="{Binding}"` but `DependencyObject` doesn't inherit it unless it's added as child of any content control. – Rohit Vats Aug 27 '14 at 12:08
  • thx. I think I got, but it is just strange `can inherit the DataContext` when `Freezable` class has no `DataContext` property :) It was cause of my confuse – monstr Aug 27 '14 at 12:14
0

You are mistaken. The used element does not have to be a ContentControl and can be any FrameworkElement instead. You also seem to be confused about how this method actually works as you are not using the required x:Reference directive in the Binding:

<Grid Margin="10">
    <Grid.RowDefinitions>
        <RowDefinition></RowDefinition>
        <RowDefinition></RowDefinition>
    </Grid.RowDefinitions>
    <DataGrid AutoGenerateColumns="False">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Name"/>
            <DataGridTextColumn Header="Number" 
                Visibility="{Binding DataContext.IsPartnerColumnVisible, 
                Source={x:Reference FrameworkElement}}"/>    
        </DataGrid.Columns>
    </DataGrid>
    <FrameworkElement Name="someElement" Grid.Row="1" Visibility="Collapsed" />    
</Grid>

This problem is caused because the DataGridTextColumn is not part of the main visual tree. We can use the x:Reference directive here because it does not depend on the source element being in the same visual tree as the Binding that it is used in. In short, we are just using this FrameworkElement (which could be any control) here, so that we can access the DataContext from the main visual tree through it.

Sheridan
  • 68,826
  • 24
  • 143
  • 183
  • I think you mean `Source={x:Reference someElement}}` :) It looks good (and works), but this string always cause of "Service provider is missing the INameResolver service" exception in design time :( – monstr Aug 27 '14 at 10:22