WPF affords developers the opportunity to create layouts that coincide greatly with how the look and behavior of a particular user interface was envisioned to be. The tricky part is, as always, knowing how to use the tools we’re given.

One common layout-related issue people run into is the layout of items displayed within an ItemsControl, specifically the space between the items. More often than not, items will not be displayed in a manner regarded as acceptable by the developer; some tweaking and customization is required to get what we want.

Sometimes we have very particular requirements set in how we want our final layouts to appear and behave. In this article, we’re going to look at an example featuring an ItemsControl, its children, and how we might be able to space it out so that it satisfies our goals.

I. The Scenario

In our application, we have the need to display a collection of customizable input fields at the bottom of our form. These input fields are defined by administrators managing the product, thus we are unaware of exactly how many of these input fields are going to end up appearing on our form.

Each input field consists of a self-describing label as well as an actual input element, such as a text box or a combo box. Although each field features two elements (label and input), it is collectively powered by a single piece of data. The following is an example of one of our input fields:

Example of a Displayed InputItem

Example of a Displayed InputItem

Appearances are important, so we have a number of requirements on how collections of these fields are laid out:

  1. Fields are laid out horizontally, but must wrap if they cannot fit on the screen
  2. Each field is spaced apart from the next and previous field in the collection by an amount dependent on the width of the window or root container. Fields should neither be too close nor too far apart, regardless of window size.
  3. Wrapped fields are aligned perfectly underneath their counterparts located above and/or below them (resulting in a grid-like layout).

If the window is large enough, the input fields should be laid out in only a single row, with proper spacing between each one, like so:

A Single Row of Input Fields.

A Single Row of Input Fields.

Notice how there is ample spacing between each item. Let’s see what we get when we constraint the width to the width shown above and add a few more fields:

Multiple Rows of Input Fields

Multiple Rows of Input Fields

See how everything is lined up in a grid-like fashion.

Now, we have no idea how many fields the customer will be adding, and we also don’t know how large their screens will be. So, our layout needs to be fluid in its shape in order to accommodate all these different possibilities. If we decrease the width of our window, we’ll have the following:

Multiple Rows of Input Items with Less Width Available

Multiple Rows of Input Items with Less Width Available

You’ll see how the number of columns shifted down to two. If you’re screen is wide enough, then you would get something like:

Multiple Rows of Input Items with Lots of Width Available

Multiple Rows of Input Items with Lots of Width Available

II. Pick Yer Poison (err, Panel)

Now that we have a good picture of what we want our layout to look like, we can proceed to choosing the particular panel to employ in our ItemControl‘s ItemsPanelTemplate.

Typically, when one wishes to achieve a “grid-like” layout, one would do well to make use of a Grid or UniformGrid. Certainly a UniformGrid might seem appropriate here, as is the typical choice when the goal is to evenly lay out items in a grid-like, evenly distributed fashion within an ItemsControl.

Unfortunately, that will not work for us here. We’re using WPF because it is dynamic dammit, and UniformGrid is a highly restrictive layout panel in that it requires the specification of the number of columns within its declaration.

Nay, a UniformGrid will not do! We need to adjust the number of columns based on the available width; in other words, we need to wrap appropriately. Therefore, the natural choice for our panel is the WrapPanel.

While the WrapPanel achieves our requirement of adjustment based on available width, it does not space out items and does not lay them out in a fashion that could be described anything close to “grid-like”. If we simply plop in a WrapPanel within our ItemsPanelTemplate, we’re going to get something like the following:

Ugly Input Field Layout

Ugly Input Field Layout

Dear Lord, that is hideous. But things are never pretty by accident (except organic life forms), and we are well on our way in getting the layout we want by making the WrapPanel our weapon of choice.

To start on the process of beautifying this pathetic creature, let’s talk briefly about the UI design behind each of the input fields.

III. Input Fields Be Stylin’

I’m going to leave up the exact design of these simple input fields as an exercise for the reader, for the most part.

In brief, the data powering each input field is responsible for indicating the type of exact input control which should be rendered, be it via a Boolean value or what have you.

A single Style is used which targets controls of the…Control variety. The name we’ll be using for this particular style is InputControlStyle. Based on our little indicator, it is wired to select the appropriate template. The various templates consists of a text block to display the descriptive text of the field, as well as a declaration of the particular type of input control which distinguishes it from the others.

So, while the actual design of the input field is arbitrary as far as we’re concerned, part of the solution for making our layout nice and pretty lies in their design. As was just witnessed in the previous section, using a WrapPanel gives us wrapping, but no grid alignment.

Luckily for us, children need not be wholly dependent on a common ancestral container in order to be collectively aligned in a grid-like manner. Instead, we can share size information between each of these fields so that we end up with just that.

We can share size information by taking advantage of the SharedSizeGroup feature made available by the DefinitionBase class.

Now, you’ve probably never heard of the DefinitionBase class before, and that’s just swell, but you’ve certainly toyed with its derivatives (e.g. ColumnDefinition, RowDefinition). These derivatives, however, only apply to Grid controls, thus we are going to need Grid controls somewhere in our setup. Going back to the template for each of the different kinds of input fields, we mentioned that each template will consists of two items. However, some sort of layout container will be needed to band the two elements together, and that’s where our Grid declarations will go.

In order to maintain our typical level of high standards and cleanliness, we’ll want to create a Style for these Grid controls so that all templates which provide a different type of input element can join the party. Assuming our magic Grid style is named InputGridStyle, an example template (one featuring the use of a text box) is as follows:

<ControlTemplate x:Key="TextBoxInputTemplate">
    <Grid Style="{DynamicResource InputGridStyle}">
          <TextBlock Style="{DynamicResource InputTextBlockStyle}"
                     Grid.Column="0"
                     />
          <TextBox Style="{DynamicResource InputTextBoxStyle}"
                   Grid.Column="1"
                   />
    </Grid>
