Adding “Multiple sets of visual children” to custom components
Flex doesn’t support adding child components to containers at multiple levels. I.e. it’s not possible to create an mxml component of type HBox with some components declared in mxml inside it, and then use that component somewhere else and put another set of components inside it there also. If you try to do this then you get a runtime error saying:
“Multiple sets of visual children have been specified for this component”
The reason that this isn’t supported in flex is that there’s not really any way for the mxml compiler to know what to do with the additional components. Any option taken would result in numerous possible conflicts and special cases.
There are several possible ways to avoid this issue however:
- Only declare mxml components in ‘leaf’ classes (ie. Have no descendants)
- Declare all components in action script rather than mxml
- Explicitly handle the passing in of the mxml components within the container class and place the components in their correct position.
Option 3 is by far the most flexible/useful, and the following example explains how to implement components which can be used in this way.
The Problem:
We want to create a custom FormItem component. This requires having a Label and Container within an mxml component, and then being able to add an arbitrary set of components to the created custom component using mxml when it is used.
The Solution:
Here is a DEMO APPLICATION with ‘View Source’ enabled which will assist the explanation that follows.
Step one: create the property within your component which the mxml compiler will assign the set of children defined for this component to. In the example application I have called this property “children”. Store the value which is passed into this function into a global variable so that it can be obtained later, and call the invalidateProperties() function. Calling invalidate properties will cause flex to call the commitProperties() function on this component sometime in the future, when it next is convenient to do so.
Step two: override the commitProperties() function. Here we simply loop through the components which were passed to us as children, and add them to the correct Container within outselves.
Step three: set the property you created in step one to be the “default property” of this component. This means that components which we define as children of our custom component will automatically be assigned to this property of the component.
And there you have it, an easily extended mxml component!
Things of note:
- The type of the value which the mxml compiler passes to your “children” property changes depending on the number of children declared. If only one child is declared, then that child is passed in to the property. If more than one child is declared, then the value passed in is an Array.
- Doing the work of putting the children in their places within the commitProperties() function is convenient for two reasons. Firstly because this function will be called when flex has some spare processor time available to devote to it which is good for the performance of your application. Second because this function won’t be called until after all the children of this component are created, so we don’t have to deal with the race condition which arises when the container we are adding children to hasn’t been created yet. Lastly, the commitProperties function is followed by a call to the measure() function, and to the updateDisplayList() function. This helps ensure your component will have the correct layout and appearance after we have finished moving children around.
- The [DefaultProperty] metadata tag must go in the <mx:Metadata> section of your component. This isn’t apparent from reading the documentation.
- Using this method, it’s then possible to create several properties within your custom component, and then be able to specify in mxml multiple sets of children which can then be assigned to different containers within your custom component.
This is weird… »« How to implement the singleton pattern in AS3