Home >
The Art of System Tray and Dock Icons
One of the basic aspects of an application is its tray or dock icon. A basic tray or dock icon will allow you to exit or restore the application. This is extremely important, especially for Adobe AIR applications. Since most of us Adobe AIR application creators are web programmers, or something similar, by trade it's extremely easy to forget or overlook basic things like this only because it's not the norm.
The reason a system tray or dock icon is so important to an application, is that it's another way to control said application. All of us know by now, to please the end user, you must give them complete control over what they're interacting with. Let's say you're making a sweet new application with all sorts of window transitions, changing visibility of windows and so on. Well, what happens if the application becomes unresponsive? What happens if the transition doesn't complete? Especially if you're using custom chrome, the user will not be able to close the app! Yes, they can force quit the application using operating system specific techniques, however, as the developer you need to be thinking that the end user can hardly manage to open their web browser, let alone know how to force quit an app.
Now, in terms of operating systems, windows won't display a tray icon by default. You have to implement a solution to first even get the system tray icon to display. Along the same lines, windows doesn't have a default tray icon menu when you click it. Macintosh, and I assume Linux, by default shows a menu when you click to the dock icon which includes an exit command. So, let start creating our system tray and dock icons, then add a few menu items, and some actions for when those items are selected.
Note this example is ActionScript 3.0 syntax.
First, lets set up our variables for the example
private var icon:Loader = new Loader();
private var systray:SystemTrayIcon;
private var dock:DockIcon;
private var copyrightCommand:NativeMenuItem = new NativeMenuItem("© 2009 Damon Edwards");
private var restoreCommand:NativeMenuItem = new NativeMenuItem("Restore");
private var exitCommand:NativeMenuItem = new NativeMenuItem("Exit");
So, as you can see, we need to set up a reference to instantiate an instance of the Loader class, the SystemTrayIcon class, the DockIcon class, and an instance for each menu item using the NativeMenuItem class. Now we can use these references in our application when setting up our system tray and dock icon. Next, I'll typically add a call to a trayIcon(_sysTray:Boolean, _dock:Boolean); function like so:
public function ApplicationConstructor():void{
trayIcon(NativeApplication.supportsSystemTrayIcon, NativeApplication.supportsDockIcon);
}
The trayIcon function accepts two boolean arguments, the first is the static method call to find out if the current system supports system tray icons, and the second is to find out if the current system supports dock icons. That way, we can apply the correct icons based on the operating system the application is running on. Let's look at the trayIcon function.
private function trayIcon(_sysTray:Boolean, _dock:Boolean):void {
if (_sysTray) {
icon.contentLoaderInfo.addEventListener(Event.COMPLETE, iconLoadComplete);
icon.load(new URLRequest("icons/icon_016.png"));
systray=NativeApplication.nativeApplication.icon as SystemTrayIcon;
systray.tooltip="DeskTube";
systray.menu=createIconMenu(_sysTray);
}
if (_dock) {
icon.contentLoaderInfo.addEventListener(Event.COMPLETE,iconLoadComplete);
icon.load(new URLRequest("icons/icon_128.png"));
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, undock);
dock=NativeApplication.nativeApplication.icon as DockIcon;
dock.menu=createIconMenu(_sysTray);
}
}
The first line in the function is a test case, basically saying, if _sysTray equals true, then do this. Same applies for the second case statement. Let's run through assuming we're running on a machine that allows system tray icons. We start by loading the correct icon, relative to your application install directory, and we add a listener to the icon loader that will fire when loading the icon completes. We then set the systray instance to the icon property of the native application, apply a tool tip, and set the menu. If you didn't notice, I set the SystemTrayIcon's menu to a function reference, that will return a NativeWindow, which we'll explore shortly. If we we're running on an operating system that supports dock icons, we'd follow almost the same steps. We have to load the correct icon, listen for it to complete loading, but here's where the differences start. We need to add a listener to the application to call our undock function whenever the icon is clicked, and second, there is no tooltip property on dock icons. Then, of course, we create our menu which we'll go over shortly.
Once the icon loads, we need to actually set the native application's icon's bitmap property to the newly loaded icon's bitmap data, which is handled when the iconLoadComplete function is fired. This next function is pretty straight forward.
private function iconLoadComplete(e:Event):void {
NativeApplication.nativeApplication.icon.bitmaps=[e.target.content.bitmapData];
}
Now on to the creation of the actual menu.
private function createIconMenu(_sysTray:Boolean):NativeMenu {
var iconMenu:NativeMenu = new NativeMenu();
iconMenu.addItem(copyrightCommand);
if (_sysTray) {
iconMenu.addItem(new NativeMenuItem("", true));
iconMenu.addItem(restoreCommand);
restoreCommand.addEventListener(Event.SELECT, undock);
iconMenu.addItem(exitCommand);
exitCommand.addEventListener(Event.SELECT, closeApp);
}
return iconMenu;
}
The createIconMenu function accepts one argument, the same boolean value we user earlier for the system tray. We do this because, as explained earlier, the dock icon comes default with the exit option, and clicking the dock icon will restore the application. On windows, we need to add that manually. So, first we create a new instance of the NativeMenu class. I then add the copyright notice to the top of the menu, the item will have no actions when you click it. Now, if the system tray is present, we need to add restore and exit items, so we first add a separator by setting the second argument of the NativeMenuItem class to true, therefore signifying it as a separator. We then add our restore item, and add a listener to it that will fire when the user clicks this item. We do the same for the exit item, add it to the menu, then assign a listener for when it's selected. After that, we return the window to be set as the menu for the icon.
We can finish up by creating our "undock" and "closeApp" functions.
private function undock(e:Event):void {
stage.nativeWindow.restore();
}
private function closeApp(e:Event):void {
NativeApplication.exit();
}
These two functions are almost totally self explanatory, where the undock function restores the application from a minimized, or even maximized state to its default state. The close function does just that, it exits the application.
It's really great to see all these new Adobe AIR applications popping up all over the place. Some are totally useless, but for the most part there are some extremely intuitive applications out there. We, as the new kids on the block in terms of application development, need to make sure we conform to the basic needs of an application. Thanks for having me, and check out my personal Adobe AIR application DeskTube, the desktop YouTube application.




