Tuesday, February 21, 2012

Creating a Flex AIR Annotator app: Part 2

In this part we will add functionality to import a picture to our application.

First, let's change the content of our VGroup.

The text we had before will be changed with a BigButton component (a custom component). However, we'll include it in a ViewStack as well. Give the stack an id of "stack" and set change event handelr to stackChange(). Add 2 NavigatorContent objects for now - one containing our BigButton control and another containing an mx:Image control with an id of imgHolder.

Now, more about the BigButton object. It has an icon property which works the same way that icons in IconButton works. We direct it to a folder.png picture in the lib folder, which is this picture:


We also give it a subtext property with a value of "Import picture". Two more unusual properties are bWidth and bHeight. Set those to 300 and 200 (the same size that drawArea is for now). Add an event listener for the click event - set handler to importPicture().

<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox backgroundColor="#ccccdd" width="100%" height="68">
<custom:IconButton icon="@Embed('../lib/bubble.png')" toolTip="Annotation" enabled="true" buttonMode="true" />
<custom:IconButton icon="@Embed('../lib/bubble.png')" toolTip="Annotation" enabled="false" buttonMode="true" />
</mx:HBox>
<mx:Canvas width="100%" height="100%" horizontalScrollPolicy="on" verticalScrollPolicy="on">
<mx:Box backgroundColor="#eeeeee" width="100%" height="100%" verticalAlign="middle" horizontalAlign="center" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:Box id="drawArea" backgroundColor="#ffffff" width="300" height="200" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:ViewStack id="stack" change="stackChange();">
<s:NavigatorContent>
<custom:BigButton icon="@Embed('../lib/folder.png')" subtext="Import picture" toolTip="Open file" enabled="true" buttonMode="true" bWidth="300" bHeight="200" click="importPicture();" />
</s:NavigatorContent>
<s:NavigatorContent>
<mx:Image id="imgHolder" />
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</mx:Box>
</mx:Canvas>
</s:VGroup>

Let's create this BigButton component. Create a new file called BigButton.mxml. It's based off spark:Button, and has a skin of bigSkin. Add the 4 properties to the class:

<?xml version="1.0" encoding="utf-8"?>
<s:Button xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx" skinClass="bigSkin">

<fx:Metadata>
[Style(name="icon",type="*")]
[Style(name="subtext",type="*")]
[Style(name="bWidth",type="*")]
[Style(name="bHeight",type="*")]
</fx:Metadata>

</s:Button>

Now create another file called bigSkin.mxml. It basically consists of a VGroup with a picture and the text in it:

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin name="iconSkin"
        xmlns:fx="http://ns.adobe.com/mxml/2009"
        xmlns:s="library://ns.adobe.com/flex/spark" 
        minWidth="128" minHeight="128"
        alpha.disabled="0.5">

    <s:states>
        <s:State name="up" />
        <s:State name="over" />
        <s:State name="down" />
        <s:State name="disabled" />
    </s:states>
 
    <fx:Metadata>
        [HostComponent("spark.components.Button")]
    </fx:Metadata>
 
    <s:Rect width="{hostComponent.getStyle('bWidth')}" height="{hostComponent.getStyle('bHeight')}" alpha.up="0" alpha.over="0.4" alpha.disabled="0">
<s:fill>
<s:SolidColor color="#ababef"/>
</s:fill>
</s:Rect>
<s:VGroup width="{hostComponent.getStyle('bWidth')}" height="{hostComponent.getStyle('bHeight')}" horizontalAlign="center" verticalAlign="middle">
<s:BitmapImage source="{hostComponent.getStyle('icon')}" top="2" right="2" left="2" bottom="2" alpha.disabled="0.5" />
<s:Label fontSize="24" color="#6666ff" text="{hostComponent.getStyle('subtext')}" />
</s:VGroup>
 
</s:SparkSkin>

Now go back to Main.mxml.

Create a function called importPicture(), the one that's called when the button is pressed.

Here we create a new File object, an ImageFilter, and use the file's browseForOpen() method to let the user import a picture:

private function importPicture():void {
var file:File = new File();
var imageFilter:FileFilter = new FileFilter("Images", "*.jpg;*jpeg;*.gif;*.png");
file.browseForOpen("Import picture", [imageFilter]);
file.addEventListener(Event.SELECT, fileSelect);
}

The fileSelect() function, which is the event handler for file's SELECT event uses a Loader class to load the image with the provided url:

private function fileSelect(evt:Event):void {
var file:File = evt.target as File;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
loader.load(new URLRequest(file.url));
}

