Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Custom Panel in Silverlight: Advanced Canvas

0.00/5 (No votes)
6 Dec 2008 1  
Here, you will learn how to create your own custom panel like the an advanced canvas.

Introduction

Sometimes, you want to control where items appear on the screen, and maybe control their size as well. In order to do this, you have to create your own version of a panel in which you do all the work when it comes to positioning and sizing the items.

In this article, you will learn how to create your own version of a panel, including the use of extra properties such as Left and Top used with the regular Canvas. I will guide you on how to add your own properties and use them properly.

Background

I was looking for a more advanced panel to satisfy my needs. I wanted to have full control of where my items appear on the screen, like at the bottom right. With the regular Canvas, that was not really easy, unless you keep collecting the size of the panel all the time. Plus, you have to do it in the code-behind file. I didn't want that, and created my own version that provides this functionality, and now I am sharing it with you because I think the regular Canvas should be extended like this.

Using the code

The project is created with Visual Studio 2008 with the .NET 3.5 framework. If you have a lower version of one of these, you will still be able to run it by creating a new Silverlight application and adding AdvancedCanvas.cs and Page.xaml with the Page.xaml.cs file to your project.

The tutorial

Now, you will learn how to actually create your own version of a Panel. To start with, you have to create a new class which extends from Panel.

public class AdvancedCanvas : Panel

In order to make anything work, you have to override the following two methods. This is required for all derivations from the Panel!

protected override Size MeasureOverride(Size availableSize)
protected override Size ArrangeOverride(Size finalSize)

MeasureOverride is the method in which you will loop through all the children and determine if you have to change their size or not. ArrangeOverride is the method in which you will loop through all the children and calculate their positions.

Here is a basic implementation of both methods. If you want to see the full implementation, see the attached project:

protected override Size MeasureOverride(Size availableSize)
{
    Size infinite = new Size(double.PositiveInfinity, 
                             double.PositiveInfinity);
    foreach (FrameworkElement child in Children)
    {
        child.Measure(infinite);
        //Give the child all space it will need
    } 
    
    return new Size(availableSize.Width, availableSize.Height);
}

protected override Size ArrangeOverride(Size finalSize)
{
    //The location where the child will be displayed
    double currentX = 0, currentY = 0;
    foreach (FrameworkElement child in Children)
    {
        Point location = new Point(currentX, currentY);
        child.Arrange(new Rect(location, child.DesiredSize));
        //Calculate the new location
        currentX += child.DesiredSize.Width;
        currentY += child.DesiredSize.Height;
    }
    //Return the total size used
    return new Size(currentX, currentY);
}

If you want to use extra properties such as a Left and Right, you first have to add three things per property:

  1. An attached DependencyProperties starting with the name and ending with the property
  2. A static method starting with Get and ending with the name of the property
  3. A static method starting with Set and ending with the name of the property

Again, a short display of how to add these to your own panel:

public static readonly DependencyProperty TopProperty = 
   DependencyProperty.RegisterAttached("Top", typeof(int), 
                      typeof(AdvancedCanvas), null); 
public static int GetTop(DependencyObject obj)
       { return (int)obj.GetValue(TopProperty); } 
public static void SetTop(DependencyObject obj, int value)
       { obj.SetValue(TopProperty, value); }

With the attached DependencyProperty, the string "Top" is used to register it as Top for use in the designer. typeof(int) defines the type of this property. You are allowed to put an enum in there to limit the options the user has. typeof(AdvancedCanvas) has to be there as well, because it has to be added to this class. If your class name is different, use it here. A null is normally used to notify when the property has changed. In this case, it is not needed and is therefore null. If you want to add a callback here, use the following code and add the method name you specified in the callback:

new PropertyMetadata(new PropertyChangedCallback(AdvancedCanvas.OnTopPropertyChanged)

In the GetTop and SetTop methods, you only make a call to the GetValue and SetValue methods of the DependencyObject. You are allowed to add extra functionality in here to do some verification, but the lesser, the better.

All you now have to know is how to use those properties in your panel. This is pretty simple. Keep in mind that the properties are set on the child and not on the panel itself. Therefore, you use the child itself to get its property in the MeasureOverride and/or ArrangeOverride methods:

int top = (int)child.GetValue(TopProperty);

With this value, you are able to do different measures or arrangements in your code.

To use your own panel in the .xaml files, you first have to load it, even though it is in the same project and directory. You do this by adding the following line and marking it as the local namespace:

xmlns:local="clr-namespace:Silverlight1"

Visual Studio helps you with this line, so you should not have to worry.

Then, you add your panel to the page, and you now can start adding items to it, like buttons:

<local:AdvancedCanvas HorizontalAlignment="Left" 
       VerticalAlignment="Top" Width="auto" Height="auto">
 <Button local:AdvancedCanvas.Top="0" 
         local:AdvancedCanvas.Left="0" Content="TopLeft" />
 <Button local:AdvancedCanvas.Top="0" 
         local:AdvancedCanvas.Center="0" Content="TopCenter" />
 <Button local:AdvancedCanvas.Top="0" 
         local:AdvancedCanvas.Right="0" Content="TopRight" />

 <Button local:AdvancedCanvas.Middle="0" 
         local:AdvancedCanvas.Left="0" Content="MiddleLeft" />
 <Button local:AdvancedCanvas.Middle="0" 
         local:AdvancedCanvas.Center="0" Content="MiddleCenter" />
 <Button local:AdvancedCanvas.Middle="0" 
         local:AdvancedCanvas.Right="0" Content="MiddleRight" />

 <Button local:AdvancedCanvas.Bottom="0" 
         local:AdvancedCanvas.Left="0" Content="BottomLeft" />
 <Button local:AdvancedCanvas.Bottom="0" 
         local:AdvancedCanvas.Center="0" Content="BottomCenter" />
 <Button local:AdvancedCanvas.Bottom="0" 
         local:AdvancedCanvas.Right="0" Content="BottomRight" />
</local:AdvancedCanvas>

Here, you see more than one property used, and that is legal. All the properties that you define are allowed to be used at once.

Now you know all the basics. For more complex implementations, you have to do more logic in MeasureOverride and ArrangeOverride. See the attached project for a fairly simple implementation.

Tips and tricks

You normally want your panel to have full size, unless defined differently. Mark its Width and Height as Auto, and you are good to go. If you do not do that, it can get a different size and align to the center of the available space. Add the HorizontalAlignment and VerticalAlignment properties if that is not what you want.

History

  • 05-12-2008: Started project.
  • 06-12-2008: Finished project and started article.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here