Flex 3 Air – Minimize to system tray (WindowedApplication)

I’ve been waiting for a release of Flex (AIR) where system tray (systray) options where available and finally with the latest release of Adobe AIR it’s there.

After searching some while on the internet I’ve found my way of minimizing my AIR application to the system tray, including a little menu.
Important to understand is that I wanted a minimize solution for a WindowedApplication. The WindowApplication offers it’s own minimize, maximize and close buttons, so these actions have to be caught if we want to attach our own actions to them.

Flex offers you docking, so that your application can be minimized, this is available for Windows and Mac platforms. The example presented here is for Windows, but close to what it should be for Macs.

I’ve created an Systray test application which contains the basic code, necessary for docking and undocking your application from the systray. The only part you will not need is the dummy label…
To test this you can:
1. create a new Flex project (application type AIR) called Systray.
2. edit Systray-app.mxml and set SystemChrome to none (so that you get the cool app skin ;) ).
2. copy the contents of the presented code into the Systray.mxml.

After testing it (and also without :) ), you should be able to place the code into your own project and use it.

Here’s the code:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="initApplication()">
  3. <mx:Script>
  4. <![CDATA[
  5. /**
  6. * This example describes how to dock an AIR application to the system tray
  7. * and then undock it again.
  8. * The minimize and close actions of the WindowedApplication are caught, so
  9. * that we can introduce our own actions.
  10. *
  11. * A simple systray menu is presented, to show the usage of that.
  12. *
  13. * @Author: S.Radovanovic
  14. * @Url: http://www.saskovic.com/blog/?p=5
  15. * @Date: 24-11-2007
  16. * @Author: Jeffry Houser
  17. * @Date: 09-01-2007
  18. * Updated for AIR Beta 3
  19. */
  20.  
  21. import mx.controls.Alert;
  22. import mx.events.CloseEvent;
  23.  
  24. private var dockImage:BitmapData;
  25.  
  26. /**
  27. * Initialize the application to the default values.
  28. * This method is called upon creationComplete from the Windowed Application
  29. *
  30. * @Author: S.Radovanovic
  31. */
  32. public function initApplication():void {
  33.  
  34. //Use the loader object to load an image, which will be used for the systray //After the image has been loaded into the object, we can prepare the application //for docking to the system tray
  35. var loader:Loader = new Loader();
  36. loader.contentLoaderInfo.addEventListener(Event.COMPLETE, prepareForSystray);
  37. loader.load(new URLRequest("http://www.saskovic.com/images/systray_icon_16.png"));
  38.  
  39. //Catch the closing event so that the user can decide if it wants to dock or really //close the application
  40. this.addEventListener(Event.CLOSING, closingApplication);
  41. }
  42.  
  43. /**
  44. * Check if the user wants to close the application or dock it
  45. *
  46. * @Author: S.Radovanovic
  47. */
  48. private function closingApplication(evt:Event):void {
  49. //Don't close, so prevent the event from happening
  50. evt.preventDefault();
  51.  
  52. //Check what the user really want's to do //Alert.buttonWidth = 110;
  53. Alert.yesLabel = "Close";
  54. Alert.noLabel = "Minimize";
  55. Alert.show("Close or minimize?", "Close?", 3, this, alertCloseHandler);
  56. }
  57.  
  58. // Event handler function for displaying the selected Alert button.
  59. private function alertCloseHandler(event:CloseEvent):void {
  60. if (event.detail==Alert.YES) {
  61. closeApp(event);
  62. } else {
  63. dock();
  64. }
  65. }
  66.  
  67.  
  68. /**
  69. * Check to see if the application may be docked and set basic properties
  70. *
  71. * @Author: S.Radovanovic
  72. */
  73.  
  74. public function prepareForSystray(event:Event):void {
  75.  
  76. //Retrieve the image being used as the systray icon
  77. dockImage = event.target.content.bitmapData;
  78.  
  79. //For windows systems we can set the systray props //(there's also an implementation for mac's, it's similar and you can find it on the net... ;) )
  80. if (NativeApplication.supportsSystemTrayIcon){
  81. setSystemTrayProperties();
  82.  
  83. //Set some systray menu options, so that the user can right-click and access functionality //without needing to open the application
  84. SystemTrayIcon(NativeApplication.nativeApplication .icon).menu = createSystrayRootMenu();
  85. }
  86. }
  87.  
  88. /**
  89. * Create a menu that can be accessed from the systray
  90. *
  91. * @Author: S.Radovanovic
  92. */
  93. private function createSystrayRootMenu():NativeMenu{
  94. //Add the menuitems with the corresponding actions
  95. var menu:NativeMenu = new NativeMenu();
  96. var openNativeMenuItem:NativeMenuItem = new NativeMenuItem("Open");
  97. var exitNativeMenuItem:NativeMenuItem = new NativeMenuItem("Exit");
  98.  
  99. //What should happen when the user clicks on something...
  100.  
  101. openNativeMenuItem.addEventListener(Event.SELECT, undock);
  102.  
  103. exitNativeMenuItem.addEventListener(Event.SELECT, closeApp);
  104.  
  105. //Add the menuitems to the menu
  106. menu.addItem(openNativeMenuItem);
  107. menu.addItem(new NativeMenuItem("",true));
  108. //separator
  109. menu.addItem(exitNativeMenuItem);
  110.  
  111. return menu;
  112. }
  113.  
  114. /**
  115. * To be able to dock and undock we need to set some eventlisteners
  116. *
  117. * @Author: S.Radovanovic
  118. */
  119. private function setSystemTrayProperties():void{
  120. //Text to show when hovering of the docked application icon
  121. SystemTrayIcon(NativeApplication.nativeApplication .icon).tooltip = "Systray test application";
  122.  
  123. //We want to be able to open the application after it has been docked
  124. SystemTrayIcon(NativeApplication.nativeApplication .icon).addEventListener(MouseEvent.CLICK, undock);
  125.  
  126. //Listen to the display state changing of the window, so that we can catch the minimize
  127. stage.nativeWindow.addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, nwMinimized); //Catch the minimize event
  128. }
  129.  
  130. /**
  131. * Do the appropriate actions after the windows display state has changed.
  132. * E.g. dock when the user clicks on minize
  133. *
  134. * @Author: S.Radovanovic
  135. */
  136. private function nwMinimized(displayStateEvent:NativeWindowDisplayStateEvent):void {
  137.  
  138. //Do we have an minimize action? //The afterDisplayState hasn't happened yet, but only describes the state the window will go to, //so we can prevent it!
  139. if(displayStateEvent.afterDisplayState == NativeWindowDisplayState.MINIMIZED) {
  140. //Prevent the windowedapplication minimize action from happening and implement our own minimize //The reason the windowedapplication minimize action is caught, is that if active we're not able to //undock the application back neatly. The application doesn't become visible directly, but only after clicking //on the taskbars application link. (Not sure yet what happens exactly with standard minimize)
  141. displayStateEvent.preventDefault();
  142.  
  143. //Dock (our own minimize)
  144. dock();
  145. }
  146. }
  147.  
  148. /**
  149. * Do our own 'minimize' by docking the application to the systray (showing the application icon in the systray)
  150. *
  151. * @Author: S.Radovanovic
  152. */
  153. public function dock():void {
  154. //Hide the applcation
  155. stage.nativeWindow.visible = false;
  156.  
  157. //Setting the bitmaps array will show the application icon in the systray
  158. NativeApplication.nativeApplication .icon.bitmaps = [dockImage];
  159. }
  160.  
  161. /**
  162. * Show the application again and remove the application icon from the systray
  163. *
  164. * @Author: S.Radovanovic
  165. */
  166. public function undock(evt:Event):void {
  167. //After setting the window to visible, make sure that the application is ordered to the front, //else we'll still need to click on the application on the taskbar to make it visible
  168. stage.nativeWindow.visible = true;
  169. stage.nativeWindow.orderToFront();
  170.  
  171. //Clearing the bitmaps array also clears the applcation icon from the systray
  172. NativeApplication.nativeApplication .icon.bitmaps = [];
  173. }
  174.  
  175.  
  176. /**
  177. * Close the application
  178. *
  179. * @Author: S.Radovanovic
  180. */
  181. private function closeApp(evt:Event):void {
  182. stage.nativeWindow.close();
  183. }
  184. ]]>
  185. </mx:Script>
  186.  
  187. </mx:WindowedApplication>

