MULTIMEDIA

Silverlight Recipes : Controls - Creating a Custom Layout Container (part 2)

3/4/2015 8:23:54 AM

4. The Code

In this code sample, you will build a layout container extending the Panel type that can arrange its children in either a horizontal orientation (in rows) or a vertical one (in columns). It also automatically wraps all its children into successive rows or columns based on available space. The implementing type is named WrapPanel, and Listing 1 shows the code.

Listing 1. WrapPanel implementation
using System;
using System.Windows;
using System.Windows.Controls;

namespace Recipe5_9
{
public class WrapPanel : Panel
{
//Orientation dependency property
DependencyProperty OrientationProperty =
DependencyProperty.Register("Orientation", typeof(Orientation),
typeof(WrapPanel),
new PropertyMetadata(
new PropertyChangedCallback(OrientationPropertyChangedCallback)));


public Orientation Orientation
{
get
{
return (Orientation)GetValue(OrientationProperty);
}
set
{
SetValue(OrientationProperty, value);
}
}
private static void OrientationPropertyChangedCallback(
DependencyObject target, DependencyPropertyChangedEventArgs e)
{
//cause the layout to be redone on change of Orientation
if (e.OldValue != e.NewValue)
(target as WrapPanel).InvalidateMeasure();
}

public WrapPanel()
{
//initialize the orientation
Orientation = Orientation.Horizontal;
}
protected override Size MeasureOverride(Size availableSize)
{
double DesiredWidth = 0;
double DesiredHeight = 0;
double RowHeight = 0;
double RowWidth = 0;
double ColHeight = 0;
double ColWidth = 0;

//call Measure() on each child - this is mandatory.
//get the true measure of things by passing in infinite sizing
foreach (UIElement uie in this.Children)
uie.Measure(availableSize);
//for horizontal orientation - children laid out in rows
if (Orientation == Orientation.Horizontal)
{
//iterate over children
for (int idx = 0; idx < this.Children.Count; idx++)
{

//if we are at a point where adding the next child would



//put us at greater than the available width
if (RowWidth + Children[idx].DesiredSize.Width
>= availableSize.Width)
{
//set the desired width to the max of row width so far
DesiredWidth = Math.Max(RowWidth, DesiredWidth);
//accumulate the row height in preparation to move on to the next row
DesiredHeight += RowHeight;
//initialize the row height and row width for the next row iteration
RowWidth = 0;
RowHeight = 0;
}
//if on the other hand we are within width bounds
if (RowWidth + Children[idx].DesiredSize.Width
< availableSize.Width)
{
//increment the width of the current row by the child's width
RowWidth += Children[idx].DesiredSize.Width;
//set the row height if this child is taller
//than the others in the row so far
RowHeight = Math.Max(RowHeight,
Children[idx].DesiredSize.Height);

}
//this means we ran out of children in the middle or exactly at the end
//of a row
if (RowWidth != 0 && RowHeight != 0)
{
//account for the last row
DesiredWidth = Math.Max(RowWidth, DesiredWidth);
DesiredHeight += RowHeight;
}

}
}
else //vertical orientation - children laid out in columns
{
//iterate over children
for (int idx = 0; idx < this.Children.Count; idx++)
{
//if we are at a point where adding the next child would
//put us at greater than the available height
if (ColHeight + Children[idx].DesiredSize.Height
>= availableSize.Height)
{


//set the desired height to max of column height so far
DesiredHeight = Math.Max(ColHeight, DesiredHeight);
//accumulate the column width in preparation to
//move on to the next column
DesiredWidth += ColWidth;
//initialize the column height and column width for the
//next column iteration
ColHeight = 0;
ColWidth = 0;
}
//if on the other hand we are within height bounds
if (ColHeight + Children[idx].DesiredSize.Height
< availableSize.Height)
{
//increment the height of the current column by the child's height
ColHeight += Children[idx].DesiredSize.Height;
//set the column width if this child is wider
//than the others in the column so far
ColWidth = Math.Max(ColWidth,
Children[idx].DesiredSize.Width);
}
}
//this means we ran out of children in the middle or exactly at the end
//of a column
if (RowWidth != 0 && RowHeight != 0)
{
//account for the last row
DesiredHeight = Math.Max(ColHeight, DesiredHeight);
DesiredWidth += ColWidth;
}
}
//return the desired size
return new Size(DesiredWidth, DesiredHeight);
}

protected override Size ArrangeOverride(Size finalSize)
{
double ChildX = 0;
double ChildY = 0;
double FinalHeight = 0;
double FinalWidth = 0;
//horizontal orientation - children in rows
if (Orientation == Orientation.Horizontal)
{
double RowHeight = 0;


//iterate over children
for (int idx = 0; idx < this.Children.Count; idx++)
{
//if we are about to go beyond width bounds with the next child
if (ChildX + Children[idx].DesiredSize.Width
>= finalSize.Width)
{
//move to next row
ChildY += RowHeight;
FinalHeight += RowHeight;
FinalWidth = Math.Max(FinalWidth, ChildX);
//shift to the left edge to start next row
ChildX = 0;
}
//if we are within width bounds
if (ChildX + Children[idx].DesiredSize.Width
< finalSize.Width)
{
//lay out child at the current X,Y coords with
//the desired width and height
Children[idx].Arrange(new Rect(ChildX, ChildY,
Children[idx].DesiredSize.Width,
Children[idx].DesiredSize.Height));
//increment X value to position next child horizontally right after the
//currently laid out child
ChildX += Children[idx].DesiredSize.Width;
//set the row height if this child is taller
//than the others in the row so far
RowHeight = Math.Max(RowHeight,
Children[idx].DesiredSize.Height);
}
}
}
else //vertical orientation - children in columns
{
double ColWidth = 0;
//iterate over children
for (int idx = 0; idx < this.Children.Count; idx++)
{
//if we are about to go beyond height bounds with the next child
if (ChildY + Children[idx].DesiredSize.Height
>= finalSize.Height)
{
//move to next column
ChildX += ColWidth;


FinalWidth += ColWidth;
FinalHeight = Math.Max(FinalHeight, ChildY);
//shift to the top edge to start next column
ChildY = 0;
}
//if we are within height bounds
if (ChildY + Children[idx].DesiredSize.Height
< finalSize.Height)
{
//lay out child at the current X,Y coords with
//the desired width and height
Children[idx].Arrange(new Rect(ChildX, ChildY,
Children[idx].DesiredSize.Width,
Children[idx].DesiredSize.Height));
//increment Y value to position next child vertically right below the
//currently laid out child
ChildY += Children[idx].DesiredSize.Height;
//set the column width if this child is wider
//than the others in the column so far
ColWidth = Math.Max(ColWidth,
Children[idx].DesiredSize.Width);
}
}
}
//return the original final size
return finalSize;
}
}
}


