I have finished a video, to be posted soon, on how to build a Carousel control. Along the way, I had the opportunity to explore the Silverlight Layout System (SLS) and will describe this fascinating corner of Silverlight 2 in this and future blog entries.
The first thing to know about the SLS is that most of the time you can ignore it! It is possible to become quite proficient in Silverlight programming without even knowing explicitly that the system exists, much less having to override any of its methods. For most developers, most of the time, the layout system is implicit and mediated for you by layout controls such as the GridPanel and StackPanel, and more recently by the Silverlight Toolkit layout controls such as the DockPanel and the WrapPanel.
That said, there are times when you want to do something the existing controls just don’t provide, and familiarity with how Silverlight lays out controls can be both fascinating and essential.
When you ask Visual Studio to create an application, it creates Page.xaml. Pages consist of a UserControl that contains a single element, typically a panel which in turn has any number of child elements. If you create additional pages the same pattern is repeated (Perhaps confusingly, you create pages by selecting “Add User Control” )
|Because you can put a UserControl inside another user control (see my video on multiple-page applications and also my video on reusable user controls) we distinguish between a page (a .xaml file with a user control and its contents) on the one hand and a Custom UserControl on the other, but this is a matter of convention.|
In any case, the key point is that the outermost user control can only have one child, and that child is almost always a type derived from Panel.
Panel itself is abstract, meaning that it was created to provide shared functionality for types derived from it, but it is not possible to instantiate a Panel per se.
The Panel Class derives from FrameworkElement, which in turn derives from UIElement.
The UIElement class provides the common features for most of the objects that have a visual appearance in Silverlight, and lays the ground work for layout. It is uncommon to derive directly from Framework element, and almost unheard of to derive directly from UIElement (both should be though of as infrastructure) but it is not illegal. Probably the key UIElement method for layout is InvalidateArrange (described below) and the key property is RenderTransform.
Framework element (which derives from UIElement) provides the API for any object that participates in the Silverlight Layout System (as well as APIs for data binding and object lifetime). It is here that you find the two essential methods for taking programmatic control of layout
Along with various important properties and events.
A Two Step Process (Measure twice, cut once)
Laying out a Silverlight application is a two-step process. This allows the Silverlight Layout System to first measure all the objects you wish to display and then to lay them out given the available space balanced against each objects’ desired size. Compromises are made, and to some degree the size allocated one object is determined both by the total of the sizes requested and the total size available (and (in some cases) who asks first!)
As you can imagine, this can get quite complicated, but we can keep it relatively simple, at least at first.
Let’s start by making the assumption that what we will have is a user control with a class derived from Panel.
Your main job in MeasureOverride is to loop through the Panel’s children and call Measure() on each child. The parameter you pass in to each child’s Measure() method is of type Size and tells the child object the maximum size it can have.
Note that you can say to the child “take all the size you want,” by passing in a Size object with Double.PositiveInfinity for both dimensions.
Measure() does not return a value. Instead it sets its own internal property DesiredSize to the size it wants or the size it can have, whichever is smaller.
Note that it is essential to call Measure on every child even if you don’t need its size. A critical side-effect of calling measure is to set the “yes I want you to display” bit.
MeasureOverride() returns the desired size of the layout container (often the sum of the desired sizes of the children).
The Silverlight Layout System calls ArrangeOverride() on the panel, and again, the panel iterates over every child, this time calling Arrange() on each one, telling it how much space it has been allocated.
|Interestingly, each time you call Arrange on a child element, it calls ArrangeOverride as the first step. For that matter, each time MeasureOverride calls Measure on a child, the child calls MeasureOverride on itself! We’ll examine this bit of recursion in a future blog entry.|
You remember that the parameter to MeasureOverride was a Size object. The parameter to ArrangeOverride is a System.Windows.Rect object which describes not only the size but the location of the object.
In my next blog entry, I’ll work through a simplified Carousel example that puts all this theory into practice.