WPF Grid Column and Row Hiding






4.91/5 (49 votes)
WPF Grid column and row hiding.
Introduction
How to easily hide columns and rows in a WPF Grid.
Background
The RowDefinition
and ColumnDefinition
are missing an obvious Visibility
property. Why??? Who knows but it seems a rather silly oversight...
There have been various solutions mentioned (find these using Google etc...):
This method is by far the most heavy and quite frankly a waste of processing time... not to mention having to keep track of all the objects in a particular column/row etc...
- Set the
ColumnDefinition
/RowDefinitionWidth
/Height
to "Auto" and then set theVisibility
of each element sitting in that column/row toVisibility.Collapsed
. - Manually setting the
Width
/Height
of the respectiveColumnDefinition
/RowDefinition
to 0...
This method is quick and easy but stumbles when it comes to unhiding - oh no - I forgot to remember what the width was before I set it zero... arghhhh........ now I have to keep track of all the column/row widths/heights or some other method...
Well, to be honest both methods, although they work, they suck...
I want to have my cake and to eat it too... This means I simply want to have a Visible
property on my column and row definitions.
Solution
The solution is surprising easy...
For clarity I will now only refer to a ColumnDefinition
for explanation. The solution is exactly the same for a RowDefinition
. All you will need to do is swap the Width
to Height
and ColumnDefinition
to RowDefinition
, wave your magic wand - tap the screen and hey presto...
Recipe
This recipe will only use a true
/false
for whether the column is visible. The third state of Visibility.Hidden
makes no sense in this context.
- We need a dependency property to add the '
Visible
' to theColumnDefinition
. - Use this new
Visible
property to coerce theWidth
dependency property.
What is coercion??? In this case think of it as a filter that sits on top of the Width
property. I will have it filter the Width
value to either report its original value or report zero depending on if the column is visible or not. It is important to note that coercion does not alter the underlying Width
property. This means that we don't need code trying to remember what the width was before we hide the column.
So lets define a subclass which inherits ColumnDefinition
as thus:
public class ColumnDefinitionExtended : ColumnDefinition
{
}
Now we want to define our Visible
dependency property using some boiler plate code inside our new class:
public class ColumnDefinitionExtended : ColumnDefinition
{
// Variables
public static DependencyProperty VisibleProperty;
// Properties
public Boolean Visible
{
get { return (Boolean)GetValue(VisibleProperty); }
set { SetValue(VisibleProperty, value); }
}
// Constructors
static ColumnDefinitionExtended()
{
VisibleProperty = DependencyProperty.Register("Visible",
typeof(Boolean),
typeof(ColumnDefinitionExtended),
new PropertyMetadata(true, new PropertyChangedCallback(OnVisibleChanged)));
}
// Get/Set
public static void SetVisible(DependencyObject obj, Boolean nVisible)
{
obj.SetValue(VisibleProperty, nVisible);
}
public static Boolean GetVisible(DependencyObject obj)
{
return (Boolean)obj.GetValue(VisibleProperty);
}
}
OK, so that's taken care of.
We've defined the VisibleProperty
as a static DependencyProperty
and provided some getter/setters... We have also provided a static constructor to register the property as required with a default value of true and providing a property changed callback.
The default value true is so that the column starts off by default as visible. The only thing missing now is the OnVisibleChanged
callback code.
Now before we get there we need to put in some secret sauce and do a property override to be able to coerce the Width
property.
This can be done by adding one more line to the static constructor as thus:
static ColumnDefinitionExtended()
{
VisibleProperty = DependencyProperty.Register("Visible",
typeof(Boolean),
typeof(ColumnDefinitionExtended),
new PropertyMetadata(true, new PropertyChangedCallback(OnVisibleChanged)));
ColumnDefinition.WidthProperty.OverrideMetadata(typeof(ColumnDefinitionExtended),
new FrameworkPropertyMetadata(new GridLength(1, GridUnitType.Star), null,
new CoerceValueCallback(CoerceWidth)));
}
We have now overridden the normal ColumnDefinition.WidthProperty
to provide a CoerceValueCallback
function. This will enable the overriding of the Width
property based on our requirements. In this case I will override the value if the visibility is set to false.
So we now have two routines to go... OnVisibleChanged
and CoerceWidth
.
The OnVisibleChanged
is really a one-liner telling the ColumnDefinition.WidthProperty
to perform its coercion.
static void OnVisibleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
obj.CoerceValue(ColumnDefinition.WidthProperty);
}
So when we change the Visible
property to either true
or false
this one liner forces the WidthProperty
to be coerced. I.e., the WidthProperty
will be filtered against our requirements.
So here is the last piece of our puzzle...
static Object CoerceWidth(DependencyObject obj, Object nValue)
{
return (((ColumnDefinitionExtended)obj).Visible) ? nValue : new GridLength(0);
}
This is a simple test. If our Visible
property is set to true then return the width as is... If the visible is set to false then set the width to zero (thus hiding the column).
You might ask that if I set the width to zero when I 'hide' then when I set the visible back to true 'unhide' how does the width get set back to its original value. As mentioned above the coercion does not overwrite the width value. The width value remains unchanged. The coercion simple alters the value that you 'see' from the WidthProperty
. Remember it acts like a filter.
As an update to this article I have also included code in the below segment to override the MinWidth
property due to a comment made about this being a problem. It is the same sort of thing as overriding the main Width
property. Couple of extra lines of code...
So there we have it - The complete solution for column hiding is as follows:
using System;
using System.Windows;
using System.Windows.Controls;
namespace MyProject.MyNamespace.Extended
{
public class ColumnDefinitionExtended : ColumnDefinition
{
// Variables
public static DependencyProperty VisibleProperty;
// Properties
public Boolean Visible
{
get { return (Boolean)GetValue(VisibleProperty); }
set { SetValue(VisibleProperty, value); }
}
// Constructors
static ColumnDefinitionExtended()
{
VisibleProperty = DependencyProperty.Register("Visible",
typeof(Boolean),
typeof(ColumnDefinitionExtended),
new PropertyMetadata(true, new PropertyChangedCallback(OnVisibleChanged)));
ColumnDefinition.WidthProperty.OverrideMetadata(typeof(ColumnDefinitionExtended),
new FrameworkPropertyMetadata(new GridLength(1, GridUnitType.Star), null,
new CoerceValueCallback(CoerceWidth)));
ColumnDefinition.MinWidthProperty.OverrideMetadata(typeof(ColumnDefinitionExtended),
new FrameworkPropertyMetadata((Double)0, null,
new CoerceValueCallback(CoerceMinWidth)));
}
// Get/Set
public static void SetVisible(DependencyObject obj, Boolean nVisible)
{
obj.SetValue(VisibleProperty, nVisible);
}
public static Boolean GetVisible(DependencyObject obj)
{
return (Boolean)obj.GetValue(VisibleProperty);
}
static void OnVisibleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
obj.CoerceValue(ColumnDefinition.WidthProperty);
obj.CoerceValue(ColumnDefinition.MinWidthProperty);
}
static Object CoerceWidth(DependencyObject obj, Object nValue)
{
return (((ColumnDefinitionExtended)obj).Visible) ? nValue : new GridLength(0);
}
static Object CoerceMinWidth(DependencyObject obj, Object nValue)
{
return (((ColumnDefinitionExtended)obj).Visible) ? nValue : (Double)0;
}
}
}
I will leave it as an exercise for you to produce the corresponding RowDefinitionExtended
class... (should take all of about 10-30 seconds depending on how fast you can cut/paste and find/replace...)
Examples
So let's look at an example of how to use the code...
At the top of your XAML file, stick in a namespace definition of where you have the classes defined. E.g...
xmlns:extended="clr-namespace:MyProject.MyNamespace.Extended"
Here is an ordinary Grid
with three columns and three rows... Nothing really to note here... This is what it looks like before you upgrade it to using the new definitions.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
</Grid>
So here is the same Grid using the new extended column and row definitions.
<Grid>
<Grid.ColumnDefinitions>
<extended:ColumnDefinitionExtended />
<extended:ColumnDefinitionExtended />
<extended:ColumnDefinitionExtended />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<extended:RowDefinitionExtended />
<extended:RowDefinitionExtended />
<extended:RowDefinitionExtended />
</Grid.RowDefinitions>
</Grid>
Not much really happening - but we can do things like:
<Grid>
<Grid.ColumnDefinitions>
<extended:ColumnDefinitionExtended Visible="False" />
Or we could databind the Visible
property to a datacontext/viewmodel...
<Grid DataContext="Something">
<Grid.ColumnDefinitions>
<extended:ColumnDefinitionExtended Visible="{Binding ColumnVisible}"/>
Or programmatically:
((ColumnDefinitionExtended)m_MyGrid.ColumnDefinitions[SomeColumnNumber]).Visible = false;
So there we have it - Our cake and we get to eat it too.....
Addendum
As an addendum to this article, in the comments section I proposed a recipe to convert to using the full Visibility
tristate (ie Visible, Hidden, Collapsed) vs this solutions Visible
true/false.
I've post the solution to this recipe as an additional download. Please read the comments section under the subject line 'Width of Grid still large through hiding a row' to follow the discussion.
People may find this solution better fits their specific requirements in their grid behaviour/usage.