Introduction
One of the most exciting features of Silverlight 2.0 (beta 1) is the ability to create custom panels - just like WPF! Silverlight 2.0 (beta 1) comes with three panels: Canvas, Grid, and StackPanel. Many people will miss other popular panels already included with WPF. This article shows how to build a simple WrapPanel using the extensibility features available with Silverlight 2.0.
Background
A custom panel in Silverlight (and WPF) contains two important parts: Measure and Arrange. This is a two-pass recursive system. The first round measures the size of all children in the panel. In a recursive manner, all children in turn measure the size of their children, and so on. In the next round, the children of the panel are arranged using whatever algorithm you like.
For a complete description of building a custom panel, see this MSDN article.
Using the code
The first important aspect of the custom WrapPanel is to inherit from Panel. Panel is an abstract class from which all panels must derive. The Orientation dependency property determines whether WrapPanel flows vertically or horizontally.
public class WrapPanel : Panel
{
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation",
typeof(Orientation), typeof(WrapPanel), null);
public WrapPanel()
{
Orientation = Orientation.Horizontal;
}
Next, we must override the Measure function. The input parameter 'availableableSize' to MeasureOverride tells the panel how much size it has to work with. This size is given to the panel by its parent. The most important aspect in measuring each child is to indicate to each child their allowed size. In this simple example, we are just saying to each child that they can have the whole space of the panel. The result of the measuring yields a 'DesiredSize' for each child - this desired size will be used when arranging the children.
protected override Size MeasureOverride(Size availableSize)
{
foreach (UIElement child in Children)
{
child.Measure(new Size(availableSize.Width, availableSize.Height));
}
return base.MeasureOverride(availableSize);
}
Finally, we must override the Arrange method. Here, we will position each child in the panel. In our case, we position items either horizontally or vertically. In the horizontal case, we arrange the children (left to right) until the right edge of the panel is reached; then, we move to the next line and continue laying out the children (left to right).
protected override Size ArrangeOverride(Size finalSize)
{
Point point = new Point(0,0);
int i = 0;
if (Orientation == Orientation.Horizontal)
{
double largestHeight = 0.0;
foreach (UIElement child in Children)
{
child.Arrange(new Rect(point, new Point(point.X +
child.DesiredSize.Width, point.Y + child.DesiredSize.Height)));
if (child.DesiredSize.Height > largestHeight)
largestHeight = child.DesiredSize.Height;
point.X = point.X + child.DesiredSize.Width;
if ((i + 1) < Children.Count)
{
if ((point.X + Children[i + 1].DesiredSize.Width) > finalSize.Width)
{
point.X = 0;
point.Y = point.Y + largestHeight;
largestHeight = 0.0;
}
}
i++;
}
}
else
{
double largestWidth = 0.0;
foreach (UIElement child in Children)
{
child.Arrange(new Rect(point, new Point(point.X +
child.DesiredSize.Width, point.Y + child.DesiredSize.Height)));
if (child.DesiredSize.Width > largestWidth)
largestWidth = child.DesiredSize.Width;
point.Y = point.Y + child.DesiredSize.Height;
if ((i + 1) < Children.Count)
{
if ((point.Y + Children[i + 1].DesiredSize.Height) > finalSize.Height)
{
point.Y = 0;
point.X = point.X + largestWidth;
largestWidth = 0.0;
}
}
i++;
}
}
return base.ArrangeOverride(finalSize);
}
}
}
Points of interest
Note, the WrapPanel provided here functions mostly like the WPF WrapPanel. I noticed some minor differences, but have not had time to fix. Feel free to send along your suggested changes.
| You must Sign In to use this message board. |
|
|
 |
|
|
 |
|
 |