</ControlTemplate>

Template for Combo Box Input Fields

The styles being applied to the text block and box are arbitrary and should obviously satisfy any aesthetic and data binding objectives.

Any other template need only adhere to using a Grid control with the above style applied in order to appear correctly in our ItemsControl.

Now that you have an unbelievable understanding of how these input templates are organized and work, we’ll move on to the real magic show: the common Grid style in use by each of these templates.

As was mentioned previously, we need to tap into the SharedSizeGroup feature in order to achieve our objectives. The SharedSizeGroup property accepts a string describing the name of the group that the particular Grid should enter into. All Grid controls entered into the same group will then share size information with each other as well as That Which Contains Them (a bit Lovecraftian, I know).

But wait, didn’t we say that the SharedSizeGroup property belongs to DefinitionBase-based class, like ColumnDefinition or RowDefinition? Some of you may see this alone as being a problem in our quest to create style defining the shared size group membership information.

For those of you not in the fold: styling ColumnDefinition/RowDefinition is problematic because, well…you can’t style the Grid control’s ColumnDefinitions and RowDefinitions properties. That’s because neither of those properties are actually dependency properties. What’s more, the Grid control is not actually a control, in that it does not derive from Control, it derives from Panel, which itself derives directly from FrameworkElement. This means that making use of a ControlTemplate is not a possibility either.

Well (maybe unfortunately for you), in my solution to our layout problem, I do actually style the grid’s column definitions, but I do so by making use of an attached property that lets me do so. Creating such an attached property is outside the scope of this article, but you should be able to find plenty of examples online that will guide you in that task. If all else fails, just drop the style altogether and simply re-declare duplicate ColumnDefinitions in all of your templates, and pray no one ever makes a spelling error in the name.

Here is the style of our Grid common to all the templates (the “lib” namespace is a fictitiously named one that represents a custom UI framework where I keep such things as the attached properties we’re going to be using):

<Style x:Key="InputGridStyle" TargetType="{x:Type Grid}">
    <Setter Property="Margin" Value="0,0,0,5"/>
    <Setter Property="lib:GridProperties.ColumnDefinitions">
        <Setter.Value>
            <lib:ColumnDefinitionCollection>
                <ColumnDefinition SharedSizeGroup="InputTextGroup"/>
                <ColumnDefinition SharedSizeGroup="InputValueGroup"/>
            </lib:ColumnDefinitionCollection>
        </Setter.Value>
    </Setter>
</Style>

Style Used by Input Field Grids

Note: Although WPF ships with a ColumnDefinitionCollection type (which is where ColumnDefinition instances get stored), and even though it is public, it has no default constructor, thus we cannot make use of it in an XAML declaration. You’ll need to make one your self, all it needs to do is implement an existing collection class targeting the ColumnDefinition type for its items (ObservableCollection<ColumnDefinition>or whatnot).

That takes care of our input item styles.

IV. Turn on Size Sharing

Now that our items are set up to share size information, we need to enable size sharing on the containing WrapPanel itself. We can do this simply by setting the Grid.IsSharedSizeScope attached property to true in the WrapPanel declaration.

Taking everything we’ve done, we should have the following declaration for our ItemsControl this far:

<ItemsControl ItemsSource="{Binding YourData, UpdateSourceTrigger=PropertyChanged}">
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <WrapPanel Grid.IsSharedSizeScope="True"
                       Orientation="Horizontal"
                       HorizontalAlignment="Center"
                       />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <ContentControl Style="{DynamicResource InputControlStyle}" />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Our ItemsControl Declaration Thus Far

OK! Great, let’s see what our ItemsControl looks like now (for our example, we only have enough width available for there to be two columns):

Better Looking Input Field Rows

Better Looking Input Field Rows

Looking much better! But…hmm…they are still a bit scrunched together. Remember that the x-Dimension of a WrapPanel is constrained to its content, much like the StackPanel. There is no feature made available by the WrapPanel class which will space things out nicely for us, instead we’re going to have to space our items out ourselves by adding margins to them.

So, alright…things look a bit scrunched starting where the first control ends and the beginning of the next control, and so on. Let’s then add a margin to the right of each item by amending our ItemTemplate with a Margin attribute like so:

<ContentControl Style="{DynamicResource InputControlStyle}"
                Margin="0,0,0,25"
                />

Adding a Margin to Our ItemTemplate Declaration

Alright, not too hard. Let’s look at it now:

Two Columns of Input Fields Looking Mighty Fine

Two Columns of Input Fields Looking Mighty Fine

Alright. Sweetness! If we give our form some more width, it’ll fill up three columns as was shown in some of the earlier screenshots in this article, if we remove even more width, we’ll skinny on down to a single row even:

One Column of Some Mighty Fine Input Fields

One Column of Some Mighty Fine Input Fields

Looks like our work here is done.

Good journeys, all.

Matt Weber

I'm the founder of Bad Echo LLC, which offers consulting services to clients who need an expert in C#, WPF, Outlook, and other advanced .NET related areas. I enjoy well-designed code, independent thought, and the application of rationality in general. You can reach me at matt@badecho.com.

  3 Responses to “WPF: Grid-Like WrapPanels”

  1. WOW! I’m impressed. Hey give a call so we can talk world events.

    Dad

  2. This is amazing! Thank you so much for sharing it, this has just

  3. (previous message sent by itself, sorry)
    This is amazing! Thank you so much for sharing it, this is exactly what I was looking for. Excellent explanations, and beautiful responsive result! :)

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title="" rel=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

 
   
© 2012-2013 Matt Weber. All Rights Reserved. Terms of Use.