The next Panel derivative I’ll show you is the StackPanel, and
you’ll see how it differs from the single-cell Grid. To keep the code simple, and to avoid defining
properties, I’m going to call this custom class VerticalStackPanel. Here’s the MeasureOverride method:
Example 1. Silverlight
Project: VerticalStackPanelDemo File: VerticalStackPanel.cs (exerpt)
protected override Size MeasureOverride(Size availableSize) { Size compositeSize = new Size();
foreach (UIElement child in Children) { child.Measure(new Size(availableSize.Width, Double.PositiveInfinity)); compositeSize.Width = Math.Max(compositeSize.Width, child.DesiredSize.Width); compositeSize.Height += child.DesiredSize.Height; } return compositeSize; }
|
As usual, the MeasureOverride method loops through all its children and
calls Measure on each of them. But
notice that the Size
offered to each child here consists of the width of the VerticalStackPanel itself and a height of
infinity.
The children are essentially
being asked how tall they need to be. For TextBlock, this is easy: It’s the height of the text. The Ellipse is easy as well: It’s zero. The Image element,
however, calculates a height based on maintaining the correct aspect
ratio with the specified width, which might be a different size than in
the single-cell Grid.
As in the SingleCellGrid
version of MeasureOverride, the Width property of the local compositeSize variable
is based on the maximum child width. But in this panel the Height property of compositeSize is accumulated. The VerticalStackPanel
needs to be as tall as the sum of the heights of all its children.
If VerticalStackPanel is itself in a StackPanel
with a Horizontal orientation, then
the Width property of availableSize will be infinite, and Measure will be
called on each child with a size that is infinite in both directions.
This is fine, and it’s not something that needs to be handled as a
special case.
In SingleCellGrid,
the ArrangeOverride method positioned each of its children in the same
location. The VerticalStackPanel needs to stack its children. For that
reason, it defines local variables named x
and y:
Example 2. Silverlight
Project: VerticalStackPanelDemo File: VerticalStackPanel.cs (exerpt)
protected override Size ArrangeOverride(Size finalSize) { double x = 0, y = 0;
foreach (UIElement child in Children) { child.Arrange(new Rect(x, y, finalSize.Width, child.DesiredSize.Height)); y += child.DesiredSize.Height; } return base.ArrangeOverride(finalSize); }
|
The x variable remains 0 throughout but
the y variable is incremented based on
the Height property of each child’s DesiredSize. The Arrange
measure is called with x and y indicating the location of the child relative to the
panel’s upper-left corner. The Width
property of this Rect is the Width property of finalSize,
but the Height property is the Height of the child’s DesiredSize. This is how
much vertical
space was previously allocated for each child in the MeasureOverride method.
Giving the child its own desired height in the Arrange method essentially voids any VerticalAlignment
property set on the child—an effect we discovered empirically in earlier
explorations of the vertical StackPanel.
In general, for either
the horizontal or vertical
dimension or both, if you offer a child an infinite dimension in MeasureOverride, you’ll
be sizing that dimension of the child based on DesiredSize
in ArrangeOverride.
The MainPage.xaml file in the VerticalStackPanelDemo
project is the same as the one I showed at the outset of this article but using VerticalStackPanel:
Example 3. Silverlight
Project: VerticalStackPanelDemo File: MainPage.xaml (exerpt)
<Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> <local:VerticalStackPanel> <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:VerticalStackPanel> </Grid>
|
The display is the same as
the earlier program.
When this VerticalStackPanel is inside the content grid, its MeasureOverride
method gets the same dimensions as the content grid itself (less any Margin that might be set on the VerticalStackPanel).
But put the VerticalStackPanel (or a vertical StackPanel)
in a ScrollViewer and something quite different happens. By default, the ScrollViewer displays a
vertical scrollbar, so the ScrollViewer
(or rather, one of its children) calls Measure
on the StackPanel
with a finite width but an infinite height. The DesiredHeight of the vertical StackPanel then gives ScrollViewer the
information it needs for the vertical scrollbar parameters.
When you set the HorizontalScrollBarVisibility
property of ScrollViewer to Visible or Auto,
the ScrollViewer calls Measure on the StackPanel with an infinite width to determine the desired
width of the panel. The ScrollViewer uses this information to set its
horizontal scrollbar parameters. The StackPanel then passes this infinite width to the MeasureOverride calls
to its own children. This has the potential of affecting children of
the StackPanel in perhaps unanticipated
ways.
For example, when a TextBlock
has its TextWrapping property set to Wrap, it uses the availableSize.Width
value in its own MeasureOverride call to determine how many lines will result from text wrapping.
But if availableSize.Width is infinite—as it will be if the TextBlock is somewhere inside a ScrollViewer that has an enabled horizontal
scrollbar—then TextBlock has no choice but to return a size with the text not
wrapped at all.
This is why, in the
TelephonicConversation program, it’s not a good idea to enable the
horizontal scrollbar on the ScrollViewer.