Silverlight provides several layout containers: Canvas, StackPanel, and Grid. If you are creating a complex layout that is not easily achieved by using the provided containers, you can create a custom panel, which allows you to define layout behavior for the panel's children. To do this, derive from Panel and override its MeasureOverride and ArrangeOverride methods.
This topic contains the following sections.
Before following the steps in this topic, you should be familiar with how objects are sized and positioned in Silverlight. For details, see Silverlight Layout System.
The first step in a panel's layout pass is to measure each child and determine how much space the panel will allocate to that child. In addition, return the amount of space available to the entire panel.
The following example shows an override of the MeasureOverride method for a panel (BlockPanel) that puts 9 children in a 3x3 grid where each cell is 100x100.
'First measure all children and return available size of panel Protected Overloads Overrides Function MeasureOverride(ByVal availableSize As Size) As Size 'Measure first 9 children giving them space up to 100x100, remaining children get 0x0 Dim i As Integer = 0 For Each child As FrameworkElement In Children If i < 9 Then child.Measure(New Size(100, 100)) Else child.Measure(New Size(0, 0)) End If i += 1 Next 'return the size available to the whole panel, which is 300x300 Return New Size(300, 300) End Function
In MeasureOverride, you must call the Measure method of each child element, passing the space that the panel can allocate. Then the layout system calculates the DesiredSize of each child element based on the available size. In this example, we allocate 100x100 to the first 9 children and 0x0 to the remaining children. The size that the parent element allocates to the child can be based on the availableSize value that is passed into the MeasureOverride method. The value of availableSize represents the area that the panel can occupy in the layout.
When Measure is called, the layout system determines the DesiredSize of the child element based on the availableSize passed to Measure and the natural size of the element. The natural size is determined by the element's Width and Height properties or the native size of an image. The DesiredSize is generally set to the lesser of the availableSize and the natural size.
After the DesiredSize of the children has been set, the panel must determine how much space to request from its parent as the return value from its override of MeasureOverride. The appropriate value can be determined in a number of ways:
Calculated based on the total DesiredSize values of all the child elements.
A predetermined size, as in the current example.
A very large size, which will allow the panel excess space for arranging its children.
After the Measure pass is complete, the Arrange pass begins. In the Arrange pass, you must determine the position and size of each child's layout slot and set the final size of the panel.
'Second arrange all children and return final size of panel Protected Overloads Overrides Function ArrangeOverride(ByVal finalSize As Size) As Size 'Get the collection of children Dim mychildren As UIElementCollection = Children 'Get total number of children Dim count As Integer = mychildren.Count 'Arrange children 'We're only allowing 9 children in this panel. More children will get a 0x0 layout slot. Dim i As Integer For i = 0 To 8 'Get (left, top) origin point for the element in the 3x3 block Dim cellOrigin As Point = GetOrigin(i, 3, New Size(100, 100)) 'Arrange child 'Get desired height and width. This will not be larger than 100x100 as set in MeasureOverride. Dim dw As Double = mychildren(i).DesiredSize.Width Dim dh As Double = mychildren(i).DesiredSize.Height mychildren(i).Arrange(New Rect(cellOrigin.X, cellOrigin.Y, dw, dh)) Next For i = 9 To count - 1 'Give the remaining children a 0x0 layout slot mychildren(i).Arrange(New Rect(0, 0, 0, 0)) Next 'Return final size of the panel Return New Size(300, 300) End Function 'Calculate point origin of the Block you are in Protected Function GetOrigin(ByVal blockNum As Integer, ByVal blocksPerRow As Integer, ByVal itemSize As Size) As Point 'Get row number (zero-based) Dim row As Integer = CInt(Math.Floor(blockNum / blocksPerRow)) 'Get column number (zero-based) Dim column As Integer = blockNum - blocksPerRow * row 'Calculate origin Dim origin As New Point(itemSize.Width * column, itemSize.Height * row) Return origin End Function
In ArrangeOverride, call Arrange on each child, passing a Rect. This sets the point of origin, the height, and the width of the child's layout slot in the parent panel. In this example, the first 9 children are placed in one cell of the 3x3 grid based on their order in the Children collection. The first child is placed in the top-left cell, so the Rect passed is (0,0,100,100), which means it will be placed in the top-left corner of the panel with a width and height of 100. The first 9 children are given a layout slot of 100x100. Any remaining children are given a layout slot of 0x0. If the child's desired size is larger than the allocated space, it will be clipped. Each child element positions itself in the layout slot based on other layout properties such as HorizontalAlignment, VerticalAlignment, and Margin.
Your custom panel should not consider settable properties such as Visibility, Margin, or MinWidth when determining layout. The Silverlight layout system will handle all these properties. So, for example, you do not need to skip the layout for elements with Visibility of Collapsed because this will be handled by the layout system.
After each child is arranged, return the final size of the panel. This is the size that the panel will request from its parent container. In this example, the BlockPanel is always set to be 300x300.
In order to use your panel in XAML, you must define the XML namespace for your custom panel class. Then when you create an instance of your custom panel, you reference it using your XML namespace.
The following code example shows how to create an instance of the BlockPanel.
<Grid x:Name="LayoutRoot" Background="Black"> <my:BlockPanel Background="Black" HorizontalAlignment="Left" VerticalAlignment="Top" > <Rectangle Fill="Red" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Salmon" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Orange" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Yellow" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Lime" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Green" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Turquoise" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Blue" Height="500" Width="500" Margin="2"/> <Rectangle Fill="Purple" Height="500" Width="500" Margin="2"/> <TextBlock Text="This Text Does Not Appear"/> </my:BlockPanel> </Grid>
If you create a custom control that contains a lot of scrollable content (similar to a ListBox) and the control loads slowly or does not scroll smoothly, consider using virtualization. The word "virtualize" refers to a technique by which a subset of UI elements are generated from a larger number of data items based on which items are visible on-screen. Generating many UI elements when only a few elements might be on the screen can adversely affect the performance of your application. You can virtualize a custom control with scrollable content by deriving your control from VirtualizingStackPanel. For more information, see VirtualizingStackPanel.