Monday, March 23, 2009

Setting default styles for custom components

When creating custom components (e.g. extending Canvas) we sometimes want to define new style properties on our components. When I first started with Flex I would always call the setStyle() function from the constructor of my custom component. Then I noticed that if I tried to set my style property from inside MXML it didn't work.

So far I've come across two ways of solving this problem.
  1. Default Style Sheet - defaults.css (blog post)
  2. Class Construct
This blog post talks about the class construct idea:
Flex 3:
private static var classConstructed:Boolean = classConstruct();
private static function classConstruct():Boolean {
    if (!StyleManager.getStyleDeclaration("StyledRectangle")) {
        // no CSS definition for StyledRectangle, so create and set default values
        var myRectStyles:CSSStyleDeclaration = new CSSStyleDeclaration();
        myRectStyles.defaultFactory = function():void {
            this.fillColors = [0xFF00000x0000FF];
            this.alphas = [0.50.5];
        }
        StyleManager.setStyleDeclaration("StyledRectangle", myRectStyles, true);
    }
    return true;
}
Flex 4:
private static var classConstructed:Boolean = classConstruct();
private static function classConstruct():Boolean {
    if (!FlexGlobals.topLevelApplication.styleManager.
            getStyleDeclaration("myComponents.StyledRectangle")) {
        // No CSS definition for StyledRectangle,  so create and set default values
        var myRectStyles:CSSStyleDeclaration = new CSSStyleDeclaration();
        myRectStyles.defaultFactory = function():void {
            this.fillColors = [0xFF00000x0000FF];
            this.alphas = [0.50.5];
        }
        FlexGlobals.topLevelApplication.styleManager.
            setStyleDeclaration("myComponents.StyledRectangle", myRectStyles, true);
    }
    return true;
}
** Very important - in Flex4 you set the fully qualified class name (including package) in the style manager, this is different from Flex 3. It won't work if you only set it to the class name "StyledRectangle".

For a while that satisfied my needs, but then I recently came across a problem with the above code (which by the way was copied from the Adobe LiveDocs). If you scroll to the bottom you'll actually notice that someone else posted this same problem that I'm about to mention.

If you specify some styles using a StyleSheet, then the above code no longer works because no the StyleManager.getStyleDeclaration("StyledRectangle") doesn't return null, and so the default styles aren't set unless you specifically set them in your stylesheet.

To illustrate this problem I've gone overboard and created 5 examples to show you the evolution of my learning (Source code is available for all examples, just right click View Source. You can also click on the mxml link for each to jump directly to the mxml html view).



Example #1 (CustomComponents1.mxml)
I created a simple extension of the Canvas class which is called GradientCanvas1 that draws the background as a gradient using the colors and alpha values defined in the two styles
Example #1B (CustomComponents1B.mxml)
The problem with the GradientCanvas1 class is that it ignores the style values that I specify in the MXML, e.g. fillColors="[0xff0000,0x0000ff]".
Example #2 (CustomComponents2.mxml)
To solve the problem mentioned in Example #1B I've created another class called (creatively) GradientCanvas2 that uses the class construct idea mentioned at the top of this blog entry. This static class construct sets up the default style values and now setting style properties in MXML works.
Example #2B (CustomComponents2B.mxml)
The problem with the GradientCanvas2 class is that if you use a stylesheet to define some (but not all) of the styles on the GradientCanvas2 class then the class construct no longer works because the StyleManager.getStyleDeclaration("GradientCanvas2") doesn't return null anymore, and so our default styles aren't used.
Example #3 (CustomComponents3.mxml)
And finally, the solution that I'm using (might still be problems with it). In the class GradientCanvas3 the class construct now creates a new CSSStyleDeclaration for the class if it is null, and then defines the default styles on the defaultFactory, and that is it (the livedocs say something about the defaultFactory will only be defined if styles are set in MXML components, but I haven't been able to find a case where the defaultFactory wasn't null).

If anyone has different opinions about this stuff please let me know.

1 comment:

Andriy Panas said...

Have a look at this discussion that I was having with Gordon Smith from Adobe Flex SDK team

http://www.mail-archive.com/flexcomponents@yahoogroups.com/msg03205.html

He said that starting from Flex SDK 3 release, we can use "default.css" file along with custom components packaged as SWC library

It sounds pretty convenient to me unless you edit the code of your custom component too often like me :)