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:
-
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.
-
Global styles are less important than instance styles, but more important than shared styles.
-
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?
-
Instance
-
Global
-
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.
by Josh Tynjala | 6 comments
Open Source Flex Component: TreeMap 2.0 (Beta)
After quite a bit of work, I'm relieved to present a beta release for the next version of my TreeMap component for Flex. It took me a while, but I reworked much of the core item renderer code to optimize performance, improve maintenance tasks, and generally make it a better component for everyone to use. For full details, visit the TreeMap project page and grab the new build from the downloads section. The package is marked 2.0.0 BETA.
One of the features I'd like to highlight is the new header renderer. The most obvious change is the new zoom button. Because the headers may be selected (when the branchesSelectable property is set to true), a separate zoom button is needed to differentiate these actions. Additionally, you might notice another interesting change in the example linked below. Choose the "Slice-and-Dice" layout, and you'll see that some of the headers have a little arrow after any text that gets truncated. Roll the mouse over the header, and it will grow with animation to its optimal size, revealing the full text and the zoom button. A drop shadow makes it pop out a bit to help differentiate the header from the surrounding component. With particularly complex data providers, this feature helps usability enormously because the headers often become very small, which hides parts of the text and the icons on the zoom buttons too.
As with all beta software, a word of warning. This component is not completely tested, the documentation is obviously incomplete, and you should use this in production apps at your own risk. I did my best to get everything stable, but obviously, this is the very first release of some new code, so I know there will be bugs. If you're feeling adventurous, though, I hope it works well for you. Should you see any strange behavior, please file a bug report so that I can make the next release better! If you have any questions, send them to the new flextreemap discussion group I set up recently so that others can learn too.
by Josh Tynjala | 10 comments
Hey Yahoo!, so long and thanks for all the Flash!
Some personal news for you all. Today's my last day at Yahoo!. I've had an awesome time building cool Flash and Flex components and I'm honored to have been able to shape the best practices and directions taken with ActionScript here under the purple banner. Contributing to the Astra and YUI libraries and helping to expand and improve the Flash Developer Center has been a lot of fun. I'm excited to continue watching what new and interesting open source projects come out of the talented minds at Yahoo! long after I'm gone.
Where am I going now? Next week, I'll start working full time with Clint and Ben and all the great folks at ESRIA. Besides coding in the trenches on various projects there, I plan to continue contributing to the open source Flash and Flex community as much as possible. Keep your eyes on my blog for more details as I get settled in. I have some cool ideas for applications, components, libraries, and all sorts of fun stuff. ESRIA digs my community contributions, and I'll definitely have a lot more time to work on a wide range of interesting projects thanks to their support. I'm excited!
by Josh Tynjala | 5 comments
Gratuitous Text Effects Courtesy of Flash Player 10
Now that I've figured out how to compile SWFs for Flash Player 10 beta using the Flex SDK nightly builds, I'm happy. There may not be any proper API documentation yet, but at least I can experiment with just the compiler and a little curiosity to guide me. My first SWF is nothing special, but let me tell you, I've been waiting to do this for a long time:
This content has been removed. It was for an earlier beta for Flash Player 10, and I don't feel like updating it. It was just spinning text fading in and out. The font wasn't embedded. Generally, lame, but I enjoyed having the ability to do it.
That's right, those are device fonts, and they're rotating and changing transparency. No silly font embedding in sight. I no longer feel dirty when I want to rotate my text. Can I start targeting Flash Player 10 today?
Some other interesting stuff:
-
var v:Vector.<int> = new Vector.<int>();
-
v[0] = "STRING!"; //error
-
v[1] = 12; //okay!
Mmm... new Vector type.
by Josh Tynjala | 9 comments