Let's first look at the measure pass. As noted previously, in MeasureOverride(), you are given the available size to work with, and you return the total desired size of the container in question with all its children. You can see in Listing 1 that you start off by calling Measure() on every child in the Children collection.

It is worth noting here that the measuring and arranging tasks are both recursive in nature. When you call Measure() on every child, the runtime ultimately calls MeasureOverride() on that child, which in turn calls Measure() on any children that child might have, and so on until MeasureOverride() gets called on every leaf element (i.e., an element without any more children). The desired size returned by MeasureOverride() at every level of recursion travels back to its parent and is available through the DesiredSize property on the child.

Once you call Measure() on each of the children in your code, and consequently populate the DesiredSize property on each of them, you then need to calculate the desired size of the entire WrapPanel based on the individual desired sizes of each child. To do that, you iterate over the Children collection and try to arrange them along rows or columns, based on the Orientation value of Horizontal or Vertical, respectively. Note that you do not actually create any rows or columns; rather, you simply try to calculate the size of such rows or columns.

So for example, in a Horizontal orientation, as you iterate over each child you add its width to a counter named RowWidth, indicating the current row's width. You also keep a track of the row's height by constantly evaluating the maximum height among the children added to that row up to that point. Once you reach a point where the addition of the next child would cause the row to go beyond the Width component of the DesiredSize parameter, you consider the row complete.