Facebook Application Development
Wow this is amazing!!!!
Absolutely brilliant. I can't wait to start using this information.
Very nice write up Damon. I definitely think taking advantage of native OS behavior is good practice. You tend to inherit a lot of the good usability features that the OS has built in, and this will be familiar with most users of the OS.
Under Windows (and most implementations of X under *nix), keep default chrome is wise, as you get a lot of control over the application from the built in menu (maximize, minimize, restore, close, move), and you get all this bundled with full accessibility - worth keeping for sure.
You automatically get an entry in the Windows taskbar as well, which is gives similar functionality.
However, the purpose of the system tray is not so much to provide you with a third repetition of this functionality, but it is generally to provide access to persistent applications or services running on the system (usually without the need for a persistent GUI), or to provide notifications to the user if necessary. Microsoft has a good little article outlining the purpose of the system tray here: http://msdn.microsoft.com/en-us/library/aa511448.aspx.
This information is awesome for creating this type of functionality. However, clogging up the system tray with a myriad of icons for every application may be a source of annoyance for many users. So, it is definitely wise to use this sparingly ;)
Hi Andrew, thanks for reading!
I definitely agree with you that most applications that utilize the system tray, don't always have a main GUI to interact with, but run more in the system tray. In my opinion though, this doesn't apply to Adobe AIR applications. I know that's a tad contradictory to what I said earlier regarding conforming to industry standards. However, the developers that are creating these Adobe AIR applications, generally come from a design/creative background. That means to me that a lot of these new applications are made using non-standard or custom chrome. Not only that, these applications are doing fancy transitions, and all sorts of things you don't see in everyday, run-of-the-mill applications.
The problem with all these new transitions is sometimes things break, and it could happen at the most inopportune time. Example, when building my DeskTube application, I use a pixelbender filter to transition my windows out and in. Well, at some points during development these transitions would fail to finish, or not happen at all. This cause a problem since I didn't have a tray icon to shut down the application completely, and of course there was a really annoying video playing. Luckily, I know how to force quit applications on operating systems.
You make a great point, there is no doubt about that. However, in my experiences giving the user more control over what they interact with, only adds to the success of it.
Great Article.
I use some similar implementation in may Air Apps.
Our clients usually ask for custom chrome apis with system tray (dock) icons and no presence in the task bar, as if they were widgets (skype, jing...).
The chrome usually uses the company logo qhen the window is 'visible but not expanded', and then expands with some effect reacting to a click. Nothing really useful or new, just be able to drag their log on the desktop.
(1) Anyway, to achieve, in windows, an api with tray icon and with no presence in the task bar, i have to close the main window and play with a son of it (at least by the time of air 1.0, haven't looked at it again ever since).
(2) Also, i would like to restore the api after a double click on the tray icon, but i think nativeApplication.icon doesn't have the event native. So I guess i'll have to do it myself (with two single clicks and a short Timer).
Anyone confirm these two points
Hello eme,
Pertaining to question number one, yes, this is the most common way to achieve what you're after. Along the same lines as question one, yes, since the SystemTrayIcon reacts to the 'select' event, you'll have to create a timer to achieve the 'double click' method.
Thanks, this helped a lot.
On eme's point 2:
I'm trying to implement what I think must be the standard behavior. This app restores itself when you click once on the docked icon. But there must be an easier way?
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Label text='hello' creationComplete="Init()" />
<mx:Script>
<![CDATA[
/*--------------------------------------------------
Test AIR app for Mac docked icons. Why is this normal behavior
so much work to achieve?
Features:
1. On startup, you get both a main window and a docked icon.
2. When you close the main window, using the window chrome or
the docked icon, and it's visible, it becomes invisible and the
docked icon stays in place.
3. When you click on the docked icon, the main window reappears.
4. When you click "close" on the docked icon, and the main window
is invisible, the app closes.
Problem:
1. When you "close" the app by clicking on the close button in
the chrome, you cannot immediately click on the docked icon to get
it back. The app is still active, so clicking on the icon does
not activate it. Event.ACTIVATE may be incorrect.
---------------------------------------------------*/
private function Init():void {
// Don't exit when the main window closes.
NativeApplication.nativeApplication.autoExit = false;
addEventListener(Event.CLOSING, closeApp);
// There must be something more appropriate than ACTIVATE here.
addEventListener(Event.ACTIVATE, restoreApp);
if (NativeApplication.supportsDockIcon){
var icon:Loader = new Loader();
icon.contentLoaderInfo.addEventListener(Event.COMPLETE,iconLoadComplete);
icon.load(new URLRequest("http://www.photometria.com/widget/el/resources/icon_128.png"));
}
function iconLoadComplete(event:Event):void {
NativeApplication.nativeApplication.icon.bitmaps
= [event.target.content.bitmapData];
}
}
private function closeApp(e:Event):void {
if (stage.nativeWindow.visible) {
// Don't close the app, just make the main window invisible.
stage.nativeWindow.visible = false;
e.preventDefault();
} else {
// If the main window is not visible, and the user
// clicks "close" in the docked icon's menu,
// they must really want to close the app.
NativeApplication.nativeApplication.icon.bitmaps = [];
NativeApplication.nativeApplication.exit();
}
}
private function restoreApp(e:Event):void {
stage.nativeWindow.visible = true;
}
]]>
</mx:Script>
</mx:WindowedApplication>
This is pretty damn useful from a technical point-of-view, it’s not art in the design sense – no pretty pictures, guys! – but is instead a good collection of code you can use as a basis for implimenting your own System Tray / Dock Icons and dedicated servers
That´s amazing tutorial!
The Stopwatch class constructor function adds child display objects for the clock and clock controls, and initializes the timer. webdesigner
To set up the application icon, Stopwatch checks which type of icon is available using the static supportsDockIcon and supportsSystemTrayIcon properties of the NativeApplication class and then adds event listeners, menus, and a tooltip:
if(NativeApplication.supportsDockIcon){
var dockIcon:DockIcon = NativeApplication.nativeApplication.icon as DockIcon;
NativeApplication.nativeApplication.addEventListener(InvokeEvent.INVOKE, undock);
dockIcon.menu = createIconMenu();
} else if (NativeApplication.supportsSystemTrayIcon){
var sysTrayIcon:SystemTrayIcon =
NativeApplication.nativeApplication.icon as SystemTrayIcon;
sysTrayIcon.tooltip = "Stopwatch";
sysTrayIcon.addEventListener(MouseEvent.CLICK,undock);
sysTrayIcon.menu = createIconMenu();
}
Thanks.
Good write up and examples Damon.
Man!! I was on the internet forever trying to make sense of this and found your site and understood within minutes, thanks so much man, saved me a lot of time!
Nice article, thanks for sharing.
I have an issue with windows/air choosing the incorrect size icon for the taskbar and was wondering if any could help out...
I made four test icons, simple white icons with black numbers on them... 16, 32, 48, 128 just to make them easy to distinguish, then I defined them in the -app.xml file like so:
assets/appIcons/appIcon.png
assets/appIcons/appIcon_32.png
assets/appIcons/appIcon_48.png
assets/appIcons/appIcon_128.png
It displays properly in the quicklaunch bar (16x16), in the start menu (16x16), on my desktop (32x32), and even windows explorer (the correct icon is chosen when the view is changed from details, list, tile, etc), but for some reason, the 16x16 icon is not being used in the taskbar (its choosing the (32x32).
I'm not putting an icon in the system tray, I would just like the taskbar icon to be the correct 16x16 icon.
Any ideas?
Just noticed that the xml tags for the icons in my previous comment were stripped out...