The loadComplete() function takes data from the loader's content and creates a BitmapData out of it. Then the selectedIndex of stack is set to 1:

private function loadComplete(evt:Event):void {
content = evt.target.content;
bitmapData = new BitmapData(content.width, content.height, false);
bitmapData.draw(content, new Matrix(), null, null, null, true);
stack.selectedIndex = 1;
}

Now create a function called stackChange(), which is called when the selectedIndex of the stack changes. Add a conditional checking if the index is 1, if it is - set the image's source to a Bitmap object with our bitmapData, and set the size of drawArea and imgHolder to size of content.

private function stackChange():void {
if (stack.selectedIndex == 1) {
imgHolder.source = new Bitmap(bitmapData);
drawArea.width = content.width;
drawArea.height = content.height;
imgHolder.width = content.width;
imgHolder.height = content.height;
}
}

And there we go.

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
   xmlns:s="library://ns.adobe.com/flex/spark"
   xmlns:mx="library://ns.adobe.com/flex/mx" 
   xmlns:custom="*"
   showStatusBar="false"
   creationComplete="init();" minWidth="400" minHeight="400"
   width="600" height="500">

<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>

<fx:Script>
<![CDATA[
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.FocusDirection;
import flash.display.Loader;
import flash.events.Event;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.filters.DropShadowFilter;
import flash.geom.Matrix;
import flash.net.FileFilter;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import mx.controls.Alert;
import flash.filesystem.FileMode;

private var bitmapData:BitmapData;
private var content:*;

private function init():void{
drawArea.filters = [new DropShadowFilter(4, 60, 0, 0.7, 10, 10, 1, 3)];
}

private function importPicture():void {
var file:File = new File();
var imageFilter:FileFilter = new FileFilter("Images", "*.jpg;*jpeg;*.gif;*.png");
file.browseForOpen("Import picture", [imageFilter]);
file.addEventListener(Event.SELECT, fileSelect);
}

private function fileSelect(evt:Event):void {
var file:File = evt.target as File;
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadComplete);
loader.load(new URLRequest(file.url));
}

private function loadComplete(evt:Event):void {
content = evt.target.content;
bitmapData = new BitmapData(content.width, content.height, false);
bitmapData.draw(content, new Matrix(), null, null, null, true);
stack.selectedIndex = 1;
}

private function stackChange():void {
if (stack.selectedIndex == 1) {
imgHolder.source = new Bitmap(bitmapData);
drawArea.width = content.width;
drawArea.height = content.height;
imgHolder.width = content.width;
imgHolder.height = content.height;
}
}
]]>
</fx:Script>

<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox backgroundColor="#ccccdd" width="100%" height="68">
<custom:IconButton icon="@Embed('../lib/bubble.png')" toolTip="Annotation" enabled="true" buttonMode="true" />
<custom:IconButton icon="@Embed('../lib/bubble.png')" toolTip="Annotation" enabled="false" buttonMode="true" />
</mx:HBox>
<mx:Canvas width="100%" height="100%" horizontalScrollPolicy="on" verticalScrollPolicy="on">
<mx:Box backgroundColor="#eeeeee" width="100%" height="100%" verticalAlign="middle" horizontalAlign="center" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:Box id="drawArea" backgroundColor="#ffffff" width="300" height="200" horizontalScrollPolicy="off" verticalScrollPolicy="off">
<mx:ViewStack id="stack" change="stackChange();">
<s:NavigatorContent>
<custom:BigButton icon="@Embed('../lib/folder.png')" subtext="Import picture" toolTip="Open file" enabled="true" buttonMode="true" bWidth="300" bHeight="200" click="importPicture();" />
</s:NavigatorContent>
<s:NavigatorContent>
<mx:Image id="imgHolder" />
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</mx:Box>
</mx:Canvas>
</s:VGroup>

</s:WindowedApplication>

Thanks for reading!

2 comments:

Sharat Achary said...

Here whenever the image is loaded, the viewstack is set to index of 1, and the width-height of both drawArea and imageHolder is set same of the content.
So my question is that, even when I load an image of dimension 256x256, drawArea shows an horizontal scrollbar, just curious to know why this happens?

Humble Regards,
Sharat

Kirill Poletaev said...

It is fixed by putting horizontalScrollPolicy="off" verticalScrollPolicy="off" in the tags.

As to why this happens, I am not sure, because there may be many reasons. It is just the way components in Flex work, I guess, it's the way the developers made it.

Post a Comment