This post is about hacking the spark.components.Window
component in the Flex 4 SDK.
Introduction
I wanted to create a simple debug console for an AIR application at work. Nothing fancy, just a standalone window with a non-editable text area for displaying log messages. This debug console should be accessible through a tray/dock menu and via a global keyboard shortcut.
Problem 1: removing the status bar
Supposedly, spark.components.Window
is the bee's knees when it comes to standalone native windows in AIR. After struggling for the good part of an hour trying to create a Window programmatically and opening it, I finally realized what I really should be doing is creating an MXML Component. Boom, five minutes later I had my ConsoleWindow
up an running, GUI hooked up with event handlers and everything:

Simple enough. However; did you notice the unused — thus annoying — status bar? Why all Windows in AIR have an empty status bar is beyond me, but well, let's hide it. If you're familiar with Flex, you're probably thinking 'just use showStatusBar="false"
in the MXML and be done with it'. Can it be that it was all so simple then? No, it cannot. This is what happens:

Extra space not contributing to anything in particular. That's even more annoying than having an unused status bar. So I tried various (combinations of) statements to remove it:
- Set
this.showStatusBar = false
after Window creation, does same as when setting it in MXML - Specifying
statusBar="{null}"
in MXML, does absolutely nothing - Set
this.statusBar = null
after Window creation, does nothing... this.removeChild(statusBar)
doesn't compile (expectsDisplayObject
, getsIVisualElement
)this.removeElement(statusBar)
throws anArgumentError: ConsoleWindow12.WindowedApplicationSkin13.Group14.Group16 is not found in this Group.
in spark.components.Group.as:1086this.removeChild(statusBar as DisplayObject)
throws anError: removeChild() is not available in this class. Instead, use removeElement() or modify the skin, if you have one.
this.statusBar.parent.removeChild(statusBar)
doesn't compile, same reason as #4
As you may see, there's a lot of bogus going on because of different types; removeChild()
works on DisplayObject
classes, removeElement()
works on IVisualElement
classes, statusBar.parent
returns a DisplayObjectContainer
which doesn't have removeElement()
, etc.
Solution
So what's the solution? In the creationComplete
event handler, cast the statusBar.parent
to an IVisualElementContainer
and call removeElement(statusBar)
, like this:
private function onCreationComplete():void { // hack to remove status bar (statusBar.parent as IVisualElementContainer) .removeElement(statusBar); }
Extremely elegant, I know. It works, though:

Problem 2: closing the console and opening it again later
You'd think that you could open()
and close()
the window component freely after it was created. As it turns out, the API docs for NativeWindow.close()
points out a teeny-weeny gotcha:
“Closed windows cannot be reopened. If the window is already closed, no action is taken and no events are dispatched.”
This doesn't exactly go hand in hand with my intentions of using the window as a debug console, where you'd open the console and check something, close it, wait for something to happen, then reopen the console and check for any messages.
Of course, I do understand why the window is destroyed per default. It saves memory, and one might argue that in most cases you'd actually want it to be destroyed. In my case, though, this is not desirable, so let's hack it.
Solution
There's a two-step solution to making the Window behave as desired.
- Add an event handler for the
closing
event, in which you prevent default action and instead hide the window. - Override the
open()
method and add some more logic to it.
private function onClosing(event:Event):void { event.preventDefault(); visible = false; } override public function open(openWindowActive:Boolean = true):void { if (_opened) { visible = true; if (openWindowActive) activate(); } else { super.open(openWindowActive); _opened = true; } }
Conclusion
So there you have it. That's how to tweak the Window class to your liking. There's also the problem of not being able to write messages to the text area until it is created, but that's easily solved by buffering messages. Here's the code for the entire debug window:
<?xml version="1.0" encoding="utf-8"?> <s:Window xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" width="640" height="480" title="Debug Console" showStatusBar="false" closing="onClosing(event)" creationComplete="onCreationComplete()"> <s:TextArea id="out" left="0" right="0" top="0" bottom="36" editable="false"/> <s:Button bottom="8" left="8" label="Clear" click="clear()"/> <s:Button bottom="8" right="8" label="Close" click="close()"/> <s:layout> <s:BasicLayout/> </s:layout> <fx:Script> <![CDATA[ import mx.core.IVisualElementContainer; import flash.filesystem.File; private var _buffer:Array = []; private var _opened:Boolean = false; /** * Adds message to console. * * @param message Message to add to console. */ public function log(message:String):void { if (_buffer != null) { _buffer.push(message); } else { out.appendText(message + File.lineEnding); } } /** * Removes all messages. */ public function clear():void { out.text = ""; } /** * @inheritDoc */ override public function open(openWindowActive:Boolean = true):void { if (_opened) { visible = true; if (openWindowActive) activate(); } else { super.open(openWindowActive); _opened = true; } } /** * @private * Hack to prevent window destruction. */ private function onClosing(event:Event):void { event.preventDefault(); visible = false; } /** * @private * Post-creation setup. */ private function onCreationComplete():void { // hack to remove status bar (statusBar.parent as IVisualElementContainer) .removeElement(statusBar); // clear buffered log messages for (var i:* in _buffer) out.appendText(_buffer[i] + File.lineEnding); _buffer = null; } ]]> </fx:Script> </s:Window>
Wonderful post! Thanks, man. I went through exactly the same train of thought (steps 1-7) to try to remove that status bar as you did.
ReplyDeleteI wonder, have you ever heard of Arthropod? It's an AIR application that is great for debugging commands and probably does exactly what you're looking for and more. Check it out at http://arthropod.stopp.se/
Oh snap, a comment! (why did I not get any email notification? oO)
ReplyDeleteI have not heard about it, but it looks interesting. I'll check it out!
Regarding the status bar problem: This has been fixed in Flex 4 beta 2, so this post is kinda obsolete.
Other easy way to remove status bar is to skin your application/window. Create a new skin and just delete the status bar skin part...
ReplyDeletehahahaha.. This post was really awesome. really interesting though im not that familiar with these. Anyway thank you for posting, Man.
ReplyDeleteIt worked for me, too, :)
ReplyDelete(statusBar.parent as IVisualElementContainer)
.removeElement(statusBar);
Very nice.
This was very hard to spot this error.
Chris
Thanks for this! Just what I needed!
ReplyDelete