When I started using Flex 4 I tried to get up to speed by using the new version in a manner similar to its predecessor until I came across roadblocks or when I determined I was able to take advantage of the new features I had heard about such as skinning, states, etc. I quickly found that there were some obvious Flex questions that I was not able to answer without diving into the API docs and by playing with some examples so I could truly understand what is going on. I knew from past experience that Halo was hiding a lot of the dirty details about what was going on in the display list, but once you start using Spark, you will come to see just how much Halo was really doing under the covers and why Spark is a much more ‘honest’ way of working with the display list.
One of the first things I hit was the term ‘elements’. What is an element? Is it the same thing as a child? Initially I concluded that elements where just another way of saying children (wrong). I figured that all elements where child components and vice versa. Then I noticed that some of the methods that I was expecting to find inside the new containers (as well as some of the existing ones) were missing. Where is getElements()? How do I find out how many elements I have? Can I just use getChildren() and treat the result like an IVisualElement (which itself was a mystery to me at the time).
So I started to really read the API docs, Flex source and blog postings about these topics. I also tried to decipher the slides shows that some bloggers posted about the great new features of Flex 4, but it turns out the slides do not make much sense if you do not have the audio of the presentation to go along with them.
Finally, I started to put together some very basic demos that would help me explore the new world of elements and help me figure out what I was really doing when it came to using the new Spark containers.
I’ll start by stating the questions I had at the time. Question number one was “How do I get the all the elements of a spark container?” I was expecting a getElements() method similar to the familiar getChildren() method from Flex 3. Instead, what you have are two properties: numElements and numChildren. To obtain all of the elements of a container, you can use a for-loop with the numElements count and call the getElementAt() method for each one. Pretty obvious after you realize there is no getElements convenience method. Question number two was: “what is the difference between an element and a child?”. Let’s look at the difference between elements and children….
An element is basically anything that implements the IVisualElement interface. A child is anything that extends from DisplayObject. When trying to determine if a component is an element or a child or both, remember that a UIComponent is a descendant of DisplayObject, so all UIComponents are DisplayObjects and therefore can be children. Also know that UIComponent implements IVisualElement so all UIComponents are considered ‘elements’. However, this does not mean that all DisplayObjects are elements. A DisplayObject that is parented by a container (ie, contained within that container) is a child of that container. But it is only an element of that container if it also implements IVisualElement. So when is a DisplayObject a child of a container and when is a DisplayObject an element of that container? Some examples showing different types of containers will answer this question.
In the first example, we look at the result when the container in question is an older Halo container (in this case, a Halo Panel). Because Panel is a descendant of DisplayObjectContainer, it has the addChild() method. In addition, because it descends from Container (which implements IVisualElementContainer), it also has the addElement() method. We will see that the Container’s implementation of the IVisualElementContainer interface is just a facade to the display list API but it is implemented here so that it will have the same methods that we find in the newer Spark containers that also implement IVisualElementContainer.
So we can add both children and elements to this container. However, we cannot add just any type of element to it. There is a difference between VisualElements and GraphicElements. VisualElements implement the IVisualElement interface, but GraphicElements implement an extension of IVisualElement called IGraphicElement which has some extra features that the container needs to know about. Therefore, some elements (such as the GraphicElement) cannot be added directly to the Halo Panel. The compiler will complain that it needs to be wrapped in a Group container first. More on why in a second….
The Panel in the following example contains several UIComponents including another Halo Panel, a Spark Panel, some Halo Buttons, some Spark Buttons and a SkinnableContainer with child components of its own. (Note that the components inside the SkinnableContainer will NOT be children of the Panel. They are children of the SkinnableContainer.) All of these components are DisplayObjects so they are all ‘children’. Clicking the ‘Show Children” button reveals as much. In addition, all of these components are UIComponents (which implements IVisualElement) so they are all ‘elements’ as well.
Now take a look at the next example. This time the container in question is a Spark Group. Like the Halo Panel in the previous example, Group is a descendant of DisplayObjectContainer so it has the addChild() method and because it implements IVisualElementContainer, we can add IVisualElements (‘elements’) to it as well using addElement(). The Group container can also handle GraphicElements such as a Rect (a Rectangle GraphicElement) which the Halo Panel could not. This is because, unlike the Halo Panel, the Group object knows how to display GraphicElements in an optimized manner. What does that mean? Read on…
GraphicElements require a little more involvement from their parent containers than do regular VisualElements. The key here is the IGraphicElement interface that GraphicElements implement. As I mentioned before, it is an extension of the IVisualElement interface (which is why GraphicElements can be added to the Group through the Group’s addElement() method). But GraphicElements are not DisplayObjects so they cannot be seen unless they get ‘drawn’ onto another DisplayObject by their parent. So if you add a Rectangle to a Group, the Group will also need to have a DisplayObject to draw the Rectangle on so it will show up in that Group. Just to optimize things a bit, the Group can actually reuse the same DisplayObject to draw multiple GraphicElements when possible. The container can use any DisplayObject that implements ISharedDisplayObject to draw the GraphicElement. In the first example, the Halo Panel could not do this so it could not draw GraphicElements in this optimized manner. Thus, the compiler gave an error saying you have to wrap it in a container that can deal with this. The Group container can do this so GraphicElements can be added to a Group.
One other thing to note: while some GraphicElements will allow the Group parenting them to create a DisplayObject for that GraphicElement to be drawn in, others will create their own DisplayObjects to be drawn in, and this interface (IGraphicElement) allows the parent Group to manage that DisplayObject even though the parent Group did not create it.(ie to add the DisplayObject as a child so the IGraphicElement can be drawn into it).
So what does this mean? It means that in our next example, the number of children is not the same as the number of elements. This example uses the same set of components from the first example, but it adds 4 rectangles which are GraphicElements. The children are all IVisualElements. However, they are not all children. The rectangles are GraphicElements which do not descend from DisplayObject. BUT, the Group knows that it needs to add DisplayObjects so that the GraphicElements can be drawn inside the container. The rectangles are different sizes and rotations so the Group decided to add two new DisplayObjects to draw the four rectangles on. Pretty cool, huh?
Now look at example 3. Instead of a Group, we are using a SkinnableContainer. The SkinnableContainer has the same set of components as the previous example. But, the SkinnableContainer uses a Skin for its visual representation and that Skin is the first and only child of the SkinnableContainer. The SkinnableContainer’s default Skin class is comprised of a Rectangle (for the border) and a Group called the ContentGroup which all skins for containers need so Flex know where to add children to the container.
So this example illustrates the fact that, while the SkinnableContainer has 10 elements, it only has one child: the Skin of the SkinnableContainer. In turn, that skin has only one child: the ContentGroup Group component. You may be asking “why are there not 2 children of the Skin: one being the ContentGroup and the other being the DisplayObject for the Rectangle (serving as the border) to be drawn in? The answer is because the Skin class descends from the Group class and Groups will add DisplayObjects when necessary to draw any GraphicElements they contain. In this case, no new DisplayObjects were required. The Skin class just drew the Rect GraphicElement directly onto itself. It can do this because the Skin’s ancestor, the Group class, also implements ISharedDisplayObject which means that it can serve as a shared DisplayObject used to draw GraphicElements when necessary. So the Skin manages the DisplayObjects used to render the GraphicElement, and the in this case, the DisplayObject it is using to draw on is itself! If you added other Rectangles to a custom skin and assigned that to the SkinnableContainer, the Skin might decide it needed more DisplayObjects to draw those other Rectangles and you would therefore see more children in the Skin’s child list.
One other thing to note is that the number of elements stays the same when you look at the element list for the SkinnableContainer, its Skin, and the Skin’s ContentGroup. If you look at the source for SkinnableContainer, you will see that it determines the numElements value by getting the corresponding numElements value from the currentContentGroup so basically it is just re-routing you to its content group when you query for elements. Likewise, the Skin of the SkinnableContainer does something similar. It extends from Group, which gets the numElements property from its internal mxmlContent property which is an array of visual content children for this Group. Both of these are analogous to the RawChildren property of the Panel container which you used to get ALL the children of the Panel rather than just the children that you added to the Panel which came from the getChildren() method.
After reading all of this, it may not seem any clearer than when you started. You basically have to familiarize yourself with the inheritance hierarchy and relationship between 7 different classes/interfaces:
Once you know who these fit together, you’ll soon understand the difference between elements and children. I’m sure I’ve messed up some of the facts and gotten a few things wrong so feel free to put corrections in the comments section if you think I’m way off base on any of this.
UPDATE: Check out these links for more information on this and related topics: