9 June 2010

How to add Flex MX controls to a NativeWindow for Adobe Air

The NativeWindow class in Flex is a more flexible option for creating pop-up windows in Air applications than the standard Window, but this flexibility comes at a price.

You have more options for styling a NativeWindow, as you can remove the system chrome to create a border-less window, create transparent windows for non-standard shapes and see-through effects and you can also keep the window out of the Windows taskbar. All great for producing alert windows and pop-ups, but adding standard MX controls to a NativeWindow isn’t that straightforward.

The problem: Adding controls to a NativeWindow

There are two problems here, both related to the way in which a NativeWindow class initialises.

The first problem is getting controls to actually render on the NativeWindow in the first place. Adding a control in by calling the addChild method of the NativeWindow’s stage won’t do the job – they just don’t appear when you activate the window.

There is a hack for this – you can add the controls to a container on you main application, remove them immediately and then add them to the stage of your NativeWindow. This forces the run-time to actually render the control before it is placed on the NativeWindow. The code below shows how this works:

myCanvas.addElement(content);
myCanvas.removeElement(content);
myNativeWindow.stage.addChild(content);

This does make controls render, but it introduces a new problem, as attempting to click on controls such as buttons, text areas and scroll bars will throw an “object is null” error. This is happenning because the base classes for these controls expect a system manager to have been created by the host window. The Window and WindowedApplication classes both set up a system manager when they are created – the NativeWindow does not.

The solution: Extending the NativeWindow class

The solution is to extend the NativeWindow class so it creates a WindowedSystemManager object that can be used by all the controls.

Care has to be taken here over the execution order – this is Flex after all – so you need to ensre that the tasks happen as follows:

  • An instance of our extended NativeWindow is created
  • An instance of the content to add to the NativeWindow is created – this can be any Flex component that contains a set of controls.
  • The content is added to the extended NativeWindow via a custom function
  • When the extended NativeWindow‘s Activate event is fired, a WindowedSystemManager object is created and the content is passed into it.
  • The WindowedSystemManager object is added to the NativeWindow‘s stage.

The code below shows how the extended NativeWindow looks:

package components
{
    import flash.display.NativeWindow;
    import flash.display.NativeWindowInitOptions;
    import flash.events.Event;
    import mx.events.*;
    import mx.managers.WindowedSystemManager;
    import mx.core.UIComponent;

    [Event(name="creationComplete", type="mx.events.FlexEvent")] 

    public class ExtendedNativeWindow extends NativeWindow
    {
        private var _systemManager:WindowedSystemManager;
        private var _content:UIComponent;

        public function ExtendedNativeWindow(initOptions:NativeWindowInitOptions = null)
        {
            //* Call the base class constructor
            super(initOptions);
            //* Add in a listener for the Activate event - this is where we add in the content
            addEventListener(Event.ACTIVATE, windowActivateHandler);
        }

        //* Custom function to allow the content to be passed into the window
        public function addChildControls(control:UIComponent):void
        {
            _content = control;
        }

        //* This handler actually adds the content to the NativeWindow
        private function windowActivateHandler(event:Event):void
        {
            //* Process the event
            event.preventDefault();
            event.stopImmediatePropagation();
            removeEventListener(Event.ACTIVATE, windowActivateHandler);

            //* Create the children and add an event listener for re-sizing the window
            if(stage)
            {
                //* create a WindowedSystemManager to hold the content
                if(!_systemManager)
                {
                    //*    Create a system manager
                    _systemManager = new WindowedSystemManager(_content);
                }

                //* Add the content to the stage
                stage.addChild(_systemManager);

                //* Dispatch a creation complete event
                dispatchEvent(new FlexEvent(FlexEvent.CREATION_COMPLETE));

                //* Add in a resize event listener
                stage.addEventListener(Event.RESIZE, windowResizeHandler);
            }
        }

        //* Resizes the content in response to a change in size
        private function windowResizeHandler(event:Event):void
        {
            _content.width = stage.stageWidth;
            _content.height = stage.stageHeight;
        }
    }
}

The code below shows how to invoke the entended NativeWindow from the main application window:

//* Set up the NativeWindow options
var options:NativeWindowInitOptions = new NativeWindowInitOptions();

//* Create the NativeWindow
popupWindow = new ExtendedNativeWindow(options);

//* Set the height, width and position
popupWindow.width = 400;
popupWindow.height = 300;
popupWindow.x = ( Screen.mainScreen.bounds.width - 400) / 2;
popupWindow.y = ( Screen.mainScreen.bounds.height - 300) / 2;
popupWindow.stage.scaleMode = StageScaleMode.NO_SCALE;
popupWindow.stage.align = StageAlign.TOP_LEFT;

//* Create an instance of the content for the NativeWindow
var content:NativeWindowContent = new NativeWindowContent();

//* Pass the content into the native window
popupWindow.addChildControls(content);

//* Activate the window
popupWindow.activate();

There is a code sample for FlexBuilder 4 which shows how it all works. You can download it from here (ZIP archive, 5KB).

The code sample also includes some extra code for closing and tidying up the extended NativeWindow by adding listeners for close events.

Filed under UI Development.