Click here to Skip to main content
15,867,308 members
Articles / Desktop Programming / WPF

WPF Padded Grid

Rate me:
Please Sign up or sign in to vote.
4.88/5 (13 votes)
6 Sep 2010CPOL5 min read 83.8K   1.2K   25   11
A padded grid for WPF that is ideal for laying out forms.

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.

Designer.png

Background

I often use Grids for laying out data entry sections of forms. Typically, there will be a column of Labels 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:

C#
public class PaddedGrid : Grid
{
    /// <summary>
    /// The internal dependency property object for the 'Padding' property.
    /// </summary>
    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.

C#
public PaddedGrid()
{
    //  Add a loded event handler.
    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.

C#
void PaddedGrid_Loaded(object sender, RoutedEventArgs e)
{
    //  Get the number of children.
    int childCount = VisualTreeHelper.GetChildrenCount(this);

    //  Go through the children.
    for (int i = 0; i < childCount; i++)
    {
        //  Get the child.
        DependencyObject child = VisualTreeHelper.GetChild(this, i);

        //  Try and get the margin property.
        DependencyProperty marginProperty = GetMarginProperty(child);

        //  If we have a margin property, bind it to the padding.
        if (marginProperty != null)
        {
            //  Create the binding.
            Binding binding = new Binding();
            binding.Source = this;
            binding.Path = new PropertyPath("Padding");

            //  Bind the child's margin to the grid's 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.

C#
protected static DependencyProperty GetMarginProperty(DependencyObject dependencyObject)
{
    //  Go through each property for the object.
    foreach (PropertyDescriptor propertyDescriptor in 
                TypeDescriptor.GetProperties(dependencyObject))
    {
        //  Get the dependency property descriptor.
        DependencyPropertyDescriptor dpd = 
           DependencyPropertyDescriptor.FromProperty(propertyDescriptor);

        //  Have we found the margin?
        if (dpd != null && dpd.Name == "Margin")
        {
            //  We've found the margin property, return it.
            return dpd.DependencyProperty;
        }
    }

    //  Failed to find the margin, return null.
    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.

C#
private static void OnPaddingChanged(DependencyObject dependencyObject, 
               DependencyPropertyChangedEventArgs args)
{
    //  Get the padded grid that has had its padding changed.
    PaddedGrid paddedGrid = dependencyObject as PaddedGrid;

    //  Force the layout to be updated.
    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:

XML
<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:

XML
<!-- The padded grid. -->
<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" />
        <!-- ...etc... -->
     </Grid.RowDefinitions>

     <!-- Some simple fields. -->
     <Label Grid.Row="0" Grid.Column="0">Name</Label>
     <TextBox Grid.Row="0" Grid.Column="1" Text="Enter Name Here" />

     <!-- More controls etc etc -->

</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!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United Kingdom United Kingdom
Follow my blog at www.dwmkerr.com and find out about my charity at www.childrenshomesnepal.org.

Comments and Discussions

 
QuestionSetting the column/row/columnspan/rowspan of a control programmaticly Pin
Andy Pandy Cartwright15-Apr-15 22:23
Andy Pandy Cartwright15-Apr-15 22:23 
QuestionDesigner support? Pin
chraas21-Mar-11 2:30
chraas21-Mar-11 2:30 
QuestionEven simpler? Pin
Jürgen Röhr16-Sep-10 22:24
professionalJürgen Röhr16-Sep-10 22:24 
AnswerRe: Even simpler? Pin
Dave Kerr17-Sep-10 0:55
mentorDave Kerr17-Sep-10 0:55 
GeneralMy vote of 5 Pin
Eric Xue (brokensnow)6-Sep-10 23:22
Eric Xue (brokensnow)6-Sep-10 23:22 
QuestionAnother soloution? Pin
seesharper6-Sep-10 20:22
seesharper6-Sep-10 20:22 
AnswerRe: Another soloution? Pin
Dave Kerr6-Sep-10 20:25
mentorDave Kerr6-Sep-10 20:25 
GeneralMy vote of 5 Pin
sam.hill6-Sep-10 15:50
sam.hill6-Sep-10 15:50 
GeneralNot being funny but... Pin
Sacha Barber6-Sep-10 3:51
Sacha Barber6-Sep-10 3:51 
GeneralRe: Not being funny but... PinPopular
Dave Kerr6-Sep-10 5:33
mentorDave Kerr6-Sep-10 5:33 
The issue remains the same, you either set a style that applies to all of the controls in the grid but then affects controls elsewhere on the page, or you have to explicitly set the style for each child control explicitly. This is simply a shortcut for doing that.

You could just as easily ask, why bother allowing the FontFamily to be set in the Window when you could simply set it in a style for every child control? Because it is time consuming and clutters up the XAML.

As I mentioned in the article there are many ways to achieve the desired padding - this is just one of them. It also illustrates a very basic example of how to create a Custom Control to extend standard control functionality - which many people will find useful in itself. The techniques applied (deriving a Custom Control, using dependency properties etc) are good to know about in many circumstances.

The usefulness of the PaddedGrid is fairly limited, however this article describes more than that, it shows that you never need to be limited by the built in functionality of a control, WPF allows an extraordinary amount of customisation!
GeneralRe: Not being funny but... Pin
Sacha Barber6-Sep-10 6:21
Sacha Barber6-Sep-10 6:21 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.