At this point, you track the maximum width of any such row calculated so far in a counter named DesiredWidth. The assumption is that the children could all have different sizes. In case they are all similarly sized, all those rows would be equal width as well, since the rows would break off at the exact same point every time. You also keep a measure of how much you are consuming on the Y axis with each row, using a counter named DesiredHeight, by adding up each row's height.

If the orientation was vertical, a similar logic is followed, with height and width interchanged. Once you have iterated over each child, you have your desired size in the combination of the DesiredWidth and DesiredHeight counters, and you pass that out of MeasureOverride().

The arrange pass is similar in logic. You get the finalSize as the parameter to ArrangeOverride(). You break up your logic based on the Orientation setting as before. But this time, you actually lay each child out by calling the Arrange() method on the child. The UIElement.Arrange() method accepts a Rectangle and lays the element inside that Rectangle. As you iterate through each element, you increment placement coordinates (either the x value or the y value based on whether you are laying out in rows or columns) to position child elements one after the other, and when you reach bounds where you have to break into the next row or column, you move by either the row height or the column width calculated in a similar fashion, as you did in the MeasureOverride() implementation.

The Orientation property is implemented as a dependency property of type System.Windows.Controls.Orientation that can be used to specify a horizontal or vertical layout. In OrientationPropertyChangedCallback(), you call InvalidateMeasure() on the WrapPanel instance, if the property value is being changed. InvalidateMeasure() causes the layout system to redo the layout, starting again with the measure pass.

Other  
 
Most View
Microsoft SharePoint 2010 Web Applications : Presentation Layer Overview - Ribbon (part 1)
The Cyber-athletic Revolution – E-sports’ Era (Part 1)
Windows Server 2003 : Implementing Software Restriction Policies (part 4) - Implementing Software Restriction Policies - Creating a Path Rule, Designating File Types
Sql Server 2012 : Hierarchical Data and the Relational Database - Populating the Hierarchy (part 1)
Two Is Better Than One - WD My Cloud Mirror
Programming ASP.NET 3.5 : Data Source-Based Data Binding (part 3) - List Controls
Windows 8 : Configuring networking (part 5) - Managing network settings - Understanding the dual TCP/IP stack in Windows 8, Configuring name resolution
Nikon Coolpix A – An Appealing Camera For Sharp Images (Part 2)
Canon PowerShot SX240 HS - A Powerful Perfection
LG Intuition Review - Skirts The Line Between Smartphone And Tablet (Part 2)
Popular Tags
Microsoft Access Microsoft Excel Microsoft OneNote Microsoft PowerPoint Microsoft Project Microsoft Visio Microsoft Word Active Directory Biztalk Exchange Server Microsoft LynC Server Microsoft Dynamic Sharepoint Sql Server Windows Server 2008 Windows Server 2012 Windows 7 Windows 8 Adobe Indesign Adobe Flash Professional Dreamweaver Adobe Illustrator Adobe After Effects Adobe Photoshop Adobe Fireworks Adobe Flash Catalyst Corel Painter X CorelDRAW X5 CorelDraw 10 QuarkXPress 8 windows Phone 7 windows Phone 8 BlackBerry Android Ipad Iphone iOS
Top 10
Review : Acer Aspire R13
Review : Microsoft Lumia 535
Review : Olympus OM-D E-M5 Mark II
TomTom Runner + MultiSport Cardio
Timex Ironman Run Trainer 2.0
Suunto Ambit3 Peak Sapphire HR
Polar M400
Garmin Forerunner 920XT
Sharepoint 2013 : Content Model and Managed Metadata - Publishing, Un-publishing, and Republishing
Sharepoint 2013 : Content Model and Managed Metadata - Content Type Hubs