by Joe Ward

Joe Ward

Created

9 June 2010

Requirements
Prerequisite knowledge

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

Sample files

User level

Intermediate
 
When an application needs to display informational messages without disturbing the user's workflow, toast-style windows can be the right solution. The WordUp example application, shown in Figure 1, creates toast windows with the following attributes:
 
  • Chromeless
  • Displayed above other windows
  • Do not steal the keyboard or mouse focus
  • Expire so the user doesn't have to close them
  • Persist when the user is away from the computer
The WordUp example illustrates how to use the following Adobe AIR APIs:
 
  • NativeWindow to create and display the toast windows
  • NativeApplication to detect user presence and to use the dock or system tray icons
  • Screen to place multiple toast windows on the desktop without overlapping
WordUp application
Figure 1. This sample application displays a toast window containing a random message.
 
Note: This is a sample application provided, as is, for instructional purposes.
 
This sample application includes the following files:
 
  • WordUp.as: The main application file; includes the code discussed in this article
  • WordUpIcon.as: Extends the Icon class to embed the icon assets for this application
  • WordUp-app.xml: The AIR application descriptor file
  • DisplayManager.as: Manages the creation, placement, and expiration of message windows.
  • MessageWindow.as: Extends the NativeWindow class to create a toast-style window displaying a text message.
  • MessageCenter.as: Dispatches message events
  • MessageEvent.as: Extends the Event class to add a message property
  • RandomMessageGenerator.as: Generates a random text string
  • Sample AIR icon files

 
The WordUp example application

The WordUp example application runs in the background with no main window. When a timer with a randomized interval fires, the application displays a toast window containing a random message. After about 5 seconds, the message expires and the toast window closes automatically.
 
To detect when the user is present, the application listens for userIdle and userPresent events from the NativeApplication object. When the userIdle event is dispatched, message expiration is suspended until the next userPresent event. This keeps the messages on the screen until the user has a chance to read them. A userIdle event is dispatched when no keyboard or mouse input has been detected within an interval determined by the NativeApplication.idleThreshold property. The default interval is a reasonable 5 minutes, but WordUp resets the value to a few seconds so the change in application behavior can be more easily observed.
 
To test the application, launch the WordUp installer file (WordUp.air) file. On Windows, a system tray icon is displayed in the notification area of the taskbar. On Mac OS X, the normal triangle under the dock icon shows that the application is running. After about 5 seconds, the first message will pop up from the bottom of the screen. It will then disappear after a few moments. If you leave your computer idle for several seconds, the messages stay on screen until you make a mouse or keyboard input, and then remain for a few seconds longer to give you time to read the messages.
 
To exit the application, on Windows, right-click the system tray icon and select Exit WordUp from the menu. On Mac OS X, click and hold the dock icon to open the menu and select Quit WordUp.
 

 
Understanding the code

WordUp is an ActionScript project and so uses the NativeWindow class directly. In a Flex project, you typically use the mx:Window class to create windows. In most other respects, the techniques demonstrated here are equally applicable to ActionScript and Flex projects.
 
Note: For more information about non-AIR specific functions and APIs used in this example, see the ActionScript 3 Reference for the Adobe Flash Platform.
 
 
Initializing a windowless application
The first step to creating a windowless application is to make sure that the initial window that is created automatically by AIR never becomes visible. The visibility of this window is controlled in two places. The first place is within the <initialWindow> element of the application descriptor file. If <visible>true</visible> is placed within this element, then the window is visible on application startup. The second place window visibility is controlled is the visible property of the window itself. The visible property can be set directly. It is also set to true when the window activate() method is called.
 
Note: If you are using the mx:WindowedApplication component from the Flex Framework to define your initial window, you must also set the component's visible property to false.
 
The main class added to the initial window by AIR is never deleted and garbage collected. You can close the initial window if you do not need to display it for your application. The objects referenced by the variables of the main class are not deleted unless you clear the references. In WordUp, the MessageCenter and DisplayManager classes are referenced by variables in the main WordUp class, so these objects are not subject to garbage collection, even though the initial window is closed.
 
