How to Fix the Flash CS3 Components

By: Jesse Warden, Flex & Flash Gladiator, MultiCast

…and end the drawNow madness.

I knew something was SERIOUSLY wrong when I read in the official docs (Part 3) from Adobe that if your code doesn’t work correctly, try using “drawNow”, and failing that, “validateNow”. What do you mean “try”? What… the… hell…

I was further curious when I dug in the source code. The Flex 2 & 3 components do not have this problem, why Flash? It turned out to be a bug in the Flash Player. The old way of doing invalidation in the Flex 1.5/1 & Flash 8/MX 2004/MX components was to utilize onEnterFrame. If you don’t know what invalidation is, I’ve written about it here under “Provides Invalidation”. In a nutshell, it allows a component to only redraw once per frame even if you set a property 1000 times.

Now that ActionScript 3 is supposed to be all pure, and have nothing to do with frames and what not, the new way to handle invalidation is to utilize stage.invalidate. This method triggers the Event.RENDER event for all those who are listening.

Problem? You cannot call stage.invalidate() WHILE executing code that was triggered from an Event.RENDER.

Invalidation is powerful in that you can have a Label component that is very efficient in only redrawing it’s text and style changes once per frame. In turn, that Label component is then put into a Button component who does his own invalidation. When he sets the text, or sizes the Label, you can be sure that it’s ok if his methods aren’t written very well, and he accidentally has a situation where the Label has it’s text set more than once per frame, or perhaps he sizes it twice. This process repeats itself as more and more complicated components are built using other components via Composition.

This nested invalidation, however, doesn’t work in the Flash CS3 components because of that very bug. My guess is, the crew building them didn’t know about this bug, and started implementing a convention of calling drawNow when stuff didn’t work right. When they added drawNow, it did, and thus they started to have confidence grow in using drawNow . Since this is AS3, you don’t really notice the performance impact since you’re busy building components and testing really small use cases, not larger applications. The drawNow method basically doesn’t wait a frame; it just draws right now.

Why doesn’t Flex have this problem? It uses both; Event.RENDER, and you guessed it, Event.ENTER_FRAME.

If you’re a Flash Developer, it’s an easy fix. You can replace the existing 2 functions in fl.core.UIComponent with the code below. If you’re doing AS3 Projects utilizing mxmlc, whether Flex Builder or Flash Develop, utilizing the Flash CS3 components because Keith Peter’s minimalist components aren’t thorough enough for your needs… it gets a little trickier. Basically, you do the same thing a Flash Developer would, re-export your SWC’s, and then re-link them into your libs folder of choice.

I haven’t figured out the best way to handle this in a multi-developer environment. You can’t check fl.core.UIComponent into Subversion because if Flex Builder sees a fl class, it’ll use that instead of the SWC’s, thus breaking the Flash components you’ve imported.

Anyway, here’s what you do:

1. Take the following code, and replace the “callLater” and “callLaterDispatcher” methods.

// HACK
// Found here:
// http://www.kirupa.com/forum/showthread.php?t=287632
//
// HACK
// Added Event.ENTER_FRAME listener because of
// stage.invalidate bug with Event.RENDER
// http://jessewarden.com/2008/03/how-to-fix-the-flash-cs3-components.html
protected function callLater(fn:Function):void {
callLaterMethods[fn] = true;
if (stage != null) {
stage.addEventListener(Event.ENTER_FRAME,callLaterDispatcher,false,0,true);
stage.addEventListener(Event.RENDER,callLaterDispatcher,false,0,true);
stage.invalidate();
} else {
addEventListener(Event.ADDED_TO_STAGE,callLaterDispatcher,false,0,true);
}
}

private function callLaterDispatcher(event:Event):void {
if (event.type == Event.ADDED_TO_STAGE) {
removeEventListener(Event.ADDED_TO_STAGE,callLaterDispatcher);
stage.addEventListener(Event.ENTER_FRAME,callLaterDispatcher,false,0,true);
// now we can listen for render event:
stage.addEventListener(Event.RENDER,callLaterDispatcher,false,0,true);
stage.invalidate();

return;
} else {
event.target.removeEventListener(Event.ENTER_FRAME,callLaterDispatcher);
event.target.removeEventListener(Event.RENDER,callLaterDispatcher);
if (stage == null) {
// received render, but the stage is not available, so we will listen for addedToStage again:
addEventListener(Event.ADDED_TO_STAGE,callLaterDispatcher,false,0,true);
return;
}
}
var methods:Dictionary = new Dictionary();
for (var copyMethod:Object in callLaterMethods) {
methods[copyMethod] = callLaterMethods[copyMethod];
delete(callLaterMethods[copyMethod]);
}
for (var method:Object in methods) {
method();
}
}

2. Save your file.

3. Reboot Flash.

4. In your FLA, delete the “ComponentShim” out of your Library.

5. Stop frikin’ using drawNow.

Keep in mind, there still are other issues with the CS3 components this won’t fix. Just because you implement ICellRenderer doesn’t mean you’ll work in a TileList. UILoader didn’t get the memo. The rotated vertical Slider makes skinning a nightmare. Transitions… don’t get me started.

On a less sarcastic note, there ARE valid uses of drawNow. For example, you cannot do effective measurement of assets if they aren’t rendered. Therefore, drawNow/validateNow have to be utilized to clear all dirty flags so you can get accurate measurements of width and height.