Bug when creating CS3 UIComponents in Document Class constructor

Under one specific condition, a Flash CS3 component will not draw for the first time until after it has been displayed to the user. Generally, that’s not a problem, but there are times when it becomes noticeable. The following report demonstrates an obscure bug that most component developers working with the CS3 UIComponent architecture won’t encounter. However, it becomes more visible with specific types of components, such the the Layout Containers in the Yahoo! Astra components for Flash CS3.

A quick background explanation to get everyone up to speed. The CS3 UIComponents rely on the stage’s “render” event as part of the validation model. Basically, a component will wait until just before Flash Player displays something new to screen before running its main drawing code. This ensures that developers can set many properties at once without redrawing every single time. The render event provides a “last chance” place for code to run.

The Bug: No Event.RENDER for first frame

When a SWF first starts up, the document class constructor is run. Imagine that you want to create an application with a layout container, like the Astra VBoxPane, to arrange your controls. You insantiate the VBoxPane and add it to the display list. Next, you create several controls and add them to the VBoxPane. Very standard stuff, and you put all this code into the document class constructor.

When you run your SWF, sometimes you notice a very strange issue on the edge of your vision. For a very brief moment (if you blink, you’ll miss it), all the controls in the VBoxPane are stuck in the top-left corner at x=0, y=0. You don’t see it happen every time, though. Maybe it’s your monitor’s refresh rate. Maybe the browser just doesn’t allow Flash Player to draw itself right away, but it definitely happens sometimes.

The problem I discovered is this: Event.RENDER doesn’t get fired before the very first frame is drawn when you start up your SWF. The first time you can receive Event.RENDER is after the first time Event.ENTER_FRAME is fired. For reference, the enterFrame event is a lot like RENDER. It’s the very first code that gets run after Flash Player draws to screen. When the SWF starts up, the document class constructor code gets run, then Flash Player draws itself, Event.ENTER_FRAME is fired, and then Event.RENDER is finally fired the first time before frame two. In the case of our VBoxPane, all of its children are on the display list and visible to the user when the first frame is drawn because we added them to the display list, but the component never had a chance to position them!

The Workaround: A simple drawNow()

If you’re using any of the Astra layout containers, and you encounter this problem, there’s an easy fix. In your document class constructor, after you create the layout container and add its children, call drawNow(). This will force the container to draw immediately. Since we’re still in the constructor, it’s before the first frame is displayed to the user, and all the controls should be laid out properly.

I should note, most developers using the CS3 components never have to touch drawNow(). It’s most useful to custom component developers who want to put components inside other components. It ensures that the child components draw properly. While a bit of a pain, that’s a very different thing than the bug I’m reporting here.

Test case: Some simple code to see this problem.

As part of my investigation, I created some simple code to demonstrate the bug. This is code I’m submitting to Adobe in a bug report. It contains trace() statements that show the difference between the expected order of operations and the actual order.

First, a document class that instantiates a component:

package
{
	import flash.display.Sprite;
	import flash.events.Event;

	public class UIComponentFirstRedraw extends Sprite
	{
		public function UIComponentFirstRedraw()
		{
			super();

			var test:TestComponent = new TestComponent();
			this.addChild(test);

			this.addEventListener(Event.ENTER_FRAME, enterFrameHandler);

			//the trace calls demonstrate the actual order of operations
			//numbering indiciates the expected order.
			trace("1. END OF DOCUMENT CLASS CONSTRUCTOR");
		}

		private function enterFrameHandler(event:Event):void
		{
			this.removeEventListener(Event.ENTER_FRAME, enterFrameHandler);
			trace("3. ENTERING FIRST FRAME. COMPONENT IS NOW VISIBLE TO USER.");
		}

	}
}

And the simple component that is created in the document class constructor:

package
{
	import fl.core.UIComponent;

	public class TestComponent extends UIComponent
	{
		public function TestComponent()
		{
			super();
		}

		private var drawnOnce:Boolean = false;

		override protected function draw():void
		{
			super.draw();

			if(!this.drawnOnce)
			{
				this.drawnOnce = true;
				trace("2. THE COMPONENT IS DRAWING FOR THE FIRST TIME! I SURE HOPE IT ISN'T VISIBLE YET!");
			}

		}

	}
}

For the next version of Astra, I plan to work around the bug in a different way so that developers don’t need to call drawNow() manually. However, it is a hack that involves visibility changes, and I definitely want to see this bug fixed in UIComponent in the future.

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. Some Guy

    Well, it’s not really a bug. You guys are just doing it wrong.

    UIComponents are rendered AFTER they are added to their parent. By adding them to their parents before the parents themselves are added to the screen puts them all at (0,0). To properly implement this process, you might want to consider making a custom UIComponent by extending the UIComponent class. The UIComponent has the createChildren method where you are supposed to add children. Also, the updateDisplayList method is used for sizing and positioning…

    If you need any help, try searching google for something like “custom uicomponent”

  2. Josh Tynjala

    Some Guy, this post is related to the fl.* component set included in the CS3 and CS4 versions of the Flash authoring tool. It is not about using Flex components in any way. This issue can never be encountered in Flex, but it’s an unfortunate issue with component sets meant to run in vanilla AS3 applications. Thankfully, there are workarounds.