Adding “Multiple sets of visual children” to custom components

Posted on August 30, 2007 by Tony Fendall.
Categories: Adobe, Components, Flex.

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:

  1. Only declare mxml components in ‘leaf’ classes (ie. Have no descendants)
  2. Declare all components in action script rather than mxml
  3. 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.

Comment on August 30th, 2007.

Hi, my first container component with some additional content used exactly this method. The major drawback is that the Flex Builder’s design view experience is just awful. (Try your example and switch to design view in the Main.mxml file – you can’t come with this to your company’s designers, can you.)

I improved the experience a little bit by adding some ActionScript to the Main.mxml application file so that the containers were basically created as separate controls “above” the custom container and added to the right place in the control tree only at runtime. To tell the truth, it was an ugly hack that even I didn’t like.

Then, a lot later, I discovered simple solution to this scenario – just use view states. Define all the “chrome” (custom controls that you want to be part of the control definition) inside some view state and you no longer have troubles with multiple-sets-of-visual-children problem. You now have not only the desired component behaviour but also a good design experience for both component designers and component users.

Regards,
Borek

Comment on August 31st, 2007.

Hey, thanks for the tip :) I’ll have to try it out.

To be honst though, I’ve always found the Design View to be a bit of a joke in FlexBuilder 2, and I’m really hoping that it gets fixed in FB3. Any time you create any real custom component (whether they are template containers or not), the design view stops working and just shows up a blank grey.

No one at my company uses the design view at all when building flex applications because it just stops working when you do anything more complicated than standard flex components.

Comment on August 31st, 2007.

Well, so far, I’ve always found a way to force the design view to work but it usually requires some clever idea or some kind of hack (”workaround”). I agree that design view should be made more custom-components friendly.

Regards,
Borek

Comment on December 20th, 2007.

Oh, and did not know about it. Thanks for the information …

Andre
Comment on February 14th, 2008.

This was such a great idea and write up! Regardless of the issues with the FB Design View (which didn’t seem too shabby to me), I implemented multiple containers and it works beautifully. I can now extend a container in MXML that also has other components (children as fixtures) in it. Thank you!

Comment on February 15th, 2008.

Thanks Andre! I’m glad you found this useful :)

SUN
Comment on February 28th, 2008.

Thanks for such useful information

SUN
Comment on February 28th, 2008.

()

jinni
Comment on February 28th, 2008.

That’s a great solution! It really helped me a lot. However, It seemed to be a tiny bug: Maybe you could add “_childrenChanged = false;” after adding children to b_content. Otherwise when commitProperties() is invoked again by some reasons, it’ll result in something wrong.

Comment on February 29th, 2008.

Yes, Jinni is correct. You have to set _childrenChanged to false after you have processed them for this to work correctly.

Jerome
Comment on April 1st, 2008.

Hi,

When I use your sample in my application, it works OK when running.

But in the design view, I can’t see the child added to the custom container.

Is there a way to see the children added to the custom container even in Design view ? Not only when running…

Thanks

Comment on April 1st, 2008.

Hi Jerome

Unfortunatly, because the child components are added to the container at run time, the container will not display the children in design view.

My experience, is that almost all (really) custom components cause design view to lose its usefulness.

-Tony

Comment on May 14th, 2008.

Hi,

I found another solution for the problem. Check it out here: http://www.richinternet.de/blog/index.cfm?entry=CD61A506-00CA-B6DF-802A549E330AFD67

Dirk.

Comment on May 14th, 2008.

@Dirk
Thanks for the tip :)

I think everyone has their own preference for how much work they want to put into making the components behave as intended.

Pingback on June 2nd, 2008.

[...] http://www.munkiihouse.com/?p=37 [...]

Comment on December 10th, 2008.

Thanks a buch for this tip.

Copy/paster and worked straight! You are time saver ! :)

Comment on April 23rd, 2009.

Thank you for a good example of a workaround.

I’ve found that this only works for one generation: one cannot sub-class CustomFormItem and put the new children in that. Even if I add the child-handling code and reset the DefaultProperty to it, it just does not work.

I also don’t see where you set _childrenChange = false after the processing in commitProperties(). It seems like you would re-add the children every time any property changed.

Cheers

Srikanth
Comment on May 28th, 2009.

Thank you for a good example.

Richard,
I tried subclassing CustomFormItem and it worked for me.
Please see if this works for you as well…

Srikanth
Comment on May 28th, 2009.

< CustomFormItem >
< children >
< !– add your mxml children here instead — >
< mx:Button label=”click me”/ >
< /children >
< /CustomFormItem >