Download code

Tips on shortening some code or something else are always welcome.

Thanks for Jeffry Houser for updating it for Flex 3 (beta 3).

40 Responses to “Flex 3 Air – Minimize to system tray (WindowedApplication)”

  1. auzn says:

    cool~~

  2. Kaman says:

    very COOL…

  3. bladnman says:

    BRAVO! Great work here and well documented example code. Keep up the great work

  4. wizard says:

    10x, very cool

  5. If there was an easier / cleaner way to post this I would.

    Here is code updated for Beta 3. For the most part, Shell was renamed to NativeApplication. It only took me a few minutes to change. I also removed unused imports. In an unexpected maneuver, Beta 3 is allowing us to access “NativeApplication” without importing it.

  6. Looks like my code was stripped out of the post.. ;) But if you contact me off-list I can provide it.

  7. For those wondering, I have blogged about my Beta 3 rendition of Saskovic’s code here:

    http://www.jeffryhouser.com/index.cfm/2008/1/8/How-do-you-put-an-AIR-App-in-the-System-Tray

    ;) Thanks!

  8. WebGyver says:

    Awesome! Thank you very much!

    Although some tweaks were necessary to make this work in Flex 3, I’m beginning to understand a few more things about AIR desktop application development in Flex 3. I greatly appreciate your willingness to share your code with others. Thanks again.

  9. admin says:

    Took me some while, but I’ve finally updated the code according to Jeffry Housers changes.

    Thanks for your effort Jeffry!

  10. Sean Lailvaux says:

    This is great. There is one case that doesn’t work on Windows though.

    The NativeWindowDisplayStateEvent is not fired if I minimise my desktop by pressing “Windows button + M” or “Windows button + D”.

    It seems it’s just hidden.

    How would I get around this?
    No event is fired (that I can find).

  11. Rost Zaika says:

    Ran into the same problem as Sean. The event is not fired in case of using “show desktop” functionality in Windows (at least XP). However the actual “open” item in menu works and restores the windows but if you attach the same action to click the tray icon it will not restore windows (i traced and it actually runs the function but to no avail). Weird…

  12. Sean Lailvaux says:

    If you minimise this on a Mac nothing happens. you need to add a check there… “if supports dock icon… DONT prevent minimise action”.

  13. Evan Gifford says:

    Sick! Now to use Flash to animate the minimize/maximize …

  14. Riga says:

    To make easier?

  15. John Consigli says:

    I’m trying to do the same thing (Minimize to System Tray in Windows) but using Flash CS3 not Flex. Would you happen to know how to write the code for that? By the way, I’m using Air 1.0.

    Also, it might be helpful to know that I created my own close (close_btn) and minimize (mini_btn) buttons.

    Any help you can provide would be greatly appreciated!

  16. pasensyoso manigbas says:

    How can i add mouse over event to system tray icon? I need a functionality to show a new window when the user point to the system tray icon. Any idea?

  17. Jemson Sentillas says:

    How can i add mouse over event in a system tray icon? I need to open new window when a user point to or mouse over system tray icon..Is this possible in AIR app? I appreciate any idea, thanx..

  18. COOKER says:

    Hello, that is a very good tutorial but i got some trouble you know, it gives me debug error when i compile. These are the lines where i got error:

    1- SystemTrayIcon(NativeApplication.nativeApplication .icon).menu = createSystrayRootMenu();

    2- SystemTrayIcon(NativeApplication.nativeApplication .icon).tooltip = “Systray test application”;

    3- SystemTrayIcon(NativeApplication.nativeApplication .icon).addEventListener(MouseEvent.CLICK, undock);

    Well, if i quit those lines, the app seems to be working, but when i minimise, i don’t have any menu so i cannot close the app unless i do it from the taskbar. So i don’t know what to do. Help me please, i’m a desperate rookie, thanks.

  19. Fernando says:

    Very nice code, very simple…but how can i put it into an .as file an invoke it from another mxml file, I don’t want to paste into my large app.mxml..

  20. Javier says:

    Thanks!! works great…

  21. Danxer says:

    Thanks!
    You are my Angel :-)

  22. Reperion says:

    thanks!!

    it’s very helpfull to me :)

  23. werutzb says:

    Hi!

    I want to extend my SQL experience.
    I red really many SQL books and would like to
    get more about SQL for my work as db2 database manager.

    What can you recommend?

    Thanks,
    Werutz

  24. Lisette says:

    Thanks!
    This code is great!
    It works!

  25. Venus says:

    It is beautifully turned out .. I liked ..) would be continuously zahazhivat to you.

  26. Ondrej says:

    great thanks for that … just one little thing … because I’m using nativeApplication.exit(); I needed to change the listener in the init function to this.nativeApplication.addEventListener(Event.EXITING, closingApplication);

    :-)

    Cheers,
    Ondrej

  27. I Really appreciate you… Nice Post and working perfectly also easy to understand..

    Thanks,
    Logeshkumar P.

  28. MrBen says:

    A very informative starter. Thanks muchly. A few comments (for those like me who found this article some 14 months after it was written):

    You can use an embedded asset as the minimizing graphic. Woo!

    [Embed(source='assets/systray_icon_16.png')]
    [Bindable] private var dockImg:Class;

    and in dock() replace the appropriate line with
    NativeApplication.nativeApplication.icon.bitmaps = [BitmapAsset(new dockImg()).bitmapData];

    Kill off all other references to “dockImage”

    Also, creationComplete() is now evidently the incorrect event to use as setSystemTrayProperties() is trying to add events to the stage, which hasn’t yet been created. It only worked because the URL loading took long enough that the stage _was_ initialised. Replace it with applicationComplete() in the WindowedApplication declaration and it’s all good.

  29. molokoloco says:

    Thank for your stuff :)

    For me it work well as this..
    in my mxml…

    private var dockImage:BitmapAsset;

    [Embed(source='icons/systray.png')]
    [Bindable] public var systrayIcoCls:Class;

    private function init():void {
    dockImage = new systrayIcoCls() as BitmapAsset;
    if (NativeApplication.supportsSystemTrayIcon) setSystemTrayProperties();
    addEventListener(Event.CLOSING, closingApplication);
    addEventListener(NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, minimizedApplication);
    }

    private function setSystemTrayProperties():void{
    SystemTrayIcon(NativeApplication.nativeApplication.icon).menu = createSystrayRootMenu();
    SystemTrayIcon(NativeApplication.nativeApplication.icon).tooltip = ‘Favoris Bank’;
    SystemTrayIcon(NativeApplication.nativeApplication.icon).addEventListener(MouseEvent.CLICK, undock);
    }
    private function createSystrayRootMenu():NativeMenu{
    var menu:NativeMenu = new NativeMenu();
    var openNativeMenuItem:NativeMenuItem = new NativeMenuItem(’Ouvrir’);
    var exitNativeMenuItem:NativeMenuItem = new NativeMenuItem(’Quitter’);
    openNativeMenuItem.addEventListener(Event.SELECT, undock);
    exitNativeMenuItem.addEventListener(Event.SELECT, closeApp);
    menu.addItem(openNativeMenuItem);
    menu.addItem(new NativeMenuItem(”,true));
    menu.addItem(exitNativeMenuItem);
    return menu;
    }
    private function alertCloseHandler(event:CloseEvent):void {
    if (event.detail == Alert.YES) closeApp(event);
    else dock();
    }
    private function closingApplication(evt:Event):void {
    evt.preventDefault();
    Alert.buttonWidth = 110;
    Alert.yesLabel = ‘Quitter’;
    Alert.noLabel = ‘Minimiser’;
    alertWindow = Alert.show(’Quitter ou minimiser ?’, ‘Quitter ?’, 1|2, this, alertCloseHandler);
    }
    private function minimizedApplication(displayStateEvent:NativeWindowDisplayStateEvent):void {
    if (displayStateEvent.afterDisplayState == NativeWindowDisplayState.MINIMIZED) {
    displayStateEvent.preventDefault();
    dock();
    }
    }
    private function dock():void {
    stage.nativeWindow.visible = false;
    NativeApplication.nativeApplication.icon.bitmaps = [dockImage];
    }
    private function undock(evt:Event):void {
    stage.nativeWindow.visible = true;
    stage.nativeWindow.orderToFront();
    NativeApplication.nativeApplication.icon.bitmaps = [];
    }
    private function closeApp(evt:Event):void {
    stage.nativeWindow.close(); // My friend this is ze end
    }

  30. [...] – 6 Apps in AIR. The best application I liked is Agile Agenda. And for desktop system tray I found this tutorial I was looking for. Posted in Uncategorized [...]

  31. I built a transparent desktop app and use my own custom buttons…simply called the functions and it worked like a charm without having to have a windowed app. Good luck and email me for the code if you need it.

  32. jeremy mooer says:

    Why would you use a Loader? Great for an example, but why would you wait for a URLRequest to come back. Just compile the thing in.

    [Embed(source="/assets/icons/systrayImage.png")]
    private var imagesource : Class;

    Then in the init:
    dockImage = new imagesource().bitmapData ;

    Quicker, cleaner and smaller.

  33. dev says:

    gr8 work dude..thnx a ton!! i was lookin for something this..

    Cheers !!

  34. Justin says:

    thanks man

  35. Luke says:

    Hi,

    Thanks for the great info.

    The minimise to systray works fine when I compile and run the Air app from within Flex Builder but when I run the app from an install/.air the minimise no longer works and instead of minimising to systray when clicking the windows close/minimise buttons the app just dissapears.

    Anyone know why this is happening? Is it somthing to do with settings in app.xml?

    Cheers,
    Luke

  36. cboese says:

    Hi! Thanks for the post, it got me started really quickly!
    I have one question.
    When I try the example with a png that has alpha transparency, this transparency is not shown in the tray icon.
    I did a screenshot, so you can see what I mean:
    http://bnd24.de/stuff/systray_wo_alpha.png
    The black corners around the red clock should be totally transparent, I thougth…
    How can I get transparency in systray icons working or does it work for you and its just a problem of my dosbox?
    Thanks in advance!

  37. Adrian Smith - London says:

    Many thanks. A great tutorial!!!

  38. Subhash says:

    This is very helpful. Thanks.

  39. Tahir Alvi says:

    Very Nice work.
    But i add more option in system array context menu how i do that.

    Thanks

Leave a Reply