Custom Layout
Although WPF supplies a flexible set of layout elements, you
might decide that none of them suits your requirements. Fortunately, the layout
system is extensible, and it is fairly straightforward to implement your own
custom panel. To write a panel, you need to
understand how the layout system works.
Layout occurs in two phases: measure and arrange.
Your custom panel will first be asked how much space it would like to
havethat's the measure phase. The panel should measure each of its children to
find out how much space they require and then decide how much space the panel
needs in total.
Of course, you can't always get what you want. If your panel's
measure phase decides it needs an area twice the size of the screen, it won't
get that in practice. (Unless its parent happens to be a ScrollViewer
.) Moreover, even when there is enough space onscreen, your panel's parent
could still choose not to give it to you. For example, if your custom panel is
nested inside a Grid, the Grid may well have been set up with
a hardcoded width for the column your panel occupies, in which case that's the
width you'll get regardless of what you asked for during the measure phase.
It is only in the "arrange" phase that we find out how much
space we have. During this phase, we must decide where to put all of our
children as best we can in the space available.
 |
You might be wondering why layout bothers with the
measure phase when the amount of space we get during the arrange phase may be
different. The reason for having both is that most panels try to take the
measured size of their children into account during the arrange phase. You can
think of the measure phase as asking every element in the tree what it would
like, and the arrange phase as honoring those measurements where possible,
compromising only where physical or configured constraints come into play.
|
|
Let's create a new panel type to see how the measure and arrange
phases work in practice. We'll call this new panel DiagonalPanel
, and it will arrange elements diagonally from the top left of the panel down
to the bottom right, as Figure 2-44
shows. Each element's top-left corner will be placed where the previous
element's bottom-right corner went.
 |
You don't really need to write a new panel type to
achieve this layoutyou could get the same effect with a Grid, setting
every row and column's size to Auto. However, the same argument could
be made for StackPanel and DockPanel: neither of those do
anything that couldn't be done with the Grid. It's just convenient to
have a simple single-purpose panel, as the Grid equivalent is a little
more verbose.
|
|
To implement this custom layout, we must write a class that
derives from Panel and implements the measure and arrange phases. As
Example 2-36 shows, we do this by overriding the MeasureOverride
and ArrangeOverride methods.
Example 2-36. Custom DiagonalPanel
using System;
using System.Windows.Controls;
using System.Windows;
namespace CustomPanel {
public class DiagonalPanel : Panel {
protected override Size MeasureOverride( Size availableSize ) {
double totalWidth = 0;
double totalHeight = 0;
foreach( UIElement child in Children ) {
child.Measure( new Size( double.PositiveInfinity,
double.PositiveInfinity ) );
Size childSize = child.DesiredSize;
totalWidth += childSize.Width;
totalHeight += childSize.Height;
}
return new Size( totalWidth, totalHeight );
}
protected override Size ArrangeOverride( Size finalSize ) {
Point currentPosition = new Point( );
foreach( UIElement child in Children ) {
Rect childRect = new Rect( currentPosition, child.DesiredSize );
child.Arrange( childRect );
currentPosition.Offset( childRect.Width, childRect.Height );
}
return new Size( currentPosition.X, currentPosition.Y );
}
}
}
Notice that the MeasureOverride method is passed a Size
parameter. If the parent is aware of size constraints that will be need to be
applied during the arrange phase, it passes them here during the measure phase.
For example, if this panel's parent was a Window with a specified
size, the Window would pass in the size of its client area during the
measure phase. However, not all panels will do this. You may find the available
size is specified as being Double.PositiveInfinity in both dimensions,
indicating that the parent is not informing us of any fixed constraints at this
stage. An infinite available size indicates that we should simply pick whatever
size is appropriate for our content.
Some elements ignore the available size, because their size is
always determined by their contents. For example, our panel's simple layout is
driven entirely by the natural size of its children, so it ignores the
available size. Our MeasureOverride simply loops through all of the
children, adding up their widths and heights. We pass in an infinite size when
calling Measure on each of the children in order to use their
preferred size.
 |
You must call Measure on all of your
panel's childen. If your MeasureOverride fails to Measure all
of its children, the layout process may not function correctly. All elements
expect to be measured before they are arranged. Their arrange logic might rely
on the results of calculations performed during the measure phase. When you
write a custom panel, it is your responsibility to ensure that child elements
are measured and arranged at the appropriate times.
|
|
In our ArrangeOverride, we loop through all of the
child elements, setting them to their preferred size, basing the position on
the bottom right-hand corner of the previous element. Since this very simple
layout scheme cannot adapt, it ignores the amount of space it has been given.
Any child elements that do not fit will be cropped, as happens with StackPanel.
This measure-and-arrange sequence traverses the entire user
interface treeall elements use this mechanism, not just panels. A custom
panel is the most appropriate place to write custom layout logic for managing
the arrangement of controls. However, there is one other situation in which you
might want to override the MeasureOverride and ArrangeOverride
methods. If you are writing a graphical element that uses the low-level visual
APIs described in Chapter
7, you will need to override these methods in order for the layout
system to work with your element. The code will typically be simpler than for a
panel, because you will not have child elements to arrange. Your MeasureOverride
will simply need to report how much space it needs, and ArrangeOverride
will tell you how much space you have been given.
|