Using Owned Native Window Functionality with Spark Windows

The release of AIR 2.6 brought about a lot of enhancements and features. One of which was owned native windows. What’s special about this is it makes it easy to have a main window that owns other windows, which is ultra-useful for applications that make use of multiple windows for things like tool palettes, property inspectors, etc.

The only problem is this new functionality only works out of the box for NativeWindow instances. A Spark Window, however, manages its NativeWindow internally and does not currently include support for the new owned window logic. Now, you could subclass Window and override commitProperties to add in the functionality but, that’s neither desired nor maintainable.

Why does it even matter? Because a Spark Window is skinnable and if you’ve already taken the time to build an application using skinnable Windows, you don’t want to labor over the changes necessary just to use this functionality.

So, I chose to take a different approach because I’m confident that the AIR SDK will eventually include support for ownable Spark Windows. Until then, I’m using an OwnableWindow interface that all of my Window subclasses I want to be owned are required to implement. This way I can take advantage of the new functionality in the least obtrusive way possible.

Here’s the code for OwnableWindow:

package labs.otuome.air.myapp.view.panel
{
   import flash.display.NativeWindow;
   import flash.events.Event;
   import flash.events.NativeWindowDisplayStateEvent;
 
   public interface OwnableWindow
   {
      function get windowOwner():NativeWindow;
      function set windowOwner( value:NativeWindow ):void;
 
      function respondToOwnerWindowActivated( event:Event ):void;
      function respondToOwnerWindowMinimizedOrRestored( event:NativeWindowDisplayStateEvent ):void;
      function respondToOwnerWindowClosing( event:Event ):void;
   }
}

Now, in my Spark Window subclass I implement the interface like so:

package labs.otuome.air.myapp.view.panel
{
   import flash.display.NativeWindow;
   import flash.display.NativeWindowDisplayState;
   import flash.events.Event;
   import flash.events.MouseEvent;
   import flash.events.NativeWindowDisplayStateEvent;
 
   import mx.events.FlexEvent;
 
   import spark.components.Button;
   import spark.components.RichText;
 
   /**
    * Spark Window component subclass.
    * @author Hasan Otuome
    */
   public class FloatingPanelView extends FloatingPanelUI implements OwnableWindow
   {
      ///////////////////////////////////////////////////////////////////////////
      // PRIVATE PROPERTIES
      ///////////////////////////////////////////////////////////////////////////
      private var _panelTitle:String;
      private var _windowOwner:NativeWindow;
      //////////////////////////////////////////////////////////////////////////
      // PUBLIC PROPERTIES
      /////////////////////////////////////////////////////////////////////////
      [SkinPart (required=false)]
      public var header:Button;
 
      [SkinPart (required=false)]
      public var toggle:Button;
 
      [SkinPart (required=false)]
      public var contentTitle:RichText;
 
      /**
      * Sets the panel's title. 
      * @param value
      */		
      public function set panelTitle( value:String ):void
      {
           _panelTitle = value;
      }
      /**
      * Allows you to specify an owner for this Spark 
      * Window instance. Used only until the Flex SDK 
      * adds support for owned native windows to Spark 
      * Windows.
      */		
      public function get windowOwner():NativeWindow
      {
           return _windowOwner;
      }
      public function set windowOwner( value:NativeWindow ):void
      {
           _windowOwner = value;
      }
      ///////////////////////////////////////////////////////////////////////////
      // PUBLIC METHODS
      ///////////////////////////////////////////////////////////////////////////
      /**
      * Constructor 
      */		
      public function FloatingPanelView()
      {
        super();
 
        addEventListener( FlexEvent.CREATION_COMPLETE, _onCreationComplete, false, 0, true );
      }
      //---------- OwnableWindow implementations --------------------
      /**
      * Adjust the display ordering of this 
      *	instance as it relates to its owner. 
      */
      public function respondToOwnerWindowActivated( event:Event ):void
      {
        this.nativeWindow.orderInFrontOf( _windowOwner );
      }
 
      /**
      * Show/hide this instance based 
      *	on the display state of its owner. 
      */
      public function respondToOwnerWindowMinimizedOrRestored( event:NativeWindowDisplayStateEvent ):void
      {
        switch (true)
        {
           case (event.beforeDisplayState == NativeWindowDisplayState.MINIMIZED):
             this.restore();
             break;
           case (event.afterDisplayState == NativeWindowDisplayState.MINIMIZED):
             this.minimize();
             break;
        }
      }
 
      /**
      * Close this instance when its owner is closed. This 
      *	will be handled automatically if the owner is 
      *	the main application window.  
      */
      public function respondToOwnerWindowClosing( event:Event ):void
      {
        this.close();
      }
      //////////////////////////////////////////////////////////////////////////
      // OVERRIDES
      /////////////////////////////////////////////////////////////////////////
 
      ////////////////////////////////////////////////////////////////////////
      // PRIVATE METHODS
      ///////////////////////////////////////////////////////////////////////
      /**
      * CREATION_COMPLETE event handler. 
      * Ensures this instance responds to 
      * display state changes of the 
      * instance's owner. 
      * @param e
      */		
      private function _onCreationComplete( e:FlexEvent ):void
      {
        if (_windowOwner)
        {
           _windowOwner.addEventListener( Event.ACTIVATE, respondToOwnerWindowActivated, false, 0, true );
           _windowOwner.addEventListener( NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGE, respondToOwnerWindowMinimizedOrRestored, false, 0, true );
           _windowOwner.addEventListener( Event.CLOSING, respondToOwnerWindowClosing, false, 0, true );
        }
 
        removeEventListener( FlexEvent.CREATION_COMPLETE, _onCreationComplete );
      }
   }
}

I set the windowOwner property when I instantiate the class(es) and I have a skinnable Spark Window(s) that can be owned by another NativeWindow. Downside is that the z-ordering produces a noticeable flicker and only works on the 1st click of the owner window.

Did I have to use an interface? Not at all, but it’s nice to be able to enforce the contract and easily tear it up once it’s no longer needed. Taking a different approach? Share it…

One comment

  1. thanks for your code it’s very useful. my only suggestion is to set this.alwaysInFront=true at the beginning instead of listening to ACTIVATE event

Leave a Reply

Your email address will not be published. Required fields are marked *