I'm on the road to finishing up Silverlight in
Action, Revised edition. This is really turning out to be the
definitive Silverlight book. I hope you all enjoy reading it as
much as I have enjoyed writing it.
One interesting thing that has happened is that the book has
almost doubled in size from the previous edition. I keep running
across things that I just think are really important for
Silverlight developers to understand. While the book doesn't cover
every little nook and cranny in Silverlight, I do try and make sure
there aren't too many shadowy corners.
One topic that I felt deserved brief, but good, treatment, is
the layout system in Silverlight. Tonight, I'm wrapping up the text
for chapter 6 which, among other things (frameworkelement,
uielement, panels, transforms), covers that topic. Below is an
(unreviewed and unedited) excerpt from that chapter.
6.2 The Layout System
Layout systems across different technologies vary greatly in
complexity. Take, for example, the Windows Forms layout system.
Fundamentally, that layout system involves absolute x and y
coordinate pairs, and an explicit or implicit z-order. Controls can
overlap each other, get clipped on the edge of the window, or even
get obscured completely. The algorithm is pretty simple - sort by
zorder and then blit the bits to the screen.
For another example, look to HTML and CSS. HTML and CSS support
elements that must size to content and page constraints (tables,
divs), as well as support absolute positioning, overlapping etc.
It's more of a fluid approach, where the size and position of one
element can affect the size and position of another. Therefore the
layout system for HTML and CSS is significantly more complex than
that for something like Windows Forms.
Silverlight and WPF support both types of layout: content that
self-sizes based on constraints, and content that is simply
positioned by way of an x and y coordinate pair. Depending on the
container in use, it can even handle laying elements out on curves
or radially from a central point. The complexity that makes that
possible deserves a deeper look.
6.2.1 Multi-Pass Layout - Measuring and Arranging
Layout in Silverlight and WPF involves two primary passes: the
Measure pass and the Arrange pass. In the Measure pass, the layout
system asks each element to provide its dimensions given a provided
available size. In the Arrange step, the layout system tells each
element its final size and requests that it lay out itself and its
child elements. A full run of measuring and arranging is called a
Layout Pass.
In this section we'll go through the layout system in more
detail, especially around these two key steps and their
implications for performance and design. If you're curious about
layout, or you've ever been confused by something like Height and
Width vs. ActualHeight and ActualWidth, read on.
The Measure Pass
Whenever elements need to be rendered to screen due to having
just been added, made visible, or changed in size, the layout
system is invoked for an asynchronous layout pass. The first step
of layout is to measure the elements. On a FrameworkElement, the
Measure pass is implemented inside the virtual MeasureOverride
function, called recursively on the visual tree:
protected virtual Size MeasureOverride(Size availableSize)
The availableSize parameter contains the size available for this
object can give to its self and child objects. If the
FrameworkElement is to size to whatever content it has, the
availableSize will be double.PositiveInfinity.
The function returns the size the element requires, based on any
constraints or sizes of child objects.
Height and Width vs. ActualHeight and ActualWidth
If you don't explicitly set the height and width properties of a
control, the ActualHeight and ActualWidth properties may be zero.
Why is that? Due to the asynchronous nature of the layout pass,
ActualHeight and ActualWidth may not be set at any specific point
in time from run to run, or more importantly, may actually change
their values over time as the result of layout operations.
ActualHeight and ActualWidth are set after the rendering pass,
and may also be affected by layout rounding settings or
content.
In short, check them, and if they are zero, they haven't been
set. If you want a single place where you can guarantee they'll
have a value, subscribe to the LayoutUpdated event on the element,
and check them there.
The Arrange Pass
The second pass of layout is to arrange the elements given their
final sizes. On a FrameworkElement, the Arrange is implemented
inside the virtual ArrangeOverride function, also called
recursively:
protected virtual Size ArrangeOverride(Size finalSize)
The finalSize parameter contains the size (area within the
parent) this object should use to arrange its self and child
objects. The returned size must be the size actually used by the
element, and may be smaller than the finalSize passed in.
At the end of the arrange pass, Silverlight has everything it
needs to properly position and size each element in the tree.
However, it doesn't have everything it needs to actually display
the element, as its render position or size could be affected by a
render transform, as covered in the previous section.
Layout Completed
Despite the name, the LayoutCompleted event isn't technically
part of the Layout pass. Instead, it is fired as the last event
before an element is ready to accept input. LayoutCompleted is the
safe location for inspecting the actual size and position of the
element, or otherwise responding to changes in same.
Do not do anything in LayoutCompleted that would cause another
layout pass. For example, don't change the size or position of an
element, modify its contents, change its layout rounding, or
otherwise manipulate properties that could change the size of the
elements bounding box.
6.2.2 The LayoutInformation Class
The LayoutInfomation class in System.Windows.Controls.Primatives
contains a few methods that are useful to folks implementing their
own MeasureOverride and ArrangeOverride code. Specifically,
GetLayoutSlot and GetLayoutClip are helpful when hosting child
elements in a custom panel.
GetLayoutSlot
Regardless of its actual shape, each visual element in
Silverlight can be represented by a bounding box, or layout slot.
This is a rectangular shape that takes into account the elements
size and any margins and padding or constraints in effect. Figure
6.n shows the relationship between a layout slot and the child
element hosted in a panel.

