In This Article

Building a Custom Panel

This topic describes step-by-step how to create a custom panel that supports animation and SwitchPanel. Specifically, the panel created in this tutorial will randomly arrange its child elements.

Note

The complete source code for this tutorial can be found in the associated Sample Browser.

Step 1: Class Declaration

The panel will be named RandomPanel and needs to derive from PanelBase.

The using statements listed below have been included as they will all be needed to complete this tutorial. But Visual Studio can be used to automatically add the appropriate using statements for unknown types. Since this panel can be found in the associated samples, the same namespace is used here.

using System;
using System.Collections.Generic;
using System.Windows;
using ActiproSoftware.Windows.Controls.Views;
using ActiproSoftware.Windows.Controls.Views.Primitives;

namespace ActiproSoftware.Windows.ProductSamples.ViewsSamples.QuickStart.CustomPanelRandom {

	/// <summary>
	/// Represents a custom panel that randomly arranges its child elements.
	/// </summary>
	public class RandomPanel : PanelBase {

		// SEE BELOW

	}
}

Step 2: Random Field

An instance of the Random class will be needed to randomly generate the locations of the elements.

Add the following inside the Random class:

private Random random = new Random(Environment.TickCount);

Step 3: Override MeasureElements

The PanelBase.MeasureElements method must be overridden to measure the associated elements.

Typically, a Panel will measure/arrange the elements found in the Children collection. In order to support SwitchPanel, the panel must measure/arrange the list of elements provided as a method parameter. When the RandomPanel is used by a SwitchPanel, the list includes the elements contained in the SwitchPanel. When the RandomPanel is used independently, the list simply references the Children collection.

Add the following inside the Random class:

/// <summary>
/// Measures the size in layout required for the specified elements and determines a size for the <see cref="FrameworkElement"/>-derived class.
/// </summary>
/// <param name="elements">The elements to be measured.</param>
/// <param name="availableSize">The available size that this element can give to the specified elements. Infinity can be specified as a value to indicate that
/// the element will size to whatever content is available.</param>
/// <returns>
/// The size that this element determines it needs during layout, based on its calculations of the specified elements.
/// </returns>
public override Size MeasureElements(IList<UIElement> elements, Size availableSize) {

	// SEE BELOW

}

Step 4: Measurement Logic

The measurement logic of the RandomPanel simply passes the given available size to the elements. It returns a desired size that will fit the largest element, both horizontally and vertically.

Add the following inside the MeasureElements method:

// Measure each element, and return the largest width and height needed
Size desiredSize = new Size();
foreach (UIElement element in elements) {
	if (null != element) {
		element.Measure(availableSize);

		desiredSize.Width = Math.Max(element.DesiredSize.Width, desiredSize.Width);
		desiredSize.Height = Math.Max(element.DesiredSize.Height, desiredSize.Height);
	}
}
return desiredSize;

Step 5: Override ArrangeElements

The PanelBase.ArrangeElements method must be overridden to arrange the associated elements.

Just like the measure phase, the specified list of elements should be arranged, not the elements in the Children collection. The RandomPanel will always take as much space as it's given, since it returns finalSize unaltered.

Add the following inside the Random class:

/// <summary>
/// Positions the specified elements and determines a size for a <see cref="FrameworkElement"/>-derived class.
/// </summary>
/// <param name="elements">The elements to be arranged.</param>
/// <param name="finalSize">
/// The final area within the parent that this element should use to arrange itself and the specified elements.
/// </param>
/// <returns>The actual size used.</returns>
public override Size ArrangeElements(IList<UIElement> elements, Size finalSize) {

	// SEE BELOW

	return finalSize;
}

Step 6: Arrangement Logic - Check IsLayoutUpdatePending

The PanelBase.IsLayoutUpdatePending property is used to flag when the layout logic of the RandomPanel, or the associated SwitchPanel, has changed. The animations used for a change in a layout logic change can be configured differently than standard arrange rectangle changes. These two distinct states are represented by ArrangeStatus.LayoutUpdating and ArrangeStatus.ArrangeUpdating, respectively.

