Custom Panels

Microsoft Silverlight will reach end of support after October 2021. Learn more.

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.

  • Prerequisites
  • Measuring
  • Arranging
  • Creating a Custom Panel in XAML
  • Scrollable Content and Performance
  • Related Topics

Prerequisites

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.

Measuring

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
// First measure all children and return available size of panel
protected override Size MeasureOverride(Size availableSize)
{

    // Measure first 9 children giving them space up to 100x100, remaining children get 0x0 
    int i =0;
    foreach (FrameworkElement child in Children)
    {
        if (i < 9)
        {
            child.Measure(new Size(100, 100));
        }
        else
        {
            child.Measure(new Size(0, 0));
        }

        i++;
    }


    // return the size available to the whole panel, which is 300x300
    return new Size(300,300);
}

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.

Caution noteCaution:

Do not return the availableSize value that was passed to MeasureOverride. The return value for MeasureOverride must be a finite value, but the availableSize passed in can be infinity in some scenarios.

Arranging

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.

The following code shows the ArrangeOverride method for the BlockPanel from the Measuring section.

'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
// Second arrange all children and return final size of panel
protected override Size ArrangeOverride(Size finalSize)
{
    // Get the collection of children
    UIElementCollection mychildren = Children;

    // Get total number of children
    int count = mychildren.Count;

    // Arrange children
    // We're only allowing 9 children in this panel.  More children will get a 0x0 layout slot.
    int i;
    for (i = 0; i < 9; i++)
    {

        // Get (left, top) origin point for the element in the 3x3 block
        Point cellOrigin = 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.
        double dw = mychildren[i].DesiredSize.Width;
        double dh = mychildren[i].DesiredSize.Height;

        mychildren[i].Arrange(new Rect(cellOrigin.X, cellOrigin.Y, dw, dh));

    }

    // Give the remaining children a 0x0 layout slot
    for (i = 9; i < count; i++)
    {
        mychildren[i].Arrange(new Rect(0, 0, 0, 0));
    }


    // Return final size of the panel
    return new Size(300, 300);
}

// Calculate point origin of the Block you are in
protected Point GetOrigin(int blockNum, int blocksPerRow, Size itemSize)
{
    // Get row number (zero-based)
    int row = (int)Math.Floor(blockNum / blocksPerRow);

    // Get column number (zero-based)
    int column = blockNum - blocksPerRow * row;

    // Calculate origin
    Point origin = new Point(itemSize.Width * column, itemSize.Height * row);
    return origin;

}

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.

NoteNote:

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.

Creating a Custom Panel in XAML

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.

Run this sample

 <UserControl x:Class="BlockPanel.Page"
 xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation" 
 xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml" 
 xmlns:my="clr-namespace:BlockPanel;assembly=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>

Scrollable Content and Performance

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.

See Also

Reference

Concepts