Figure 6.n The relationship between the layout slot and the
child element, for an element smaller than the slot
The layout slot is the maximum size to be used when displaying
an element. Portions of the element that fall outside the slot will
be clipped. To see the layout slot for an element, you can call the
static function GetLayoutSlot
public static Rect GetlayoutSlot(FrameworkElement element)
The returned rect will contain the bounding box or layout slot
for that element. This return value can be useful when creating a
custom panel, or when debugging layout issues.
GetLayoutClip
Sometimes elements may be larger than their layout slots, even
after measuring and arranging have attempted to fit them. When that
happens, you have a layout clip that represents the intersection of
the child element's size and the layout slot.
Figure 6.n shows the relationship between the layout slot, the
child element and the layout clip for that child element, in an
instance where the child element is too large for its slot.

Figure 6.n The relationship between the layout clip and the
layout slot for a child element too large for its slot
The function GetLayoutClip returns the intersection that
represents the layout clip. In this case, the function returns an
actual geometry object, useful for setting the clip geometry for an
element should you need to.
public static Geometry GetLayoutClip(FrameworkElement element)
The returned Geometry contains the intersection, or null if the
element was not clipped. It should be noted that in WPF, the
GetLayoutClip method has a counterpart by the same name that
actually resides on the UIElement, and takes in a slot size, and
returns clip geometry.
6.2.3 Performance Considerations
Layout is a recursive process; trigging layout on an element
will trigger layout for all the children of that element, and their
children and so on. For that reason, you should try and avoid
triggering layout for large visual trees as much as possible. In
addition, when implementing your own MeasureOverride or
ArrangeOverride code, make sure it is as efficient as possible.
Virtualization
An example of this has to do with large collections of children
in controls like lists and grids. Drawing the elements takes a
certain amount of time, but that only happens for elements that are
on-screen. Creation of the CLR objects representing the items also
takes a certain amount of time. Most importantly for us, Measure
and Layout happen for all children, regardless of their potential
position on screen or off. Therefore, if you have a thousand
elements in a listbox, MeasureOverride and ArrangeOverride will be
called for each of them. More importantly, if those elements
contain children (as is often the case with item templates), you'll
have even more calls in the layout passes.
One solution to this is Virtualization. A subset of the built-in
controls (such as the DataGrid) support UI virtualization. For
those, pre-created elements are re-used with new data. The end
result is a reduction in the number of in-memory elements, as well
as a reduction of MeasureOverride and ArrangeOverride calls.
Sizing and Positioning
Another performance consideration has to do with sizing and
positioning elements. For example, if you change the margin of an
element, or modify its width or height, you will trigger a layout
pass. However, if you instead call a render transform to either
move or resize that element, you will not trigger a pass.
While a render transform won't always do what you want it to
(after all, it doesn't affect layout), it can be a real performance
plus when used with animation.
Understanding the layout system helps take some of the mystery
out of what happens when you size elements in Silverlight, and they
don't quite do what you might have expected them to do. It's also a
key concept to understand if you plan to implement your own
panels/container controls.
WPF has the concept of a layout transform. This type of
transform is parallel to a render transform, but triggers a layout
pass. As we've seen here, trigging a layout pass can be an
expensive operation, especially if done inside an animation. For
performance considerations, and due to their relatively low
adoption, layout transforms were omitted from Silverlight.
The render transforms provided by Silverlight, however, are
almost always adequate to solve problems we used to solve with
layout transforms - and often superior. Let's take a look at them
next.
There, I hope that was useful to folks hoping to get a little
deeper insight into Silverlight. The book will be published this
summer, but MEAP subscribers get access to the chapters as
soon as they are released for tech editing. There are several
chapters up already.