Style system bug in Flash CS3 UIComponents

by Josh Tynjala

The following bug requires a bit of explanation. I'll start by describing how Flash CS3's UIComponent framework handles styles. The style system is pretty complex, so it helps to review this functionality first. Then I will describe what's wrong with the implementation and how it leads to a particularly annoying bug that affects custom component developers. Finally, I'll include a specific use-case for why this bug needs to be fixed.

I'm posting this to my blog so that other developers can find it. I spent a couple hours trying to figure out why I couldn't redefine the textFormat style in my custom component. Hopefully this will help anyone facing the same problem. For the record, I've also submitted this bug to Adobe.

Flash CS3's UIComponent framework defines styles using three levels of importance:

The order of importance for these style levels is as follows:

  1. Instance styles are the most important. If a component has an instance style defined, it will take precedence over both shared styles and global styles.

  2. Global styles are less important than instance styles, but more important than shared styles.

  3. Shared styles are the least important.

You can see how this works by looking at the getSharedStyle() method of StyleManager. It first checks to see if an instance style is defined and uses that value if it isn't null. Next, it checks for a global style. Finally, it checks for a shared style.

Actionscript:
  1. private static function getSharedStyle(instance:UIComponent,name:String):Object {
  2.     var classDef:Class = getClassDef(instance);
  3.     var inst:StyleManager = getInstance();
  4.     // first check component styles:
  5.     var style:Object = inst.classToStylesDict[classDef][name];
  6.     if (style != null) { return style; }
  7.     // then check global styles:
  8.     style = inst.globalStyles[name];
  9.            
  10.     if (style != null) { return style; }
  11.     // finally return the default component style:
  12.     return inst.classToDefaultStylesDict[classDef][name];
  13. }

Overall, this makes sense. It allows each component class to include default style values through shared styles. If a developer wants to universally change a style that is defined by many components, such as changing the font used by all controls in an application, a global style may be set. This will override the defaults set in shared styles. Finally, a single instance of a component can be made to look slightly different than everything by setting an instance style.

The Bug

Notice the very last line of the constructor defined for StyleManager. Some global styles are defined immediately when StyleManager is initialized.

Actionscript:
  1. public function StyleManager() {
  2.     styleToClassesHash = {};
  3.     classToInstancesDict = new Dictionary(true);
  4.     classToStylesDict = new Dictionary(true);
  5.     classToDefaultStylesDict = new Dictionary(true)
  6.     globalStyles = UIComponent.getStyleDefinition();
  7. }

Remember the order of style precendence?

  1. Instance

  2. Global

  3. Shared

These global styles will always take precedence over shared styles. To review, shared styles are the mechanism for setting default style values. As a custom component developer, when I want a different default value than one of these forced global styles, I'm can't do it properly.

There are actually three workarounds. The very last one is probably best because it doesn't change the existing behavior.

The fix is simple. In the StyleManager constructor, just set global styles to an empty object.

Actionscript:
  1. globalStyles = {};

There's simply no reason to have any global values pre-defined in the framework. They should all be shared styles so that developers have more flexibility. Technically, they already are shared! All implementations of getStyleDefinition() use the inheritance chain to set up their default styles values. Button adds to LabelButton's shared styles, which adds to BaseButton's shared styles, and finally to UIComponent's shared styles. In other words, most component implementations already access UIComponent.getStyleDefinition() to define defaults, so these styles will be shared from the start.

A quick, and obvious, use case

One of the global styles created by this bug is textFormat, which defines the default TextFormat object to be applied to text that appears in a control. I cannot override the default formatting of text in my custom components. If I want my component's defaults to define a text format with a larger font size (or make it bold, italic, or even a different color!), I can't do it without one of the workarounds I mentioned above.

Adobe's own Flex framework actually has this same use case and implements it as well. The Button component in Flex has bold text by default. Most other components (TextInput, for example) do not use bold text. It's an important customization option that the core Flash CS3 components don't use, which is why the developers probably didn't notice it. However, a look at the other major AS3 component architecture shows that it's a valid need.

6 Comments

TJ Downes

Probably one of the most well thought out and documented explanations I have ever read in the Flex community. Kudos and thanks for the thorough report and explanation

TJ Downes

“Probably one of the most well thought out and documented explanations…” in regards to a bug, was the intended thought.

Josh Tynjala

Thanks, TJ. It seemed like kind of an abstract bug, so I figured some extra explanation would help make it clear for those unfamiliar with the component architecture, and it shows to Adobe’s developer that I understand what’s happening under the hood. Plus, being a typical egotistical developer, I know how easy it can be to dismiss a bug report because it doesn’t point to the problem clearly enough. :)

Also, just to clarify, it’s not a Flex bug. It applies to the components that come with Flash CS3.

K Anderson

Wow, this is exactly what i was looking for! I spent hours trying to figure out why i couldn’t set the textFormat style on a List component.

For now i am using your StyleManager.setStyle() solution, but i’d really like to make the edit you suggested to the StyleManager constructor. I tracked the class down and made the change, but nothing is happening… Wondering if i need to reload the classes into flash somehow, or what… Any ideas?

Thanks!

Josh Tynjala

Copy the edited class file into the same folder as your FLA file. It should override the default pre-compiled version. Alternatively, add the top level folder containing the User Interface class files to your FLA file’s source path (in Publish Settings). This will override all classes in the “component shim”, but it may take longer to compile.

Geoffrey Hom

Hi, I randomly ran into this article, and I don’t seem to have a problem setting, say, the font size for just one type of component in Flash CS3. Presumably they fixed the bug, but my StyleManager constructor looks the same as in your article. Any idea how they fixed it?

FYI, this seems to work for me:

var textFormat:TextFormat = new TextFormat();
textFormat.size = 36;
StyleManager.setComponentStyle(new TextInput(), “textFormat”, textFormat);

Leave a Comment

Note: New comments may need to be approved before they appear.

Some HTML allowed in comments: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

To display code in comments: <pre>Code here. May be multiline. Format XML with &gt; and &lt; entities.</pre>