Style system bug in Flash CS3 UIComponents

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:

  • Instance styles apply to a single, specific instance of a component. If you call setStyle() on a component, an instance style will be defined or changed.

  • Shared styles apply to all components of the same class. If you call StyleManager.setComponentStyle(), a shared style will be defined or changed. Components use shared styles to define their default style values.

  • Global styles apply to all components controlled by the style system. If you call StyleManager.setStyle(), a global style will be defined or changed.

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.

private static function getSharedStyle(instance:UIComponent,name:String):Object {
	var classDef:Class = getClassDef(instance);
	var inst:StyleManager = getInstance();
	// first check component styles:
	var style:Object = inst.classToStylesDict[classDef][name];
	if (style != null) { return style; }
	// then check global styles:
	style = inst.globalStyles[name];

	if (style != null) { return style; }
	// finally return the default component style:
	return inst.classToDefaultStylesDict[classDef][name];
}

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.

public function StyleManager() {
	styleToClassesHash = {};
	classToInstancesDict = new Dictionary(true);
	classToStylesDict = new Dictionary(true);
	classToDefaultStylesDict = new Dictionary(true)
	globalStyles = UIComponent.getStyleDefinition();
}

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.

  • Use StyleManager.setStyle() to replace the global style with your own value. This is just plain bad because it changes the defaults for every component in the application and not just your custom component.

  • Create a new style with a different name. This leaves the original style in place, though, so it’s will make the API and documentation messy unless you hide the original somehow.

  • In the custom component’s constructor, use setStyle() to define the default value as an instance style. It will be more important than the global styles. However, StyleManager.setComponentStyle() won’t work because the instance style will take precedence. That function didn’t work with a global style defined either, though, so it’s not really a problem.

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

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.

About Josh Tynjala

Josh Tynjala is a frontend developer, open source contributor, bowler hat enthusiast, and karaoke addict. You might be familiar with his project, Feathers UI, an open source user interface library for Starling Framework that is included in the Adobe Gaming SDK.

Discussion

  1. 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

  2. TJ Downes

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

  3. 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.

  4. 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!

  5. 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.

  6. 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);