March 17, 2009

Flex internals: Setting a button label

Posted in Software at 00:50 by graham

Most ActionScript / Flash applications have a main event loop, triggered by Event.ENTER_FRAME. This is where the animation moves along to the next frame, or the sprites of the game are re-drawn in their new places.

In the Flex framework, you are expected to call invalidateDisplayList on the framework to say you need an update, and actually do the update when the framework calls your updateDisplayList method. This is the invalidation / validation pattern.

I went searching in the Flex code to understand how this invalidation / validation step ties in with Flash’s event model. I ignored properties and sizing, and edited the code down to the bare essentials.

Here is what happens when you change the label of a button:

1. Your code

var myButton:Button = new Button();
myButton.label = "New Label";

2. Button.as

public function set label(value:String):void  {
        _label = value;
        labelChanged = true;
        invalidateDisplayList();
}

This is what you are meant to do, if you build your own Flex component. You save the new value (in _label), record that it needs updating (labelChanged = true), and tell the framework that you are ‘dirty’, and hence need updating (invalidateDisplayList).

3. UIComponent.as

public function invalidateDisplayList():void {
    UIComponentGlobals.layoutManager.invalidateDisplayList(this);
}

UIComponentGlobals.layoutManager is a mx.manager.LayoutManager

4. LayoutManager.as

public function invalidateDisplayList(obj:ILayoutManagerClient ):void  {
        invalidateDisplayListFlag = true;
        callLaterObject.callLater(doPhasedInstantiation);
        invalidateDisplayListQueue.addObject(obj, obj.nestLevel);
}

The last line adds our button to the list of objects that need validating.

The line before, callLaterObject.callLater, is where the action happens, so let’s follow it. callLaterObject is an mx.core.UIComponent

5. UIComponent.as

public function callLater(method:Function, args:Array = null):void {

    methodQueue.push(new MethodQueueElement(method, args));

    // sm is an mx.core.SystemManager
    sm.addEventListener(FlexEvent.RENDER, callLaterDispatcher);
    sm.addEventListener(FlexEvent.ENTER_FRAME, callLaterDispatcher);
    listeningForRender = true;

    // Force a "render" event to happen soon
    sm.stage.invalidate();  // stage is a flash.display.Stage
}

Here we are, at the very heart of the framework. The doPhasedInstantiation method (parameter method) is added to the list of methods to call later. Then we listen for the FlexEvent.RENDER and FlexEvent.ENTER_FRAME events. This is starting to look familiar.

sm is an instance of mx.core.SystemManager.

6. SystemManager

override public function addEventListener(type:String, listener:Function,
                                              useCapture:Boolean = false,
                                              priority:int = 0,
                                              useWeakReference:Boolean = false):void
 {
   if (type == FlexEvent.RENDER || type == FlexEvent.ENTER_FRAME)   {
      if (type == FlexEvent.RENDER)
         type = Event.RENDER;
      else
         type = Event.ENTER_FRAME;

      stage.addEventListener(type, listener, useCapture, priority, useWeakReference);
   }
}

This is it, the interface between the Flex framework and the Flash player. We listen to the familiar RENDER and ENTER_FRAME events.

7. flash.display.Stage.invalidate

As in step 5 invalidate was called on the stage, Flash will fire a RENDER event when we enter the next frame, cued by the ENTER_FRAME event.

A Flex application is only two frames (preloader on frame 1, application on frame 2), but once the second frame has been reached ENTER_FRAME will keep firing at the frame rate, which by default is 24 frames a second.

So about 1/24 th of a second after we call invalidate, RENDER gets fired, which takes us back down from Flash into the Flex framework.

8. UIComponent.as

private function callLaterDispatcher(event:Event):void  {
   sm.removeEventListener(FlexEvent.RENDER, callLaterDispatcher);
   sm.removeEventListener(FlexEvent.ENTER_FRAME, callLaterDispatcher);
   for (var i:int = 0; i < n; i++)  {
        var mqe:MethodQueueElement = MethodQueueElement(queue[i]);
        mqe.method.apply(null, mqe.args);
    }
}

This calls all the callLater methods, including LayoutManager’s doPhasedInstantiation, which we added to that queue in step 5.

Note that we stop listening to the ENTER_FRAME events. As far as I can tell, there is no main loop in Flex, it’s all event driven. If nothing is happening in your application, then usually nothing is happening in the Flex framework.

9. LayoutManager.as

private function doPhasedInstantiation():void {
   if (invalidateDisplayListFlag) {
      validateDisplayList();
   }
}

private function validateDisplayList():void {
   var obj:ILayoutManagerClient = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallest());
   while (obj) {
      obj.validateDisplayList();
      obj = ILayoutManagerClient(invalidateDisplayListQueue.removeSmallest());
   }
}

Iterate through all the objects that need updating, and call their validateDisplayList method. In step 4, our button was put on invalidateDisplayListQueue.

10. UIComponent.as

public function validateDisplayList():void  {
   if (invalidateDisplayListFlag) {
      var unscaledWidth:Number = scaleX == 0 ? 0 : width / scaleX;
      var unscaledHeight:Number = scaleY == 0 ? 0 : height / scaleY;
      updateDisplayList(unscaledWidth,unscaledHeight);
      invalidateDisplayListFlag = false;
   }
}

The button class doesn’t override validateDisplayList, so it’s parent gets the call. It in turn calls updateDisplayList, which is what we have been waiting for.

11. Button.as

override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
   super.updateDisplayList(unscaledWidth, unscaledHeight);
   if (labelChanged)  {
      textField.text = label;
      labelChanged = false;
     }
}

And here we are, our updateDisplayList finally gets called. textField is a Flex specific subclass of flash.text.TextField, and is what actually gets rendered by the flash player.

12. The actual displaying

Flash completes the RENDER event, and renders the button to the screen, with it’s new label. And there you are, a new label on your button. How nice.

3 Comments »

  1. James kei said,

    March 8, 2011 at 02:21

    Thank you for article!

    This is very help for me!

  2. daminetreg said,

    February 15, 2010 at 07:30

    Really good article! You saved my day, because I did not have the time to run the whole calls through the debugger to understand how the Flex Frameword runs the doPhasedInstantation really. ^^

  3. Lorenzo said,

    October 22, 2009 at 23:04

    Hi, very good website!!

    Can I ask you some questions?

    You talk about a main loop. So, are there any differences (or analogies) between frames (understood as the units that contain actionscript code like in Creative Suites) and frames (generally understood as the time slots between two consecutive enterframe events)?

    What do you know abou FlexFramework startup? I’ve read that a Flex app is a two frames application. The first frame is the Preloader, the second one is the Application. What does it mean?

    Thanks in advance. Best regards, Lorenzo

Leave a Comment

Note: Your comment will only appear on the site once I approve it manually. This can take a day or two. Thanks for taking the time to comment.