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);
}
return new Size(availableSize.Width, availableSize.Height);
}
protected override Size ArrangeOverride(Size finalSize)
{
double currentX = 0, currentY = 0;
foreach (FrameworkElement child in Children)
{
Point location = new Point(currentX, currentY);
child.Arrange(new Rect(location, child.DesiredSize));
currentX += child.DesiredSize.Width;
currentY += child.DesiredSize.Height;
}
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:
- An attached
DependencyProperties
starting with the name and ending with the property
- A static method starting with Get and ending with the name of the property
- 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.