Perhaps the simplest panel of
all is the Grid
that contains no rows or columns, commonly referred to as a “single-cell
Grid.” I’ve been using the Grid named ContentPanel
as a single-cell Grid; as you’ve seen,
the Grid can
host multiple children, but they overlap within the same area.
Let’s duplicate the
functionality of a single-cell Grid
with a class named SingleCellGrid.
In a new project named SingleCellGridDemo, I
right-clicked the project name, selected Add and New Item from the menu, and picked Class from the dialog
box, naming it SingleCellGrid.cs. In the file, I made sure the class was
public and derived from Panel.
Example 1. Silverlight
Project: SingleCellGridDemo File: SingleCellGrid.cs (excerpt)
namespace SingleCellGridDemo { public class SingleCellGrid : Panel { . . . } }
|
Like all panels, this class
overrides the two methods MeasureOverride
and ArrangeOverride. Here’s the first:
Example 2. Silverlight
Project: SingleCellGridDemo File: SingleCellGrid.cs (excerpt)
protected override Size MeasureOverride(Size availableSize) { Size compositeSize = new Size();
foreach (UIElement child in Children) { child.Measure(availableSize); compositeSize.Width = Math.Max(compositeSize.Width, child.DesiredSize.Width); compositeSize.Height = Math.Max(compositeSize.Height, child.DesiredSize. Height); }
return compositeSize; }
|
The argument to MeasureOverride
is called availableSize of type Size, a structure that
has two properties named Width and Height of type double. This is the size that the panel is getting from
its parent. One or both of these dimensions might be infinite.
The MeasureOverride method has two fundamental jobs:
The first job is to call Measure on all its children. This is essential;
otherwise, the children will have no size and will not appear on the
screen. MeasureOverride almost always performs this job by enumerating
through the Children collection with a
foreach loop.
The second job of the MeasureOverride method
is to return a size that the panel wants to be. In this MeasureOverride
method, that size is the variable called compositeSize. This size must have finite non-negative
dimensions. The MeasureOverride method
cannot simply return the availableSize argument under the assumption that it wants all
the space it’s being offered because the availableSize
argument might have infinite dimensions.
By the time the MeasureOverride method
is called, this availableSize argument has been adjusted in some ways. If the panel
has a Margin set on it, this availableSize excludes that Margin. If any of the Width, MinWidth, MaxWidth, Height, MinHeight,
or MaxHeight
properties are set on the panel, then the availableSize
is constrained by those values.
The two jobs of MeasureOverride are
usually performed in concert: When the panel calls Measure on each of its
children, it offers to each child an available size. This size might
have infinite dimensions. The Size argument passed to the Measure method depends on the paradigm of the
particular panel. In this particular case, the SingleCellGrid offers to each of its children its own availableSize:
child.Measure(availableSize);
The panel is allowing each
child to exist in the same area as itself. It’s no problem if this availableSize argument has infinite
dimensions.
When Measure
returns, the child’s DesiredSize property has been set and has a valid value. This is
how the parent determines the size the child wants to be. This DesiredSize property was calculated by the
child’s Measure method after calling
its own MeasureOverride method, which
possibly interrogated its own children’s sizes. The MeasureOverride method doesn’t need to bother
itself with Margin settings, or
explicit Width or Height settings. The Measure method does that, and adjusts DesiredSize appropriately. If the child has a
Margin setting, for example, the DesiredSize includes that additional amount.
Some examples: The MeasureOverride
method of a TextBlock returns the size of the text
displayed in a particular font. The MeasureOverride
method of an ImageMeasureOverride method of an Ellipse returns a size of zero. element returns the native pixel dimensions of the bitmap.
The
The DesiredSize
property is always finite. The MeasureOverride
method in SingleCellGrid uses each
child’s DesiredSize property to determine a maximum size that it stores in the
local variable compositeSize:
compositeSize.Width = Math.Max(compositeSize.Width, child.DesiredSize.Width);
compositeSize.Height = Math.Max(compositeSize.Height, child.DesiredSize.Height);
This size reflects the largest
width of all the children and the largest height.
The other method required in a
Panel derivative is ArrangeOverride. Here’s the
one in the SingleCellGrid class:
Example 3. Silverlight
Project: SingleCellGridDemo File: SingleCellGrid.cs (excerpt)
protected override Size ArrangeOverride(Size finalSize) { foreach (UIElement child in Children) { child.Arrange(new Rect(new Point(), finalSize)); }
return base.ArrangeOverride(finalSize); }
|
The ArrangeOverride method receives an argument called finalSize. This is the
area that the panel has been given by its parent. It always has finite
dimensions.
The job of the ArrangeOverride method is to arrange its children on its
surface. This is accomplished by enumerating through all its children
and calling Arrange on them. The Arrange method requires an argument of type Rect—a rectangle defined by a Point indicating an upper-left corner and a Size
indicating a width and height. This is normally the only appearance of a
Rect in the layout process. The Rect specifies
both the location of the child relative to the upper-left corner of the
parent, and the size of the child.
In this particular case,
all children are positioned at the upper-left corner of the panel and
given a size of finalSize, the same size as the panel itself.
You might think that the size passed
to Arrange
should be the DesiredSize of the child, but that’s not correct (at least for this
particular panel). Very often this finalSizeDesiredSize of the child. (In an extreme case, consider an Ellipse with a DesiredSize of zero.) This is how adjustments are made
in the child’s Arrange method for HorizontalAlignment and VerticalAlignment. In SingleCellGrid, the child’s Arrange method is called with a size of finalSize: will be larger than the
child.Arrange(new Rect(new Point(), finalSize));
The Arrange method
compares that size with the child’s own DesiredSize,
and then calls the child’s ArrangeOverride method with an altered size and position based on
the HorizontalAlignment and VerticalAlignment settings. That’s how the Ellipse gets a non-zero size when its DesiredSize is zero.
The ArrangeOverride
method almost always returns the finalSize
argument, which is the value returned from
the method in the base Panel class.
Now to test it out. The
MainPage.xaml file in the SingleCellGridDemo project needs to reference this custom
class. In the root element, an XML namespace declaration associates the name “local” with
the .NET namespace used by the project:
xmlns:local="clr-namespace:SingleCellGridDemo"
The MainPage.xaml file nests the SingleCellGrid in the
content grid, and then fills it with the same four elements from the first two
programs in this article:
Example 4. Silverlight
Project: SingleCellGridDemo File: MainPage.xaml (excerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <local:SingleCellGrid> <TextBlock Text="TextBlock aligned at right bottom" HorizontalAlignment="Right" VerticalAlignment="Bottom" />
<Image Source="Images/BuzzAldrinOnTheMoon.png" />
<Ellipse Stroke="{StaticResource PhoneAccentBrush}" StrokeThickness="24" />
<TextBlock Text="TextBlock aligned at left top" HorizontalAlignment="Left" VerticalAlignment="Top" /> </local:SingleCellGrid> </Grid>
|
You’ll discover that
this program displays the elements the same way as the earlier
GridWithFourElements program.