It is often a good idea to give the user some indication that an application is running, even when it has no visible windows. On Mac OS X, the application dock icon serves this purpose. On Windows, the system tray icon can be used. The dock icon automatically appears in the dock when the application is running and provides a default menu for exiting the application so WordUp doesn't need to change the dock icon properties. The system tray icon is only shown if you assign an image.
 
WordUp detects whether it is running on an operating system that supports system tray icons using the static NativeApplication.supportsSystemTrayIcon property. If so, it adds a tooltip and a menu containing an exit command:
 
NativeApplication.nativeApplication.icon.bitmaps = icon.bitmaps; if(NativeApplication.supportsSystemTrayIcon){ var sysTray:SystemTrayIcon = NativeApplication.nativeApplication.icon as SystemTrayIcon; sysTray.tooltip = "WordUp"; sysTray.menu = new NativeMenu(); var exitCommand:NativeMenuItem = sysTray.menu.addItem(new NativeMenuItem("Exit WordUp")); exitCommand.addEventListener(Event.SELECT, function(event:Event):void{ NativeApplication.nativeApplication.exit(); }); }
An exit command is not necessary on Mac OS X, because the dock icon automatically includes a command to quit the application.
 
To initialize the application logic, WordUp creates a DisplayManager object, which manages the pop-up toast windows, and a new MessageCenter object, which periodically generates an event containing a random message:
 
displayManager = new DisplayManager(); messageCenter = new MessageCenter(); messageCenter.addEventListener(MessageCenter.NEW_MESSAGE, onMessage);
The onMessage() event handler function passes the message text to the display manager, which creates a new message window.
 
Since the initial window created by AIR is not needed, it is closed. Setting the autoExit property of the NativeApplication object to false prevents the application from terminating when the last window is closed.
 
NativeApplication.nativeApplication.autoExit = false; stage.nativeWindow.close();
 
Creating a toast-style window
The MessageWindow class extends NativeWindow. The constructor sets the appropriate NativeWindowInitOptions for the window. The important options are:
 
options.type = NativeWindowType.LIGHTWEIGHT; options.systemChrome = NativeWindowSystemChrome.NONE; options.transparent = true;
These options define a window with no system chrome and which don't appear on the Windows taskbar or Mac OS X windows menu. In addition, the window alwaysInFront property is set to true so that the message appears above other windows.
 
To regulate the life span of a message window, the window listens to custom lifeTick events from the DisplayManager object:
 
public function lifeTick(event:Event):void{ timeToLive--; if(timeToLive < 1){ close(); } } public override function close():void{ manager.removeEventListener(DisplayManager.LIFE_TICK,lifeTick); super.close(); }
The MessageWindow class overrides the close() method so that the event listener is removed when the window closes.
 
 
Placing the message window
WordUp uses the Screen API to find a spot to display the new message. The findSpotForMessage() function searches for an open area starting with the lower-right-hand corner of the first screen in the Screen.screens array. When it finds an open spot, it animates a window movement from the bottom of the screen to its appointed destination.
 
To check whether a message window is already displayed in a given spot, WordUp loops through the NativeApplication.nativeApplication.openedWindows array. It tests whether the rectangle defined by the area being considered intersects the rectangle defined by the bounds of an existing message window:
 
private function isOccupied(testRect:Rectangle):Boolean{ var occupied:Boolean = false; for each (var window:NativeWindow in NativeApplication.nativeApplication.openedWindows){ occupied = occupied || window.bounds.intersects(testRect); } return occupied; }
 
Detecting user presence
WordUp uses the NativeApplication userIdle and userPresent events to determine whether the user is actively using the computer.
 
NativeApplication.nativeApplication.idleThreshold = idleTime; NativeApplication.nativeApplication.addEventListener(Event.USER_IDLE,onIdle); NativeApplication.nativeApplication.addEventListener(Event.USER_PRESENT,onPresence)
In response to these events, WordUp suspends or restarts the DisplayManager timer that dispatches lifeTick events to the message windows:
 
//When the computer is idle, don't remove the messages private function onIdle(event:Event):void{ displayManager.pauseExpiration(); trace("Idling."); } //On return, let windows expire again private function onPresence(event:Event):void{ displayManager.resumeExpiration(); trace("Resuming."); }