Fitting Native Windows to Content
During the AIR Europe Tour event in Brussels, Belgium I was approached by an attendee with a technical question. He wanted to drag and drop video content from the desktop (or presumably other source), and drop it into his application. After the drop operation, he wanted to size the native window to fit the specific content size. This might initially sound pretty easy, but it requires accounting for the OS-specific chrome. There are also some usability questions to consider.
The first step is to add an event listener for a native drag event. There are several of these, but in terms of bringing content into an AIR application, you’re mostly concerned with NATIVE_DRAG_ENTER and NATIVE_DRAG_DROP. In my lightweight implementation, I’ve chosen to check the file extension for JPEG or PNG content, which I know is supported by AIR. I’ve also chosen to limit the extension check to the first file in the array of files that may possibly be dragged.
// Called when a drag operation enters the window
public function doEnter( event:NativeDragEvent ):void
{
var imgFound:Boolean = false;
var item:File = null;
// Check and see if there is a list of files on the drag operation
if( event.clipboard.hasFormat( ClipboardFormats.FILE_LIST_FORMAT ) )
{
files = event.clipboard.getData( ClipboardFormats.FILE_LIST_FORMAT,
ClipboardTransferMode.CLONE_PREFERRED ) as Array;
// Iterate through the files to make sure there are images
for( var i:Number = 0; i < files.length; i++ )
{
item = files[i] as File;
if( item.extension.toLowerCase() == JPEG ||
item.extension.toLowerCase() == PNG )
{
imgFound = true;
break;
}
}
}
// Accept the drop if there is an image in a file list
if( imgFound )
{
NativeDragManager.acceptDragDrop( this );
}
}
This is clearly functional, but very limited. Are we sure that the file extension of JPG or PNG actually means that the identified file is actually of that type? Is the application prepared to handle the situation in which the file really isn’t of that type? Should we physically inspect the file contents to be sure of the file type?
And what of the number of files that are being dragged into the application? How does the UI inform the user that either only the first item will be accepted? If multiple files are going to be accepted, does that mean we need to perform the above check on each item? Do we perform these checks when the content is dragged into the application, or when it is actually dropped? Both?
I don’t pretend to have definitive answers for these questions, and in fact, they’ll likely be different for each application. An interesting side effect of these questions however is that when paired with resizing the native window to the content dimensions, you effectively limit the available screen reality available to display an message to the user. Fitting the native window to the content may actually compound the usability situations outlined above.
I’m not suggesting that the technique is wrong or invalid in any way. In fact, much of what I’ve called into question is applicable regardless of the application. It’s an interesting question that we as desktop developers (using web technologies) must consider handling. The desktop is a great place to deliver amazing functionality, but at the same time introduces a degree of unpredictability in the form of the OS itself, and how users will expect to interact with your application.
Okay, I feel like I’ve done a sufficient job in setting up the disclaimer, now onto my solution.
The first step is to make sure the content you want to size the window to fit, has actually been loaded. The easiest way to do this for an image (or SWF, or video, etc.) is simply to listen for the complete event on the content holder. How you’ll get the size will depend on some level to the nature of the content that you are loading. In the case of an image, specifically an instance of the Image control, we can point to the Image.contentHeight and Image.contentWidth properties.
The next step is to know the dimensions of the current stage. This may be because previously loaded content was of a different dimension, or simply that the window was never populated with any other content. Perhaps the user resized the window? In which case I’ll point you back to my disclaimer about the usability of this trick. The stage is available as a property on the WindowedApplication object, and contains Stage.stageHeight and Stage.stageWidth properties.
The last value we need to know is the dimensions of the native window itself. In the case of WindowedApplication, a reference to the native window can be accessed via the WindowedApplication.nativeWindow property. The NativeWindow object will have height and width properties that reflect the native window itself, including any native chrome. You may also want to account for the Flex-created status bar if you’re using it, which can be access via WindowedApplication.statusBar.height.
Subtracting the stage width and height from the native window width and height will give us the width and height of the chrome itself as painted by the operating system. Add those width and height dimensions onto the loaded content, an image in this case, and you’ll have the size you want to make the native window. You can set the NativeWindow.width and NativeWindow.height properties to make the window fit the content. Again, you may also want to account for the status bar if you are using that feature.
// Measurements
chromeHeight = Math.abs( stage.nativeWindow.height - stage.stageHeight );
chromeWidth = Math.abs( stage.nativeWindow.width - stage.stageWidth );
...
// Get appropriate window dimensions
var targetHeight:Number = img.content.height + chromeHeight;
var targetWidth:Number = img.content.width + chromeWidth;
var targetX:Number = ( Capabilities.screenResolutionX - targetWidth ) / 2;
var targetY:Number = ( Capabilities.screenResolutionY - targetHeight ) / 2;
// Size the native window
stage.nativeWindow.width = targetWidth;
stage.nativeWindow.height = targetHeight;
As an added bonus, you may want to center the window since the size has changed. I like to do this by getting the resolution of the screen, subtracting the dimensions of the native window, and then dividing by two (2). This is actually a good way to center pretty much anything inside any other content, and can be used in your own internal layouts. In this case, screen resolution is found on the Capabilities.screenResolutionX and Capabilities.screenResolutionY properties.
// Utility method to center the native window
public function centerWindow( w:Number = 200, h:Number = 200 ):void
{
var targetWidth:Number = w + chromeWidth;
var targetHeight:Number = h + chromeHeight;
var targetX:Number = ( Capabilities.screenResolutionX - targetWidth ) / 2;
var targetY:Number = ( Capabilities.screenResolutionY - targetHeight ) / 2;
stage.nativeWindow.x = targetX;
stage.nativeWindow.y = targetY;
}
As a demonstration, I’ve put together a little application that allows you to drop a single image, or multiple images onto the AIR application. The window gets resized and re-centered based on the size of the content. I’ve even put in some animation between sizes, and to transition from one image to the next. The drag handlers even check to filter out files that are not images. As usual the source code for the application (written using Flash CS3) is available for download.