Hi Ineir,
Great work on the WrapPanel. I am working on the SilverlightContrib project. This is an open source Silverlight control library (http://www.silverlightcontrib.org). Like Member 2952139 I would like to use your WrapPanel as a base for a WrapPanel control in the SilverlightContrib project. Can I get your permission to use it?
Thanks,
Rob.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
Hi lneir,
I use your WrapPanel in my LinkLabel control - http://www.silverlightshow.net/items/Silverlight-LinkLabel-control.aspx. I'm planning to publish my control to CodePlex. I've read the license of this article and as far as I understand there is no problem to use your control for my purpose. Anyway, I thought it is best if I for your permission. So, do you mind if I use it?
Thank you.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
hi, I have modified the code to make this work like an animating wrap panel and intend to blog about it at my blog site (http://blogs.infosys.com/microsoft and publish the modified code. Hope you don't have issues with it?
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
 | Thanks!  Seth Webster | 17:23 13 Jun '08 |
|
|
 |
|
 |
As the previous poster mentioned, this method doesn't work when inside another layout panel. I was able to get it working (somewhat) by doing the following
protected override Size MeasureOverride(Size availableSize) { foreach (UIElement child in Children) { child.Measure(new Size(availableSize.Width, availableSize.Height)); }
double maxWidth = double.MaxValue; FrameworkElement parent = this.Parent as FrameworkElement; while (parent != null) { if (!Double.IsNaN(parent.Width)) { if (parent.Width < maxWidth) { maxWidth = parent.Width; } } parent = parent.Parent as FrameworkElement; }
int rowNum = 1; double remainingWidth = maxWidth; foreach (UIElement child in Children) { if (remainingWidth - child.DesiredSize.Width < 0) { rowNum += 1; remainingWidth = maxWidth; } remainingWidth -= child.DesiredSize.Width; }
return new Size(maxWidth, rowNum * this.Children[0].DesiredSize.Height); }
I'm sure you could get it working after playing with it a bit.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This didn't work for me, but I was able to modify it slightly to give the desired result for me:
protected override Size MeasureOverride(Size availableSize) { foreach (UIElement child in Children) { child.Measure(new Size(availableSize.Width, availableSize.Height)); }
if (Children.Count == 0) { return base.MeasureOverride(availableSize); } else { double maxWidth = double.MaxValue; FrameworkElement parent = this.Parent as FrameworkElement; maxWidth = parent.ActualWidth;
int rowNum = 1; double remainingWidth = maxWidth; foreach (UIElement child in Children) { if (remainingWidth - child.DesiredSize.Width < 0) { rowNum += 1; remainingWidth = maxWidth; } remainingWidth -= child.DesiredSize.Width; }
return new Size(maxWidth, rowNum * this.Children[0].DesiredSize.Height); } }
I am filling the WrapPanel dynamically, and has it inside a ScrollViewer. Maybe thats why it didn't work at first.
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
Thanks for the wrapPanel, just what I was looking for. Thanks also vRud for your amendment.
I've made a couple of tweaks to it as my maxWidth was remaining infinite (i.e. no parents had fixed width) and also the full width was being claimed meaning that I couldn't center my panel contents horizontally. I did have an issue with all of the rows being assumed to be the same height but I changed my app rather than sorting this out. Shouldn't be to difficult to fix though, keeping a running total of height so far as ArrangeOverride does.
Here's my alteration:
protected override Size MeasureOverride(Size availableSize) { foreach (UIElement child in Children) { child.Measure(new Size(availableSize.Width, availableSize.Height)); } double maxWidth = double.MaxValue; FrameworkElement parent = this.Parent as FrameworkElement; while (parent != null) { if (!Double.IsNaN(parent.Width)) { if (parent.Width < maxWidth) { maxWidth = parent.Width; } } parent = parent.Parent as FrameworkElement; }
if (maxWidth == double.MaxValue && !double.IsInfinity(availableSize.Width)) { maxWidth = availableSize.Width; }
int rowNum = 1; double remainingWidth = maxWidth;
double minRemainingWidth = maxWidth; foreach (UIElement child in Children) { if (remainingWidth - child.DesiredSize.Width < 0) { if (remainingWidth < minRemainingWidth) { minRemainingWidth = remainingWidth; } rowNum += 1; remainingWidth = maxWidth; } remainingWidth -= child.DesiredSize.Width; }
if (remainingWidth < minRemainingWidth) { minRemainingWidth = remainingWidth; }
maxWidth -= minRemainingWidth;
if (this.Children == null || this.Children.Count == 0) { return new Size(maxWidth, 10); }
return new Size(maxWidth, rowNum * this.Children[0].DesiredSize.Height); }
Enjoy!
Chris
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
 |
This code isn't quite right... put it inside a ScrollViewer and you'll see what I mean. You need to do the same type of "how many rows/cols do I have" in MeasureOverride() that you do in ArrangeOverride(). The reason for this is that the WrapPanel's parent might need to know its exact dimensions (such as when the parent is a ScrollViewer), and it gets this from MeasureOverride().
Thanks, Russ. http://www.russellgreenspan.com
|
| Sign In·View Thread·PermaLink | 2.00/5 |
|
|
|
 |
|
 |
Yes, it probably does not work inside a ScrollViewer. I did not take that into consideration when I designed. Thanks for info...I'll look at incorporating the changes ... or feel free to send along changes.
Thanks, Lynn
|
| Sign In·View Thread·PermaLink | |
|
|
|
 |
|
|
 |
|
 |
This inside the MeasureOverride seems to give it's parent(like a ScrollViewer) it's proper size:
Size infiniteSize = new Size(double.PositiveInfinity, double.PositiveInfinity); double curX = 0, curY = 0, curLineHeight = 0;
foreach (UIElement child in Children) { child.Measure(infiniteSize);
if (curX + child.DesiredSize.Width > availableSize.Width) { curY += curLineHeight; curX = 0; curLineHeight = 0; }
curX += child.DesiredSize.Width; if (child.DesiredSize.Height > curLineHeight) { curLineHeight = child.DesiredSize.Height; } }
curY += curLineHeight;
Size resultSize = new Size(); resultSize.Width = double.IsPositiveInfinity(availableSize.Width) ? curX : availableSize.Width; resultSize.Height = double.IsPositiveInfinity(availableSize.Height) ? curY : availableSize.Height;
return resultSize;
|
| Sign In·View Thread·PermaLink | 5.00/5 |
|
|
|
 |
|
|