Graham King

Solvitas perambulum

Flex internals: Setting a button label

software
Summary
When you change the label of a button in a Flex application, the code sets the new label, marks the display list as needing an update, and calls the `invalidateDisplayList` method. This triggers the LayoutManager to add the button to a queue of objects needing validation and schedules a function to run later using `callLater`. The SystemManager listens for Flash's `RENDER` and `ENTER_FRAME` events to initiate this function call. When the RENDER event fires, the `callLaterDispatcher` method runs, executing the `doPhasedInstantiation` method from the LayoutManager, which in turn validates the display list of the button. Finally, `updateDisplayList` is called on the button, updating its display with the new label, and Flash renders the updated button on the screen.

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.