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.
April 11th, 2008 at 10:52 pm
These questions/dilemmas are pure luxury. The luxury of working with something so groundbreaking and flexible as AIR.
The developers will work the questions out as they go along, and with time and experience, will establish de-facto standards for achieving certain desired end results.
Exciting days, these.
April 21st, 2008 at 4:30 pm
Very clear instructions this has been a great help thank you
April 22nd, 2008 at 5:15 am
Thanks for the nice post Kevin. great to know and learn, this is perfect for trying things out.
April 24th, 2008 at 11:36 am
Good very definition about ‘Fitting Native Windows to Content’ i liked it also i have downloaded its Flash CS3 zip copy, Thanks!
April 26th, 2008 at 6:41 pm
Very useful information and right now it is very special for because i am in, many thanks for share this.
April 27th, 2008 at 11:25 pm
It really helps me a lot thank you very much!
非常感谢:)
April 29th, 2008 at 3:28 am
thanks for the application kevin, very clear instructions as well
May 9th, 2008 at 3:55 pm
Thank you for the article.
June 12th, 2008 at 4:33 am
AFAIK changing the stage size directly will change the size of the native window correctly, making it unnecessary to calculate the chrome size:
stage.stageHeight = img.content.height; // sets the ‘inner’ height of the window
stage.stageWidth = img.content.width; // sets the ‘inner’ width of the window