Derived panels are responsible for checking and using the IsLayoutUpdatePending flag, so both statuses are correctly applied.

Important

If the flag is not properly used, then the "layout updating" animations and settings will not be used. Instead, the "arrange updating" animations and settings will always be used.

Add the following inside the ArrangeElements method above the "SEE BELOW" comment marker:

// Cache and reset layout pending flag
bool isLayoutUpdatePending = this.IsLayoutUpdatePending;
this.IsLayoutUpdatePending = false;

Step 7: Arrangement Logic - Determine Location

Using the random field, a unique x and y coordinate will be calculated for each element. Determining an x and y coordinate is similar to how the native WPF Canvas works but can actually be applied to any panel. In reality, the final rectangle of the element is being determined.

In addition to randomly placing the elements, this logic will ensure that the element is fully visible inside the panel, when possible.

Add the following inside the ArrangeElements method in place of the "SEE BELOW" comment marker:

// Iterate over the elements and arrange
foreach (UIElement element in elements) {
	if (element != null) {
		Size desiredSize = element.DesiredSize;

		// Calculate a random x/y position that keeps the element in the view
		double x = Math.Max(random.NextDouble() * (finalSize.Width - desiredSize.Width), 0);
		double y = Math.Max(random.NextDouble() * (finalSize.Height - desiredSize.Height), 0);

		// SEE BELOW
	}
}

Step 8: Arrangement Logic - Update ArrangeState

Instead of directly arranging elements, derivations of PanelBase update the "arrange state". This information is captured in an instance of ArrangeState and is set on an element via an attached property.

The PanelBase will then watch for state changes and animate an element based on its state (i.e., entering the view, leaving the view, updating location, etc.) and the current animation settings.

This logic will also prevent elements from moving when one or more sibling elements are leaving the scene.

Add the following inside the ArrangeElements method in place of the "SEE BELOW" comment marker:

// Update the arrange state with the new arrange rect, but if there are leaving elements then don't move
//   the element
ArrangeState state = new ArrangeState(element, false, isLayoutUpdatePending);
if (!this.HasLeavingChildren)
	state.ArrangeRect = new Rect(x, y, desiredSize.Width, desiredSize.Height);
else
	state.ArrangeRect = state.PreviousArrangeRect;

PanelBase.SetArrangeState(element, state);

Step 9: Use RandomPanel

The RandomPanel is completed and ready to be used in your applications.

xmlns:sample="clr-namespace:ActiproSoftware.Windows.ProductSamples.ViewsSamples.QuickStart.CustomPanelRandom"
...
<sample:RandomPanel>
	<!-- Both buttons will be randomly placed -->
	<Button Content="One" \>
	<Button Content="Two" \>
</sample:RandomPanel>

Step 10: Custom Animations

The RandomPanel supports all same the built-in animations and settings as the built-in panels, as well as any custom animations created. The following will change will have elements entering the scene fly in from random locations, and leaving elements will fly out.

xmlns:views="http://schemas.actiprosoftware.com/winfx/xaml/views"
xmlns:sample="clr-namespace:ActiproSoftware.Windows.ProductSamples.ViewsSamples.QuickStart.CustomPanelRandom"
...
<sample:RandomPanel>
	<sample:RandomPanel.ArrangeAnimation>
		<views:ArrangeAnimation EnterAnimation="Fade,Rotate,Scale,Translate" LeaveAnimation="Fade,Rotate,Scale,Translate"
		                        ScaleEnterUniformScale="0" ScaleLeaveUniformScale="5" />
	</sample:RandomPanel.ArrangeAnimation>
	<!-- Both buttons will be randomly placed -->
	<Button Content="One" \>
	<Button Content="Two" \>
</sample:RandomPanel>