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.