There are multiple DOM trees in Flex 4. How are these exposed?
graphic element - A graphical element like a Rectangle, Path, or Bitmap. This element is not a subclass of DisplayObject; however, it does need a DisplayObject to render on to.
visual element - A visual element (a.k.a. - "element"). This can be a halo component, a gumbo component, or a graphic element. Visual elements implement IVisualElement
.
data item (a.k.a. - "item") - Basically anything is considered a data item. Mostly it refers to a non-visual item like a String, Number, XMLNode, etc. A visual element can also be a data item--it just depends on how it's treated.
Component Tree - The component tree represents the structure of an MXML document. For a simple example, consider a Panel
which contains a Label
. In this case, both the Panel
and the Label
are in the component tree, but the Panel's
skin is not.
Layout Tree - The layout tree represents the runtime layout. In this tree, parents are responsible for displaying and laying out the object, and children are the visual elements being laid out. For a simple example, consider a Panel
which contains a Label
. In this case, both the Panel
and the Label
are in the layout tree, as is the Panel's
skin and the contentGroup
in that skin.
Display Tree - This is the low-level Flash DisplayObject tree.
For Diagrams below, use the following legend:
When you create an application through MXML markup, a lot happens behind the scenes to translate this into Flash display objects that get displayed. The three main factors contributing to this are skinning, item rendering, and display object sharing. The first two are important concepts to developers; information about the last one is only needed for framework developers but is important to note.
When you instantiate a Button
, more than one object gets created. For instance:
<s:Button />
Results in a layout tree of:
(Note: TextBox has been renamed to Label)
There's a Skin file that gets instantiated and put into Button
's display list. The skin file for a Button
, looks like:
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" minWidth="23" minHeight="23">
<fx:Metadata>
<a href="HostComponent%28%26quot%3Bmx.components.Button%26quot%3B%29">HostComponent("mx.components.Button")</a>
</fx:Metadata>
<s:states>
<s:State name="up" />
<s:State name="over" />
<s:State name="down" />
<s:State name="disabled" />
</s:states>
<!-- background -->
<s:Rect left="0" right="0" top="0" bottom="0"
width="70" height="23"
radiusX="2" radiusY="2">
<s:stroke>
<s:SolidColorStroke color="0x5380D0" color.disabled="0xA9C0E8" />
</s:stroke>
<s:fill>
<s:SolidColor color="0xFFFFFF" color.over="0xEBF4FF" color.down="0xDEEBFF" />
</s:fill>
</s:Rect>
<!-- label -->
<s:Label id="labelDisplay" />
</s:Skin>
So even though Button
looks like a leaf node, because of skinning, it actually contains children. To access these elements, use the skin
property defined on all SkinnableComponent
s. This gives you the ButtonSkin
instance, which will let you access the Rectangle
and Label
. So to access the Label
, you would say: myButton.skin.getElementAt(2)
or myButton.skin.labelDisplay
. Since labelDisplay
is an Button
skin part, you can also use myButton.labelDisplay
.
This same principle applies equally to SkinnableContainers
. SkinnableContainers
are containers that innately have children, but they are also SkinnableComponents
that have a skin and the children from that skin.
Taking Panel
as an example:
<s:Panel>
<s:Button />
<s:Label />
<s:CheckBox />
</s:Panel>
The panel has three children: a button, a label, and a checkbox. To access them, use the content APIs defined on SkinnableContainer
. The content APIs mimic the flash DisplayObjectContainer
APIs, and include addElement(), addElementAt(), getElementAt(), getElementIndex(), etc…
. The full list of these methods is listed later in the document.
Because the panel has 3 children, its component tree looks like:
(Note: TextBox has been renamed to Label)
However, that's the component tree. Because of skinning, Panel
's real layout tree looks like:
(Note: TextBox has been renamed to Label)
There are a lot of arrows in the diagram above. The important items to note are:
Panel's
component children are: button, label, and checkbox.owner
property) is the Panel.parent
property) is the contentGroup
of the PanelSkin
.What this means is that even though it looks like Panel's
children should be a button, a label, and a checkbox; it's only real child is a panel skin instance. And the button, label, and checkbox get pushed down to become children of the contentGroup
in the skin file. There are a few ways to access the Button
in the panel: myPanel.getElementAt(0)
or myPanel.contentGroup.getElementAt(0)
or myPanel.skin.contentGroup.getElementAt(0)
.
All SkinnableComponent's
have a skin
property. In an SkinnableContainer
, the children of the components are actually pushed down to the skin's contentGroup
. The component tree refers to the semantic tree translated from MXML. In the Panel
example, this would just include, the Panel
and its children: a button, a label, and a checkbox. The layout tree refers to the actual tree seen by the layout system, the layout tree due to skinning. In the Panel
example, this would include the panel, the panel skin, all the panel skin's children, and all the panel's children that are actually pushed down into the contentGroup
of the panel skin.
The layout tree doesn't necessarily correlate to the display list tree that Flash sees. This is because GraphicElements
are not innately display objects. Because of performance reasons, they implement display object sharing to minimize the number of display objects.
IVisualElementContainer
is the interface that defines the content APIs. In Spark, Skin
, Group
, and SkinnableContainer
are the components for holding visual elements and implement this interface. To provide consistency, MX's Container
will also implement this interface and just be a facade for addChild(), numChildren, etc…
.
package mx.core
{
public interface IVisualElementContainer
{
//----------------------------------
// Visual Element iteration
//----------------------------------
/**
* The number of elements in this group.
*
* @return The number of visual elements in this group
*/
public function get numElements():int;
/**
* Returns the visual element that exists at the specified index.
*
* @param index The index of the element to retrieve.
*
* @return The element at the specified index.
*
* @throws RangeError If the index position does not exist in the child list.
*/
public function getElementAt(index:int):IVisualElement
//----------------------------------
// Visual Element addition
//----------------------------------
/**
* Adds a visual element to this visual container. The element is
* added after all other elements and on top of all other elements.
* (To add a visual element to a specific index position, use
* the <code>addElementAt()</code> method.)
*
* <p>If you add a visual element object that already has a different
* container as a parent, the element is removed from the child
* list of the other container.</p>
*
* @param element The element to add as a child of this visual container.
*
* @return The element that was added to the visual container.
*
* @event elementAdded ElementExistenceChangedEvent Dispatched when
* the element is added to the child list.
*
* @throws ArgumentError If the element is the same as the visual container.
*/
public function addElement(element:IVisualElement):IVisualElement;
/**
* Adds a visual element to this visual container. The element is
* added at the index position specified. An index of 0 represents
* the first element and the back (bottom) of the display list, unless
* <code>layer</code> is specified.
*
* <p>If you add a visual element object that already has a different
* container as a parent, the element is removed from the child
* list of the other container.</p>
*
* @param element The element to add as a child of this visual container.
*
* @param index The index position to which the element is added. If
* you specify a currently occupied index position, the child object
* that exists at that position and all higher positions are moved
* up one position in the child list.
*
* @return The element that was added to the visual container.
*
* @event elementAdded ElementExistenceChangedEvent Dispatched when
* the element is added to the child list.
*
* @throws ArgumentError If the element is the same as the visual container.
*
* @throws RangeError If the index position does not exist in the child list.
*/
public function addElementAt(element:IVisualElement, index:int):IVisualElement;
//----------------------------------
// Visual Element removal
//----------------------------------
/**
* Removes the specified visual element from the child list of
* this visual container. The index positions of any elements
* above the element in this visual container are decreased by 1.
*
* @param element The element to be removed from the visual container.
*
* @return The element removed from the visual container.
*
* @throws ArgumentError If the element parameter is not a child of
* this visual container.
*/
public function removeElement(element:IVisualElement):IVisualElement;
/**
* Removes a visual element from the specified index position
* in the visual container.
*
* @param index The index of the element to remove.
*
* @return The element removed from the visual container.
*
* @throws RangeError If the index does not exist in the child list.
*/
public function removeElementAt(index:int):IVisualElement;
//----------------------------------
// Visual Element index
//----------------------------------
/**
* Returns the index position of a visual element.
*
* @param element The element to identify.
*
* @return The index position of the element to identify.
*
* @throws ArgumentError If the element is not a child of this visual container.
*/
public function getElementIndex(element:IVisualElement):int;
/**
* Changes the position of an existing visual element in the visual container.
*
* <p>When you call the <code>setElementIndex()</code> method and specify an
* index position that is already occupied, the only positions
* that change are those in between the elements's former and new position.
* All others will stay the same.</p>
*
* <p>If a visual element is moved to an index
* lower than its current index, the index of all elements in between increases
* by 1. If an element is moved to an index
* higher than its current index, the index of all elements in between
* decreases by 1.</p>
*
* @param element The element for which you want to change the index number.
*
* @param index The resulting index number for the element.
*
* @throws RangeError - If the index does not exist in the child list.
*
* @throws ArgumentError - If the element parameter is not a child
* of this visual container.
*/
public function setElementIndex(element:IVisualElement, index:int):void;
//----------------------------------
// Visual Element swapping
//----------------------------------
/**
* Swaps the index of the two specified visual elements. All other elements
* remain in the same index position.
*
* @param element1 The first visual element.
* @param element2 The second visual element.
*/
public function swapElements(element1 :IVisualElement, element2 :IVisualElement):void;
/**
* Swaps the visual elements at the two specified index
* positions in the visual container. All other visual
* elements remain in the same index position.
*
* @param index1 The index of the first element.
*
* @param index2 The index of the second element.
*
* @throws RangeError If either index does not exist in
* the visual container.
*/
public function swapElementAt(index1:int, index2:int):void;
}
}
This interface makes it easy to descend the tree. Essentially, the interface is a way for containers to advertise what their children are. For example, it will be used by the FocusManager
for this purpose. This will help so that the focus manager won't be dependent on Group
or other Spark code (just this interface), and MX applications won't add much code-weight because of it. We debated not adding the mutation APIs here or not having MX implement these APIs, but we think it's more useful for developers (and framework developers) to have all containers (MX and Spark) implement this interface with the mutation APIs. As we look at DataGroup and SkinnableDataContainer, they do not implement IVisualElementContainer
, though DataGroup has some of the same "read-only" methods, like numElements
and getElementAt()
.
The IVisualElementContainer
holds IVisualElements
. IVisualElement
is a new interface for visual elements. It will contain a small set of properties and methods needed in order for containers to add the element. It extends ILayoutElement
and adds a few other properties.
////////////////////////////////////////////////////////////////////////////////
//
// ADOBE SYSTEMS INCORPORATED
// Copyright 2003-2008 Adobe Systems Incorporated
// All Rights Reserved.
//
// NOTICE: Adobe permits you to use, modify, and distribute this file
// in accordance with the terms of the license agreement accompanying it.
//
////////////////////////////////////////////////////////////////////////////////
package mx.core
{
/**
* The IVisualElement interface defines the minimum properties and methods
* required for a visual element to be laid out and displayed in a Spark container.
*/
public interface IVisualElement extends ILayoutElement
{
/**
* The owner of this IVisualElement object.
* By default, it is the parent of this IVisualElement object.
* However, if this IVisualElement object is a child component that is
* popped up by its parent, such as the drop-down list of a ComboBox control,
* the owner is the component that popped up this IVisualElement object.
*
* <p>This property is not managed by Flex, but by each component.
* Therefore, if you use the <code>PopUpManger.createPopUp()</code> or
* <code>PopUpManger.addPopUp()</code> method to pop up a child component,
* you should set the <code>owner</code> property of the child component
* to the component that popped it up.</p>
*
* <p>The default value is the value of the <code>parent</code> property.</p>
*/
function get owner():DisplayObjectContainer;
function set owner(value:DisplayObjectContainer):void;
/**
* The parent container or component for this component.
* Only visual elements should have a <code>parent</code> property.
* Non-visual items should use another property to reference
* the object to which they belong.
* By convention, non-visual objects use an <code>owner</code>
* property to reference the object to which they belong.
*/
function get parent():DisplayObjectContainer;
...OTHER STUFF NOT DISCUSSED HERE...
}
}
The parent of a visual element is the container directly in charge of laying it out. The owner of a visual element is the component that logically owns it. If a Button is in a SkinnableContainer
, its parent is the contentGroup
while its owner is the SkinnableContainer
.
Note that the parent
and owner
properties are typed as DisplayObjectContainer
and not IVisualElementContainer
. This is because in MX, these properties were typed as
DisplayObjectContainer
. Moreover, because the parent
property is inherited from Flash's DisplayObject
, we couldn't change it. We debated coming up with new names for these properties, but in the end, didn't think it was worth it.
MX components had the same concepts as above, but a lot of it was hidden behind the scenes. Spark components are more transparent about what's actually going on as skinning is more exposed than before.
An MX button has one child, the TextField. This child was added directly to the Button via addChild() (there was no skin). For instance, the TextField for the Button was a direct child of the Button. So if you asked Button for its children, it would give you the TextField. If you asked the TextField for its parent, it would give you the Button.
In Spark, a Button has one child, the skin object. The skin object contains a Label. If you ask the Button for its display object children, it would tell you it has one child: the skin. If you want to determine the skinning children of a Button, you should go to the skin object and get the skinning children there.
Containers are more difficult and have both the component children and the children in the skin. In MX, a Panel's display list would contain the children due to skinning and a special child called "contentPane." All of the panel's component children would get pushed into the contentPane. This is very similar to the Spark story; however, a lot of this was hidden to the developer in MX. If you asked the Panel for its display list children, it would actually lie to you and give you the contentPane children (Panel's component children). In order to access the skinning children, there was a list of children available through the rawChildren
property. If you asked a Panel's component child for its parent, it would tell you the panel, even though it's real display list parent was the contentPane.
In Spark, IVisualElementContainer
is an interface that gives you a way to access children. It's also a way for Spark components to advertise who their visual children are. Group
and SkinnableContainer
will implement this interface. In addition, MX's Container
will implement this interface. It'll just facade to the display list APIs, but by doing so, IVisualElementContainer
will provide a single, consistent way to access a container's children.
In Spark, for a SkinnableContainer
, the DisplayList API is still there. However, if you try to manipulate the DisplayList through this API, we will throw an RTE. If you ask for numChildren
or getChildAt()
, unlike in MX, Spark faithfully gives you whatever the display list actually is. If you call the "content API" (numElements, getElementAt()) on the SkinnableContainer
, it will give you its component children (the contentGroup's actual children). To access the skinning children (the "rawChildren" concept from MX components), you would go down to the skin object. If you asked a Panel's component child who its parent is, it would give you the contentGroup
(not the Panel, like it does in MX). However, there's another property, owner, that would give you the Panel. The owner property existed in MX as well, but in this scenario in MX, it was the same as the parent property. However, in Spark, owner and parent point to two different objects.
In Spark, there are two main types of containers: ones that hold only visual elements and ones that hold data items. DataGroup
and SkinnableDataContainer
are made to hold data items. Group
and SkinnableContainer
are made to hold visual elements. A data container can hold anything, but it is particularly useful for holding non-visual elements (i.e.-straight up data). The important thing about data containers is that they support item rendering and converting data items into visual elements that can be displayed on screen.
DataGroup
has the ability to take arbitrary, non-visual elements and render them onscreen. Because of this, item renderers are inserted into the layout tree as needed. In some cases, even visual elements, such as UIComponents
or GraphicElements
, are wrapped in item renderers as well. To expose this model to developers, we considered several options:
IVisualElementContainer
. When asked for the number of visual elements on screen, we return only the elements that are currently being renderred. Mutation APIs RTE.IVisualElementContainer
. When asked for the number of visual elements on screen, we return the total list of items. If the user asks for a visual element of something that isn't currently being renderred, we will create an item renderer for them.We decided to add the "read-only" element APIs to DataGroup
, like numElements
, getElementAt()
, and getElementIndex()
. There is also another API, getItemIndicesInView()
to determine which data items are in view.
As in MX, an item renderer's owner
property always refers to the same object as the owner property of the component it's wrapping. An item renderer's parent
property is the parent in charge of laying it out.
Here are some diagrams showing item rendering in action.
You'll notice a DataGroup
has no children in the component tree. This is because it's considered a leaf node--one that renders data. Here's a look at the layout tree:
(Note: TextBox has been renamed to Label)
In the example above, the String is not a visual element and needs an item renderer. An item renderer comes in and wraps the String object. Its owner
property is the DataGroup
. Because an itemRendererFunction is set, the same thing happens for Employee Object and the other String.
Developers typically interact with the component tree exclusively. Layouts and effects interact with the layout tree, as does FocusManager. Only low-level code like Group's DisplayObject sharing code will interact with the display tree.
public interface IVisualElementContainer {
public function get numElements():int;
public function getElementAt(index:int):IVisualElement;
public function getElementIndex(element:IVisualElement):int;
public function addElement(element:IVisualElement):IVisualElement;
public function addElementAt(element:IVisualElement, index:int):IVisualElement;
public function removeElement(element:IVisualElement):IVisualElement;
public function removeElementAt(index:int):IVisualElement;
public function setElementIndex(element:IVisualElement, index:int):void;
public function swapElements(element1:IVisualElement, element2:IVisualElement):void;
public function swapElementsAt(index1:int, index2:int):void;
}
public interface IVisualElement extends ILayoutElement {
owner:DisplayObjectContainer;
parent:DisplayObjectContainer;
...other stuff not discussed here...
}
public class UIComponent implements IVisualElement {
owner:DisplayObjectContainer;
parent:DisplayObjectContainer;
...other stuff...
}
public class GraphicElement implements IVisualElement {
owner:DisplayObjectContainer;
parent:DisplayObjectContainer;
...other stuff...
}
<a href="DefaultProperty%28%26quot%3Bcontent%26quot%3B%29">DefaultProperty("content")</a>
public class Group extends GroupBase implements IVisualElementContainer {
<a href="write-only">write-only</a> mxmlContent:Array;
layout:ILayout;
public function get numElements():int;
public function getElementAt(index:int):IVisualElement;
public function getElementIndex(element:IVisualElement):int;
public function addElement(element:IVisualElement):IVisualElement;
public function addElementAt(element:IVisualElement, index:int):IVisualElement;
public function removeElement(element:IVisualElement):IVisualElement;
public function removeElementAt(index:int):IVisualElement;
public function setElementIndex(element:IVisualElement, index:int):void;
public function swapElements(element1:IVisualElement, element2:IVisualElement):void;
public function swapElementsAt(index1:int, index2:int):void;
}
public class Skin extends Group {
}
public class SkinnableComponent extends UIComponent {
function get skin():Skin;
<a href="CSS">CSS</a> function set skinClass:Class;
}
<a href="DefaultProperty%28%26quot%3Bcontent%26quot%3B%29">DefaultProperty("content")</a>
public class SkinnableContainer extends SkinnableContainerBase implements IVisualElementContainer {
<a href="write-only">write-only</a> mxmlContent:Array;
public function get numElements():int;
public function getElementAt(index:int):IVisualElement;
public function getElementIndex(element:IVisualElement):int;
public function addElement(element:IVisualElement):IVisualElement;
public function addElementAt(element:IVisualElement, index:int):IVisualElement;
public function removeElement(element:IVisualElement):IVisualElement;
public function removeElementAt(index:int):IVisualElement;
public function setElementIndex(element:IVisualElement, index:int):void;
public function swapElements(element1:IVisualElement, element2:IVisualElement):void;
public function swapElementsAt(index1:int, index2:int):void;
<a href="SkinPart">SkinPart</a> contentGroup:Group;
}
public class Container extends UIComponent implements IVisualElementContainer {
public function get numElements():int;
public function getElementAt(index:int):IVisualElement;
public function getElementIndex(element:IVisualElement):int;
public function addElement(element:IVisualElement):IVisualElement;
public function addElementAt(element:IVisualElement, index:int):IVisualElement;
public function removeElement(element:IVisualElement):IVisualElement;
public function removeElementAt(index:int):IVisualElement;
public function setElementIndex(element:IVisualElement, index:int):void;
public function swapElements(element1:IVisualElement, element2:IVisualElement):void;
public function swapElementsAt(index1:int, index2:int):void;
}
<a href="DefaultProperty%28%26quot%3BdataProvider%26quot%3B%29">DefaultProperty("dataProvider")</a>
public class DataGroup extends UIComponent {
dataProvider:IList;
itemRenderer/itemRendererFunction;
layout:ILayout;
public function get numElements():int;
public function getElementAt(index:int):IVisualElement;
public function getElementIndex(element:IVisualElement):int;
public function getItemIndicesInView():Vector.<int>;
}
<a href="DefaultProperty%28%26quot%3BdataProvider%26quot%3B%29">DefaultProperty("dataProvider")</a>
public class SkinnableDataContainer extends SkinnableContainerBase {
dataProvider:IList;
layout:ILayout;
itemRenderer/itemRendererFunction;
<a href="SkinPart">SkinPart</a> dataGroup:DataGroup;
}
Some sample code for walking the trees:
public function walkTree(element:IVisualElement, proc:Function):void
{
proc(element);
if (element is IVisualElementContainer)
{
var visualContainer:IVisualElementContainer = IVisualElementContainer(element);
for (var i:int = 0; i < visualContainer.numElements; i++)
{
walkTree(visualContainer.getElementAt(i));
}
}
}
public function walkLayoutTree(element:IVisualElement, proc:Function):void
{
proc(element);
if (element is SkinnableComponent)
{
var skin:Skin = SkinnableComponent(element).skin;
walkTree(skin);
}
else if (element is IVisualElementContainer)
{
var visualContainer:IVisualElementContainer = IVisualElementContainer(element);
for (var i:int = 0; i < visualContainer.numElements; i++)
{
walkTree(visualContainer.getElementAt(i));
}
}
// expand this to MX and IRawChildrenContainer?
}
public function walkUpTree(element:IVisualElement, proc:Function):void
{
while (element!= null)
{
proc(element);
element = element .owner;
}
}
public function walkUpLayoutTree(element :IVisualElement, proc:Function):void
{
while (element != null)
{
proc(element );
element = element .parent;
}
}
One way to think about the parent
property is "who is laying you out." This also corresponds with your physical display list parent if you're a DisplayObject. (GraphicElements do a little faking here since they aren't display objects but it's the same concept).
The owner
property has a few purposes:
SkinnableContainer
)DateField
, to let you know who's in charge of the popup.The way to think about the owner
property is that an element's owner
refers to the component that's in charge of it.
The changes that need to be made are adding in a parent
property and an owner
property to GraphicElement
. We'd also need to add code in the proper places to hook up to these properties (item renderers and SkinnableContainer)
The other main part of this proposal is creating and implementing the interfaces.
We have the Group/DataGroup
split. This is work that is spec-ed separately and completed as a separate (though related) work item.
Lastly, there's work that needs to be done with the virtualization spec around how to expose the renderred visual elements.
owner
property is so that people can walk up the logical DOM. The reason why we're introducing the parent
property is so people can walk up the layout DOM. We debated not using the parent
property because it's typed as DisplayObjectContainer
, and at some point in the future, parent nodes may not necessarily be DisplayObjectContainers
. However, for now they are, and parent
just makes a lot more sense than any other name. If we do decide to make containers non-DisplayObjects, then we might as well go down this path for everything and make everything DisplayObject optional.SkinnableComponent
and Skin
. This means Mustella (or other places) will need to bring these classes in (or treat them as untyped).IVisualElementContainer
.owner
seems to serve 3 separate purposes.Scroller
also implements IVisualElementContainer
to advertise the one child it contains. We debated having a separate interface for "decorators", but our approach seems more generic and can handle HDividedBoxes
well. The "getter" APIs will work fine on Scroller
, while the mutating ones will throw RTEs.IVisualElement
and IVisualElementContainer
. Because these classes are newer definitions, they will overwrite the old versions of the base classes. Another solution is to just update the Flash Component Kit and don't support the older swcs. We'll need more direction from product management; however, regardless, these classes need to be updated.
Hi , There are missed images and scheme in article. Please add them because they are very helpful. For example after text :"For Diagrams below, use the following legend:"
there should have the image .