by Joe Ward

Joe Ward


10 June 2010

Prerequisite knowledge

General experience of building applications
with Flex Builder is suggested. For more
details on getting started with this Quick
Start, refer to Building the Quick Start
sample applications with Flex
Required products

Sample files

User level

When you turn off system chrome for a window, you gain tremendous creative possibilities, but you also lose the automatic management of window size, position, and display state that the operating system provides for standard windows. The Custom Chrome example application, shown in Figure 1, illustrates how to leverage some of the creative advantages of providing your own chrome and how to replace the window management services that are no longer provided by the operating system.
Custom chrome window
Figure 1. The Custom Chrome example application demonstrates alternatives to the default system chrome.
Note: This is a sample application provided, as is, for instructional purposes.
This sample application includes the following files:
  • The main application file in ActionScript.
  • chrome/ the properties and functions used to lay out the chrome elements based on the current window size.
  • chrome/ the Chrome class to draw the window background.
  • chrome/ the Chrome class to define the main drawing area in the window.
  • buttons/ the Chrome class to implement a close button.
  • buttons/ the Chrome class to implement a maximize button.
  • buttons/ the Chrome class to implement a minimize button.
  • buttons/ the Chrome class to implement a restore button.
  • buttons/gfx/*.png:The images used for the buttons
  • handles/ the Chrome class to implement the top drag handle.
  • handles/ the Chrome class to implement the bottom drag handle.
  • handles/ the Chrome class to implement resize gripper.
  • handles/gfx/*.png:The images used for the handles.
  • application/ a simple animation that changes based on the dimensions of the window.
  • CustomChrome-app.xml:The AIR application descriptor file.
  • Sample AIR icon files
The application descriptor file, CustomChrome-app.xml, uses the AIR 1.0 namespace. If you are compiling the application using Flex Builder 3.0.2 or Flex SDK 3.2 or later, edit the file to use the AIR 1.5 namespace:
Otherwise, you will not be able to test or package the application.

Types of chrome

The Custom Chrome example uses four general types of window chrome:
  • buttons: SimpleButton objects and mouse-click events to close, maximize, minimize, and restore the window.
  • move handles: Bitmap-based Sprites and mouse-down events to allow moving the window.
  • resize grippers: Bitmap-based Sprite and the mouse-down event to allow resizing the window. The Custom Chrome example only implements resizing from the bottom-right corner, but AIR supports resizing from each edge and corner of a window.
  • background: Draws an irregular background using a Sprite object and vector drawing commands.

Installing and testing the application

The Custom Chrome application is intentionally simple. The intent is to show you the basics of how to implement custom window chrome.
To test the application, download and run the application installer (CustomChrome.air). You can maximize, minimize, and restore the window; drag it around the desktop; and resize it. Right-click in the window to open a source code browser in a new window.

Understanding the code

To code a window in AIR that does not use system chrome, you can either use Flex chrome with the mx:Window and mx:WindowedApplication components, or code your own chrome. This example demonstrates how to add your own chrome using the Flash APIs for drawing bitmap and vector graphics. This article does not describe all of the ActionScript classes used in the application. For information on these, see the Flex 3 Language Reference.
Initializing the Window
The CustomChrome class extends the Sprite class so that it can be used as the root class in the application SWF file. AIR automatically creates the window, instantiates an instance of the CustomChrome class, and adds the CustomChrome object to the window stage.
The CustomChrome constructor adds an event listener to detect when it has been added to the stage. The handler for the addedToStage event, gets the window instance using the nativeWindow property of the stage, and initializes the window properties.
Note: You may notice that you can access the stage directly from the class constructor without waiting for the addedToStage event. However, this can be a bad habit to get into since it is only true for the main Sprite class in the initial window of an AIR application.
Initialization tasks include:
  • Adding event listeners for the basic window events:
win.addEventListener(NativeWindowBoundsEvent.RESIZE,onBoundsChange); win.addEventListener(NativeWindowBoundsEvent.MOVE,onBoundsChange); win.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGE, onDisplayStateChange);
Custom Chrome uses these events to reposition and redraw the window chrome elements when the window is resized and also to control which elements to display for a particular window display state.
  • Setting the stage properties:
stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE;
Setting the stage alignment to topLeft means that the x and y coordinates of all sprites added to the window are initialized to (0,0). Setting the stage scaling mode to noScale prevents the stage from scaling as the window is resized. Since the stage doesn't scale, the contents of the window are clipped when the window is resized smaller unless the contents are repositioned and redrawn.
  • Adding the chrome:
addChildAt(background,0); addChildAt(topChrome,1); addChildAt(closeButton,2); addChildAt(minimizeButton,2); addChildAt(maximizeButton,2); addChildAt(restoreButton,2); bottomGroup.addChild(bottomChrome); bottomGroup.addChild(gripperChrome); addChildAt(bottomGroup,1);
The addChildAt() method is used to explicitly set the depth ordering of the sprites used for the chrome elements. (If addChild() is used, then the depth is set implicitly according to the order in which the sprites are added to their container.) Because Custom Chrome hides both the bottomChrome and gripperChrome sprites when the window is maximized, these sprites are grouped in the same container, bottomGroup, so that their visibility can be controlled with a single property: bottomGroup.visible.
Defining the chrome elements
Custom Chrome defines a Chrome class to serve as the base class for all the chrome elements.
The constructor for the Chrome class takes a rectangle to define the chrome placement and size, a string defining how to anchor the control to the window border, and an optional function reference used to attach the chrome element to its related control function. For example, the Close button is defined with the following declaration:
private var closeButton:CloseButton = new CloseButton(new Rectangle(65,8,0,0), 'TR', onCloseCommand)
The control is anchored to the window border according to the anchorType parameter. A value of "TR" specifies that the control is anchored to the top-right corner.
Button chrome
To implement the buttons for controlling the window state, Custom Chrome extends the Chrome class and adds a SimpleButton object as a child of the chrome element. The images for the button states are loaded by the button chrome class. Each button chrome class also adds the event listener and passes the offset and the anchorType parameters to the Chrome super class.
Move handle and resize gripper chrome
To implement the move handles and resize grippers, Custom Chrome extends the Chrome base class and adds the chrome image as a child. PNG files are used for the images so that transparent areas can be defined. The transparent areas of the image don't capture mouse events.
To draw the background, Custom Chrome extends the Chrome base class and adds a draw() method to perform the vector drawing. To call the draw() method, the Background class overrides the base class layout() method.
Allowing cancelation of window operations
When a window uses system chrome, user interaction with the window can be canceled by listening for and canceling the default behavior of the relevant event. For example, when a user clicks the system chrome close button, the closing event is dispatched. If any registered listener calls the preventDefault() method of the event, then the window will not close.
When a window does not use system chrome, notification events for window changes like resizing or closing are not automatically dispatched. Hence, if you call the methods for closing a window, changing the window display state, or set any of the window bounds properties, the change cannot be canceled. To notify components in your application before a window change is actually made, your application logic can dispatch the appropriate notification event using the dispatchEvent() method of the window. For example, the following logic implements a cancelable event handler for a window close button:
public function onCloseCommand(event:MouseEvent):void{ var closing:Event = new Event(Event.CLOSING,true,true); dispatchEvent(closing); if(!closing.isDefaultPrevented()){ win.close(); } }
Note: Although the dispatchEvent() method returns false if the event preventDefault() method is called by a listener, it can also return false for other reasons. It is better to explicitly use the isDefaultPrevented() method to test whether the window change should be canceled.
Resizing and moving a window
Calling the startResize() or startMove() methods of a window starts a system-mediated window change. The user's mouse or keyboard actions are used to change the window bounds. In this case, each incremental change is preceded by an automatically generated moving or resizing event. If any listeners cancel these events, the move or resize sequence is terminated and no further changes are made to the window.
Custom Chrome uses the following functions to start a resize and move:
public function onMoveCommand(event:MouseEvent):void{ win.startMove(); } public function onResizeCommand(event:MouseEvent):void{ win.startResize(NativeWindowResize.BOTTOM_RIGHT); }
When a window is moved, its contents move with it (as you would expect), but when a window is resized, the situation is more complicated. The default behavior of a window depends on the stage scale mode. If the scale mode is anything except, noScale, then the stage and its contents are scaled when the window is resized. This means that any buttons or other chrome you add to your window are also scaled, which may not be a desirable effect. On the other hand, when you use the noScale mode, your chrome won't be scaled, but then it won't be moved along with the resized window borders either. If you resize a window smaller, your chrome could be clipped. If you resize the window larger from the top edge, your title bar could be left in the middle.
The CustomChrome example solves this problem by drawing its chrome relative to the window corners, and redrawing whenever the window dispatches a resize event. Each chrome element listens for the resize event through an event listener added by the base Chrome class:
stage.nativeWindow.addEventListener(NativeWindowBoundsEvent.RESIZE, onWindowResize);
When the event is received, the chrome element sets its position and size based on the afterBounds property of the event object:
private function onWindowResize(boundsEvent:NativeWindowBoundsEvent):void{ layout(boundsEvent.afterBounds); } public function layout(bounds:Rectangle):void{ setPosition(bounds); if(resizable){ setSize(bounds); } }
Note: If you are using Flex components, the automatic layout features of the framework already solve this problem.
Controlling the window display state
The window display state can be controlled using the maximize(), minimize(), and restore() methods of the NativeWindow object. To allow other components to prepare for the change, Custom Chrome dispatches a displayStateChanging event before changing the display state:
public function onMaximizeCommand(event:MouseEvent):void{ var displayStateChanging:NativeWindowDisplayStateEvent = new NativeWindowDisplayStateEvent( NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, true, true); dispatchEvent(displayStateChanging); if(!displayStateChanging.isDefaultPrevented()){ win.maximize(); } }
The command to change the display state and the process of updating the size and position of the chrome elements is decoupled. Custom Chrome listens for the displayStateChange event to detect a change in display state and the resize event to detect a change in window size.
Closing the window
CustomChrome closes the window using the close() method of the NativeWindow object. To allow other components to prepare for the change, Custom Chrome dispatches a closing event before closing the window:
public function onCloseCommand(event:MouseEvent):void{ var closing:Event = new Event(Event.CLOSING,true,true); dispatchEvent(closing); if(!closing.isDefaultPrevented()){ win.close(); } }
Creating Custom Chrome as an MXML application
Although Custom Chrome is programmed entirely in ActionScript, you can also achieve the same ends using the Flex framework. To do this, define a mx:WindowedApplication component for the main window with the following properties:
<mx:WindowedApplication xmlns:mx="" layout="absolute" showFlexChrome="false">
You could then define MXML components for the chrome elements. The Flex Framework makes it much easier to lay out components, so many of the issues involving tracking the window resizing events and repositioning or redrawing the chrome elements can be handled automatically. For example, instead of storing an anchor point value for a button, you could use the Flex-defined layout properties right and top, to place it relative to the window border:
<mx:Button label="Button" right="10" top="10"/>
Note: You cannot add a Sprite object as a child of a Flex component. You must either change the class of the Sprite to UIComponent (which itself extends Sprite), or add the Sprite as a child of a UIComponent object and add the UIComponent instance as a child of the Flex component.