How to hide wpf datagrid columns depending on a property
I have the following WPF sample program:
Xaml:
<Window x:Class="AncestorArie.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="BoolToVis" />
</Window.Resources>
<Grid>
<DataGrid AutoGenerateColumns="False" Name="Blumen"
ItemsSource="{Binding Leaves}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Color}"
Header="Farbe" Width="160" />
<DataGridTextColumn Binding="{Binding Size}"
Header="Größe" Width="60"
Visibility="{Binding Path=DataContext.Flag,
RelativeSource={RelativeSource Findancestor,
AncestorType={x:Type Window}},
Converter={StaticResource BoolToVis}}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Flowers rose = new Flowers();
rose.Leaves = new ObservableCollection<Leaf>();
rose.Flag = false;
Leaf L1 = new Leaf();
L1.Color = "rot";
L1.Size = 3;
rose.Leaves.Add(L1);
Leaf L2 = new Leaf();
L2.Color = "gelb";
L2.Size = 2;
rose.Leaves.Add(L2);
this.DataContext = rose;
}
}
And the model classes are:
public class Leaf
{
public string Color { get; set; }
public int Size { get; set; }
}
public class Flowers
{
public bool Flag { get; set; }
public ObservableCollection<Leaf> Leaves { get; set; }
}
As you can see, I want to hide the 2nd datagrid column, if the Flag
property is set to false. But it doesn't work. I get the following binding error in the Visual Studio Output window:
System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Flag; DataItem=null; target element is 'DataGridTextColumn' (HashCode=44856655); target property is 'Visibility' (type 'Visibility')
What is wrong in my code concerning the Visibility
attribute?
A column in a datagrid is an abstract object which does not appear in the visual tree, thus you cannot use RelativeSource
-binding, ElementName
will not work either since it will not find a governing FrameworkContentElement so you are in kind of a bind.
One way that works is via Source
and x:Reference
, for that you will need to name your window and move the column to its resources to avoid a cyclical dependency error:
<Window Name="_window" ...>
<Window.Resources>
<DataGridTextColumn x:Key="ThatPeskyColumn"
Binding="{Binding Size}"
Visibility="{Binding DataContext.Flag, Source={x:Reference _window}, Converter={StaticResource BoolToVis}}"/>
</Window.Resources>
<!-- ... -->
<DataGrid AutoGenerateColumns="False" Name="Blumen"
ItemsSource="{Binding Leaves}">
<DataGrid.Columns>
<StaticResource ResourceKey="ThatPeskyColumn"/>
<!-- ... -->
Great fun.
I would prefer a more elegant approach which involves using a Freezable
.
<Window.Resources>
<DiscreteObjectKeyFrame x:Key="FlagKey" Value="{Binding Flag}"/>
</Window.Resources>
<DataGridTextColumn ... Visibility="{Binding Value, Source={StaticResource FlagKey}, ...}" />
Visibility on DataGridTextColumn is not a DependencyProperty and can't be databound. Use a DataGridTemplateColumn and bind the visibility of the controls within the template.
Edit: Actually, this statement only applies to silverlight. See this other SO question for further details.
How to bind DataGridColumn.Visibility?
I asked about the easiest way to tell whether a property is a dependency here.
How can I most easily determine whether a property is a dependency property?
Solution proposed by H.B. is really good and has true WPF MVVM spirit. Use it where possible.
In my particular case something went wrong so I came out with different way, as my project is not strict MVVM, so I can use coded solution.
In CustomView.xaml name assigned to column:
<DataGrid>
<DataGrid.Columns>
<DataGridTemplateColumn x:Name="MachinesColumn" ... />
...
In CustomView.xaml.cs we have a simple property which directly changes visibility of column:
public Visibility MachinesColumnVisible
{
get { return MachinesColumn.Visibility; }
set
{
if (value == MachinesColumn.Visibility)
return;
MachinesColumn.Visibility = value;
}
}