Common Layout Properties
All user-interface elements have a standard set of layout
properties, inherited from the FrameworkElement base class. We have
seen a few of these in passing in the preceding section, but we will now look
at them all in a little more detail.
2.8.1. Width and Height
You can set these properties to specify an exact width and
height for your element. You should try to avoid using thesein general it is
preferable to let elements determine their own size where possible. Allowing
elements to size to content makes changing your user interface less effort. It
can also simplify localization. However, you will occasionally need to provide
a specific size.
If you specify a Width or Height, the layout
system will always attempt to honor your choices. Of course if you make an
element wider than the screen, WPF can't make the screen any wider, but as long
as what you request is possible, it will be done.
2.8.2. MinWidth, MaxWidth, MinHeight, and MaxHeight
These properties allow you to specify upper and lower limits on
the size of an element. If you need to constrain your user interface's layout,
it is usually better to use these than Width and Height where
possible. By specifying upper and lower limits, you can still allow WPF some
latitude to automate the layout.
It is possible to mandate limits that simply cannot be
fulfilled. For example, if you request a MinWidth of 10000, WPF won't
be able to honor that request unless you have some very exotic display
hardware. In these cases, your element will be truncated to fit the space
available.
2.8.3. HorizontalAlignment and VerticalAlignment
These properties control how an element is placed inside a
parent when there is more room available than necessary. For example, a
vertical StackPanel will be as wide as the widest element, meaning
that any narrower elements are given excess space. A DockPanel will
provide enough space for an element to fill an edge or all the remaining space.
Alignment is for these sorts of scenarios, enabling you to determine what the
child element does with the extra space.
The default setting for both of these properties is Stretch:
when excess space is available, the element will be enlarged to fill that
space. The alternatives are Left, Center, and Right for
HorizontalAlignment, and Top, Center, and Bottom
for VerticalAlignment. If you choose any of these, the element will
not be stretchedit will use its natural height or width and will then be
positioned to one side or in the center.
2.8.4. Margin
This property determines the amount of space that should be left
around the element during layout.
Margin can be specified as a single number, a pair of
numbers, or a list of four numbers. When one number is used, this indicates
that the same amount of space should be left on all sides. With two numbers,
the first indicates the space to the left and right while the second indicates
the space above and below. When four numbers are specified, they indicate the
amount of space on the left, top, right, and bottom sides, respectively.
2.8.5. Padding
Padding is similar to Margin, except it is
internal rather than external. While Margin indicates how much space
should be left around the outside of an element, Padding specifies how
much space should be left between a control's outside, and its internal
content.
Padding is not present on all WPF elements, since not
all elements have internal content. It is defined by the Control base
class.
Example 2-30 shows
three buttons, one with just margin, one with both margin and padding, and one
with just padding. It also fills the area behind the buttons with color so the
effects of the margin can be seen.
Example 2-30. Margin versus padding
<Grid ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Rectangle Grid.ColumnSpan="2" Fill="Cyan" />
<Button Grid.Column="0" Margin="20">Click me!</Button>
<Button Grid.Column="1" Margin="10" Padding="10">Click me!</Button>
<Button Grid.Column="2" Padding="20">Click me!</Button>
</Grid>
Figure 2-37 shows
the results. The button with margin but no padding has appeared at its normal
size, but has space around it. The middle button is larger, because the padding
causes space to be added around its content. The third button is larger still
because it has more padding, but it has no space around it since it has no
margin.
2.8.6. FlowDirection
The FlowDirection property controls how text flows. The
default is left to right, then top to bottom. Many cultures use the alternative
right-to-left then top-to-bottom flow style.
2.8.7. RenderTransform and LayoutTransform
The RenderTransform and LayoutTransform properties
can both be used to apply a transform, such as scaling or rotation, to an
element and all of its children. The use of transforms is described in
Chapter 7, but it is useful to understand their impact on layout.
If you apply a transform that doubles the size of an element,
the element will appear to be twice as large onscreen. You would normally want
the layout system to take this into accountif a Rectangle with a Width
of 100 is scaled up to twice its size, it will normally make sense for the
layout system to treat it as having an effective width of 200. However, you
might sometimes want the transformation to be ignored for layout purposes. For
example, if you are using a transform in a short animation designed to draw
attention to a particular part of the UI, you probably don't want the entire
UI's layout to be changed as a result of that animation.
You can apply a transform to an object using either LayoutTransform
or RenderTransform. The former causes the transform to be taken into
account by the layout system, while the latter causes it to be ignored.
Example 2-31 shows three buttons, one containing untransformed content
and the other two containing content transformed with these two properties.
Example 2-31. RenderTransform and LayoutTransform
<Button><TextBlock>Foo bar</TextBlock></Button>
<Button><TextBlock RenderTransform="scale 3">Foo bar</TextBlock></Button>
<Button><TextBlock LayoutTransform="scale 3">Foo bar</TextBlock></Button>
The results are shown in
Figure 2-38. As you can see, the button with content scaled by RenderTransform
is the same size as the unscaled one. The presence of the transform has had no
effect on layout, and the content no longer fits inside the space allocated for
it. However, the LayoutTransform has been taken into account by the
layout systemthe third button has been enlarged in order for the scaled content
to fit.
The way in which LayoutTransform is dealt with by the
layout system is straightforward for simple scaling transforms. The size
allocated for the content is scaled up accordingly. But what about rotations?
Figure 2-39 shows a button whose content has a LayoutTransform
of "rotate 30". This is not a scaling transform, but notice that the
button has grown to accommodate the content: it is taller than a normal button.
When it encounters a LayoutTransform, the layout system
simply applies that transform to the bounding box, and makes sure that it
provides enough space to hold the transformed bounding box. This can
occasionally lead to surprising results. Consider the two buttons in
Example 2-32.
Example 2-32. Rotation of content
<Button>
<Line Stroke="Blue" Y1="30" X2="100" />
</Button>
<Button>
<Line LayoutTransform="rotate 50" Stroke="Blue" Y1="30" X2="100" />
</Button>
These are shown in Figure
2-40. The top button looks as you would expect: the button is large
enough to contain the graphical content. But the bottom one is rather
surprisingthe button appears to be much larger than necessary.
This result only makes sense when you consider the bounding
boxremember that the layout system decides how much space to allocate by
applying the LayoutTransform to the bounding box. So let's look at it
again, this time with the bounding boxes shown.
Example 2-33 is a modified version of
Example 2-32, with Border elements added to show the bounding
box of the lines.
Example 2-33. Rotation showing bounding box
<Button>
<Border BorderBrush="Black" BorderThickness="1">
<Line Stroke="Blue" Y1="30" X2="100" />
</Border>
</Button>
<Button>
<Border BorderBrush="Black" BorderThickness="1"
LayoutTransform="rotate 50" >
<Line Stroke="Blue" Y1="30" X2="100" />
</Border>
</Button>
In Figure 2-41,
we can now see the bounding box of the content. The button on the bottom shows
this bounding box with the same 30 degree rotation as has been applied to the
line. This makes it clear that the button is exactly large enough to hold this
rotated bounding box.
You might be wondering why WPF doesn't simply calculate a new
bounding box for the transformed content instead of transforming the existing
one. The reason is that calculating a new bounding box may not be possible.
Some elements, such as Canvas, can declare a width and height that
does not directly reflect their apparent size. The only sensible way in which
the layout system can deal with such elements is to treat their logical shape
as being rectangular. Using this approach of transforming the bounding box
everywhere ensures consistent behavior.
 |