Introduction
Wouldn't it be nice if the WPF Grid
control had a 'Padding
' property? In this article, I will show you how to create the PaddedGrid
control, a custom control that derives from Grid
and provides this much needed property.
Here's a picture of the PaddedGrid
being used in the Visual Studio XAML designer window. As you can see, the labels and text boxes are spaced apart from each other by the amount specified in the Padding
property.

Background
I often use Grid
s for laying out data entry sections of forms. Typically, there will be a column of Label
s on the left and a column of fields on the right. As the Grid
control doesn't have a Padding
property, the Margin
of the controls in the Grid
has to be set to an appropriate value to space things out properly. This can clutter up the XAML, and is a bit time consuming. Having a Padding
property on the parent Grid
makes things much cleaner.
Planning the Control
Before I go through the control code step-by-step, I'll explain how the padding will work.
With WPF, there are generally many ways to achieve things like this; each technique will have its own benefits and drawbacks. Ideally, we would wrap each grid cell in a container of some kind, and set the margin on that. This would give the correct layout in all cases, but is needlessly complex. A more simple solution is to have the control do what I have been doing manually - set the margin property of the child controls. In fact, if we can bind the Margin
property of the grid's immediate children to the Padding
property of the parent PaddedGrid
, then we will have what we need.
By binding the children's Margin
to the parent's Padding
(a technique that is commonly used in DataTemplates, ItemTemplates, and so on), we avoid the need to do any complicated custom layout in procedural code. By making Padding
a Dependency Property, we keep the PaddedGrid
consistent with the standard WPF controls.
The PaddedGrid Control
I'll take you through the PaddedGrid
control step-by-step. I've tried to do this in the most logical order for the article; the code file itself is in a slightly different order. Here we go:
public class PaddedGrid : Grid
{
private static readonly DependencyProperty PaddingProperty =
DependencyProperty.Register("Padding",
typeof(Thickness), typeof(PaddedGrid),
new UIPropertyMetadata(new Thickness(0.0),
new PropertyChangedCallback(OnPaddingChanged)));
First, we create the class and derive it from Grid
. Then we create the static Dependency Property for Padding
. Dependency Properties are absolutely critical to WPF; if you are unfamiliar with them, start with the MSDN article: 'Dependency Properties Overview'.
We register the Dependency Property as a Thickness
type. This is the same data type as the Margin
property that most controls have. It allows a padding on the left, right, top, and bottom to be specified (or a single padding for each side). The default value is set to zero, which means that, by default, the PaddedGrid
will look just like a standard grid. One thing to note is that we also specify a PropertyChangedCallback
- this will be called every time Padding
is changed. More on this later.
public PaddedGrid()
{
Loaded += new RoutedEventHandler(PaddedGrid_Loaded);
}
The constructor is very straightforward - it simply adds an event handler for the Loaded
event. It is in the Loaded
event handler that we will bind the Margin
properties of the child controls to the Padding
property of the PaddedGrid
.
void PaddedGrid_Loaded(object sender, RoutedEventArgs e)
{
int childCount = VisualTreeHelper.GetChildrenCount(this);
for (int i = 0; i < childCount; i++)
{
DependencyObject child = VisualTreeHelper.GetChild(this, i);
DependencyProperty marginProperty = GetMarginProperty(child);
if (marginProperty != null)
{
Binding binding = new Binding();
binding.Source = this;
binding.Path = new PropertyPath("Padding");
BindingOperations.SetBinding(child, marginProperty, binding);
}
}
}
We use the VisualTreeHelper
to get the number of immediate children in the visual tree. We can then use a loop and the VisualTreeHelper
to get each child as a DependencyObject. By calling the helper function GetMarginProperty
(which is described later), we attempt to get the Margin
property for the child. If it exists, we create a binding to the PaddedGrid
's Padding
property; then we set it to the Margin
property of the child. This way, each immediate child will have its Margin
property bound to the Padding
of the parent PaddedGrid
.
By using a binding like this, we ensure that changes to the padding at run-time will be correctly reflected in the Margin
of each child control.
protected static DependencyProperty GetMarginProperty(DependencyObject dependencyObject)
{
foreach (PropertyDescriptor propertyDescriptor in
TypeDescriptor.GetProperties(dependencyObject))
{
DependencyPropertyDescriptor dpd =
DependencyPropertyDescriptor.FromProperty(propertyDescriptor);
if (dpd != null && dpd.Name == "Margin")
{
return dpd.DependencyProperty;
}
}
return null;
}
Getting the Margin
dependency property for a dependency object is simple enough. We use the TypeDescriptor
object to iterate through each property. The DependencyPropertyDescriptor.FromProperty
function will get a reference to the dependency property - but only if the property is a dependency property (rather than a standard property). If it isn't, it will return null
. This is the quickest way I found to enumerate the dependency properties.
If we have found a dependency property, we check to see if it is the 'Margin
' property. If it is, we return it. Easy!
This function could be generalised to find any dependency property by name, but I'm keeping things simple in the article to make sure that the logic is easy to follow.
The last thing to do is implement the OnPaddingChanged
callback we set in the constructor of the Padding
dependency property.
private static void OnPaddingChanged(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs args)
{
PaddedGrid paddedGrid = dependencyObject as PaddedGrid;
paddedGrid.UpdateLayout();
}
The callback is very simple. We get the PaddedGrid
that has had its padding changed and force it to update its layout.
Using the PaddedGrid
First, add a reference to the PaddedGrid assembly to your project, then add the namespace in the XAML of the Window
(or Page
) that you will use it in:
<Window x:Class="PaddedGridExample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:paddedGrid="clr-namespace:PaddedGrid;assembly=PaddedGrid"
Title="MainWindow" Height="350" Width="525">
Once you have the namespace defined, you can use the PaddedGrid
(and its Padding
) like so:
<!---->
<paddedGrid:PaddedGrid x:Name="paddedGrid" Padding="2,6,3,6" >
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<!---->
</Grid.RowDefinitions>
<!---->
<Label Grid.Row="0" Grid.Column="0">Name</Label>
<TextBox Grid.Row="0" Grid.Column="1" Text="Enter Name Here" />
<!---->
</paddedGrid:PaddedGrid>
Limitations
I have only used the PaddedGrid
in fairly simple layouts. The trick of setting the margin of the immediate children may not work in every case. This is a simple control for simple scenarios - however, if you have any problems with it, do let me know and I will endeavour to resolve the issue and post updates.
Improvements
If you have any suggestions for improvements, then let me know. I'd be happy to extend the control and the article if there is more that could be done!
The PaddedGrid library and a sample project can be downloaded from the link above. Enjoy!