Here’s one more example of a custom panel. The code below is for a panel that arranges its children in a very simple treemap sort of structure. (This implementation isn’t really a treemap, but vaguely similar to what has been described in the literature).
The panel defines a Weight attached property that the child elements use to indicate a relative weight. The panel then sorts the children based on weight and arranges them such their final area is proportional to their weight.
public class ChildAndRect
{
public UIElement Element { get; set; }
public Rect Rectangle { get; set; }
}
public class WeightedPanel : Panel
{
private static FrameworkPropertyMetadata weightMetadata =
new FrameworkPropertyMetadata(1.0,
FrameworkPropertyMetadataOptions.AffectsParentArrange);
public static readonly DependencyProperty WeightProperty =
DependencyProperty.RegisterAttached("Weight", typeof(double),
typeof(WeightedPanel), weightMetadata);
public static void SetWeight(DependencyObject depObj, double value)
{
depObj.SetValue(WeightProperty, value);
}
// Measure phase
protected override Size MeasureOverride(Size availableSize)
{
double totalWeight = totalChildWeight();
foreach (ChildAndRect child in ChildrenTreemapOrder(InternalChildren.Cast<UIElement>(), availableSize))
child.Element.Measure(child.Rectangle.Size);
return availableSize;
}
// Arrange phase
protected override Size ArrangeOverride(Size finalSize)
{
foreach (ChildAndRect child in ChildrenTreemapOrder(InternalChildren.Cast<UIElement>(), finalSize))
child.Element.Arrange(child.Rectangle);
return finalSize;
}
private double totalChildWeight()
{
double weightSum = 0;
foreach (UIElement elem in InternalChildren)
weightSum += (double)elem.GetValue(WeightProperty);
return weightSum;
}
/// <summary>
/// Return child elements orderd by weight (largest to
/// smallest), passing back Rect for each child
/// (size and location), implementing a (crude)
/// treemap.
/// </summary>
/// <param name="elems">Child elements to measure/arrange</param>
/// <param name="containerSize">Available container size</param>
/// <returns></returns>
private IEnumerable<ChildAndRect> ChildrenTreemapOrder(IEnumerable<UIElement> elems, Size containerSize)
{
double remainingWeight = totalChildWeight();
double top = 0.0;
double left = 0.0;
// Alternate between left edge and top edge
bool leftEdge;
// Sort by weight
var childrenByWeight = elems.OrderByDescending(
e => (double)e.GetValue(WeightProperty));
// Allocate space for each child, one at a time.
// Moving left to right, top to bottom
foreach (var child in childrenByWeight)
{
leftEdge = (containerSize.Width - left) > (containerSize.Height - top);
Size size;
double childWeight = (double)child.GetValue(WeightProperty);
double pctArea = childWeight / remainingWeight;
remainingWeight -= childWeight;
// Entire height, proportionate width
if (leftEdge)
size = new Size(pctArea * (containerSize.Width - left), containerSize.Height - top);
// Top edge - Entire width, proportionate height
else
size = new Size(containerSize.Width - left, pctArea * (containerSize.Height - top));
yield return new ChildAndRect { Element = child, Rectangle = new Rect(new Point(left, top), size) };
if (leftEdge)
left += size.Width;
else
top += size.Height;
}
}
}
Below, we use the panel to create labels representing several states. The Weight property is used to record the states’ area. (The states are in no particular order).
<loc:WeightedPanel>
<Label Content="Oregon" loc:WeightedPanel.Weight="93381"
Background="Bisque" />
<Label Content="California" loc:WeightedPanel.Weight="163696"
Background="Lavender" />
<Label Content="Colorado" loc:WeightedPanel.Weight="104094"
Background="LightCoral" />
<Label Content="Montana" loc:WeightedPanel.Weight="147042"
Background="Honeydew" />
<Label Content="Nevada" loc:WeightedPanel.Weight="110561"
Background="Goldenrod" />
<Label Content="New Mexico" loc:WeightedPanel.Weight="121589"
Background="Silver" />
<Label Content="Texas" loc:WeightedPanel.Weight="268581"
Background="Thistle" />
<Label Content="Arizona" loc:WeightedPanel.Weight="113998"
Background="GhostWhite" />
</loc:WeightedPanel>
Here’s what this looks like at run-time:

Note: One improvement that could be made to this algorithm is to adopt a true implementation of a treemap algorithm that includes “squarifying” elements to reduce the number of “long skinny” child objects.