Not too long ago I read a question from someone who wanted to know how to make the Accordion control display more than one child at time. The response said to use a VBox.
This question and the response intrigued me, so I set out to create a control which would allow any number of its children to be open simultaneously. After some tinkering I came up with the Stack components: VStack and HStack, show in the following figures.
VStack with 3 children open
VStack with middle child open, rest closed
HStack with 3 children open
HStack with middle child closed, rest open
The VStack component looks very much like Accordion, except zero, one, or any number of its children can be visible at once. HStack is the horizontal equivalent.
These might not be the most useful components, but there are some interesting things you can use in your own components. You can download the source (a zip file Flex Builder 2 project) here: Stack Component Source.
How It’s Made
I created a base component called StackBase which extends UIComponent. StackBase has a single child: a Box container; VStack extends StackBase and makes the child a VBox and HStack extends StackBase and makes the child an HBox.
My goal was to capitalize on the layout management already built into the Box classes and to use "off-the-shelf" parts when possible, making the component easier write, understand, and maintain.
I wanted the Stack components to behave just like any other Flex navigator component, such as Accordion. Meaning, I wanted to be able to use it like this:
<!– canvas children here –>
<mx:Canvas label="Page 2">
<!– canvas children here –>
<!– etc. –>
I used the [DefaultProperty] metadata tag to tell the Flex compiler which property should be used if none is specified. Now it might not look obvious, but those Canvas containers within the VStack definition do belong to a property – contents – of the StackBase class. Open StackBase.as and you’ll see what I mean:
public class StackBase extends UIComponent …
and further down you’ll see the contents property defined:
public function set contents( value:Array ) : void …
The contents property is an Array and for the Stack components, it is an Array of mx.core.Container classes (or any class that extends Container, such as Canvas). Just try and put something other than a Container in stack and you’ll get the same error as if you were using a ViewStack or Accordion.
Creating the Content
The StackBase createChildren() method creates the Box. The contents – children of the Box – cannot be created at this time in the component life cycle because the contents property may not be set. A better place is in commitProperties().
In case you didn’t know, commitProperties() is called once all of the properties have been set and the component children created (or in response to invalidateProperties()). The children may not yet be visualized, but they are available to have their own properties set. This means the contents Array is set with the components to go into the Box and the Box is ready to accept children.
The process of creating the content actually involves creating a couple of additional components. The content given in the MXML file is not made directly the children of the Box. Instead, a Canvas is created and it is given the content children and a control to open and close the child. This is a StackHeaderButton control which is part of this package.
When the content has been created you have:
The trick to this component is sizing the content properly. Suppose for example you have a VStack with 3 children. If all of them are visible, how much room do they take up? Suppose only one of them is visible?
The solution is that all of the open children evenly divide the space available, minus the space taken by the StackHeaderButtons. Using the example, when all three are visible they use approximately 1/3 each of the space. If one is closed, the remaining two occuply 1/2 of the the space. You are welcome to take this component and modify it to do something differently.
The StackHeaderButtons not only visually separate the content but they also open and close the content. When you click on a header, the child slides either open or closed and the remaining children have their sizes adjusted. This should appear fairly smooth because I used a Resize effect to do this.
My algorithm goes something like this:
First count the number of children in the content which are closed ("collapsed" in the code). Then take the space occupied of the Stack control and divide it evenly among the open children, subtracting the space taken for the StackHeaderButtons, of course.
As this is being done, a Resize effect is created for each child. Afterall, when one child closes the open ones increase their size.
All of the Resize effects are placed into a Parallel effect so that all of the adjustments are done at once. When the calculations have completed the Parallel effect is played and the contents adjust to their new sizes.
VStack vs. HStack
I had originally planned this to be a vertical control, but after I saw how it all came together I decided to add in the HStack control. I changed StackBase to use some protected functions for determining the size and position of its child content and the HStack component overrides these functions and returns width instead of height or x instead of y.
Mostly the controls are the same and StackBase takes care of the majority of the work.
The StackHeaderButton uses a skin class for its appearance. A skin is a class whose sole job is to provide the visualization for a component. I started off using a simple Button for the header, but decided it was easier to rotate a Label than the label of a Button. You can change the appearance of the headers just by writting your own skin classes.
Even if you don’t find these controls useful themselves, use them as a guide to building your own components. I have to admit that using Box, Canvas, and Resize made the job easier, but if you want to write the whole thing from scratch, go for it. Just pay attention to the Flex framework component life cycle.
Some things of note in this component are:
[DefaultProperty] and [ArrayElementType] meta tags. These tags make it easier for people to use the component.
Resize and Parallel effects. You can make a whole lot happen all at once and make the control appealing to use.
Skins. Think about how your component is visualized and then write those separately as skins. This will make customizing its appearance easier and it separates the function of the component from its presentation.
Embedded Fonts. For the HStack to look correct, the labels on the StackHeaderButtons are rotated 90 degrees. They would be invisible if you didn’t use an embedded font for them. To make things speedy, the Flash Player uses system fonts for most of the text. But system fonts do not have vector paths (outlines) that describe the letters, so they cannot be rotated. By embedding a font you can rotate, skew, and scale text controls easily.