Adding Drag and Drag functionality in Adobe Air

In this tutorial I will teach you step by step how to use the out of the box drag & drop functionality provided by flex list controls.

Just some basic theory before we go into code. The drag & drop process has three stages – initiation, dragging and dropping.

The initiation is when the user clicks on a flex component and keeps the button pressed. In this case the component that the user clicks on is the drag initiator. Any flex component that supports dragging will handle either the mouseDown() or mouseMove() event to initiate the drag & drop operation. The event handler will then create a DragSource object which contains data related to the object being dragged. For example if you are dragging a file between two components, the DragSource object will contain a reference to a File object. Once the DragSource is constructed the DragManager.doDrag() method is called where the first argument is a reference to the object that initiated the drag, the second argument is the DragSource object and the third argument is a reference to the mouse event that called the event handler. There is a fourth optional argument that allows to specify a drag proxy that may be used to represent the object being dragged on the screen(e.g. a semi-transparent version of the drag initiator)

In the Dragging stage the user moves the flex component across the screen up to the drop target. If you specify a drag proxy image, that is what is shown on the screen when dragging the component. If not specified a rectangle will be shown instead.

In the dropping stage when the component being dragged arrives to a potential drop target, a dragEnter event is raised. The event handler that handles this event should check whether the DragSource object contains data that is of an acceptable format. If that’s the case then DragManager.acceptDragDrop() is called indicating that the drop target is happy in accepting the dragged data.

In this tutorial I will teach you step by step how to use the out of the box drag & drop functionality provided by flex list controls.

Just some basic theory before we go into code. The drag & drop process has three stages – initiation, dragging and dropping.

The initiation is when the user clicks on a flex component and keeps the button pressed. In this case the component that the user clicks on is the drag initiator. Any flex component that supports dragging will handle either the mouseDown() or mouseMove() event to initiate the drag & drop operation. The event handler will then create a DragSource object which contains data related to the object being dragged. For example if you are dragging a file between two components, the DragSource object will contain a reference to a File object. Once the DragSource is constructed the DragManager.doDrag() method is called where the first argument is a reference to the object that initiated the drag, the second argument is the DragSource object and the third argument is a reference to the mouse event that called the event handler. There is a fourth optional argument that allows to specify a drag proxy that may be used to represent the object being dragged on the screen(e.g. a semi-transparent version of the drag initiator)

In the Dragging stage the user moves the flex component across the screen up to the drop target. If you specify a drag proxy image, that is what is shown on the screen when dragging the component. If not specified a rectangle will be shown instead.

In the dropping stage when the component being dragged arrives to a potential drop target, a dragEnter event is raised. The event handler that handles this event should check whether the DragSource object contains data that is of an acceptable format.  If that’s the case then DragManager.acceptDragDrop() is called indicating that the drop target is happy in accepting the dragged data.

Fortunately for list controls most of this work is already done for you. Implementing drag & drop is very simple:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal">
     <mx:ArrayCollection id="source">
            <mx:Array>
            <mx:Object singer="Michael Jackson" />
            <mx:Object singer="Jennifer Lopez" />
            <mx:Object singer="Janet Jackson" />
            <mx:Object singer="Paul Mcarthney" />
        </mx:Array>
    </mx:ArrayCollection>

   <mx:ArrayCollection id="destination">
      <mx:Array>
          <mx:Object singer="Sheryl Crowe"/>
      </mx:Array>
   </mx:ArrayCollection>
   <mx:DataGrid dataProvider="{source}" dragEnabled="true" dragMoveEnabled="true"/>
   <mx:DataGrid dataProvider="{destination}" dropEnabled="true"/>
</mx:Application>

In the example above by setting dragEnabled to <b>true</b> allows the rows inside the data grid to be dragged. For any dragged rows to be accepted by the other data grid we need to set dropEnabled to true on that target DataGrid. We also set dragMoveEnabled to true to enable moving rows between data grids.

Ok, now let’s see how we can add drag & drop support to our file system manager. The steps required to do this are:

Step 1

We need to set the dragEnabled and the dropEnabled properties to true on both FileSystemDataGrid. The reason we do on both is because we want to allow the user to copy or move files in both directions: from left to right and right to left.

Step 2

We need to override the default handler for the DragEvent.DRAG_DROP event. This is because the standard drag & drop behavior for the FileSystemDataGrid isn’t quite what we need. In this event handler we need to call event.preventDefault() so that the default event handler is not called. The event handler inspects what file is specified inside the DragSource object carried by the event and performs the copy/move operation as specified by the key that user pressed. If the user just drags a file between the two data grids while pressing the shift key a move file operation is performed. Otherwise the default is to do a copy operation. If the file operation goes is successful we refresh the contents of two FileSystemDataGrid’s, otherwise an alert box is shown.

Here is the code:

 /**
  * Determines what object was dropped in the data grid
  */
 private function handleDragDrop(event:DragEvent):void {
 	// we do this because we want to override the default drag and drop
 	// behaviour implemented in the DataGrid
 	event.preventDefault();
 	var dragSource:DragSource=event.dragSource;
 	if (dragSource.hasFormat("items")) {
 		var items:Array = dragSource.dataForFormat("items") as Array ;
 		for each (var item:File in items) {
 			// copy this file to the directory indicated by fs2
 			// make sure that the origin data grid is not the same as the dest
 			if (event.dragInitiator!= event.target) {
 				var fileDest:File = FileSystemDataGrid(event.target).directory.resolvePath(item.name)

 				var dataGrid:FileSystemDataGrid=event.currentTarget as FileSystemDataGrid;
 				item.addEventListener(Event.COMPLETE, refreshFileSystems);
 				// if the user has pressed the shift key then he just
 				// wants to copy the file
 				if (event.shiftKey)
 				{
 					this.copyFile(item, fileDest);

 				}
 				else {
 					this.moveFile(item, fileDest);
 				}

 			}
 		}
 	}
 }

Click here to download the File System Manager Air app with Drag and Drop support


It is very cool to drag and drop a file from one file system data grid to another. However wouldn’t it be even cooler to be able to drag and drop files from the operating system desktop to Air file manager? That kind of drag & drop requires native drag & drop support. If you are interested in native drag and drop support then read my post on Native Drag and Drop in Adobe Air with example

How to apply effects to components inside containers with automated layout

In this tutorial I will explain how to apply a tween effect to a component inside a container with automated layout.

Containers like VBox, HBox, Panel(except for layout=absolute), Grid, … automatically position items for you.

Lets try to apply a Move effect to a Button inside a VBox:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" height="500" width="500">
<mx:Script>
 <![CDATA[
     /**
     * This method runs an effect on a button to move it
     * to the right side.
     * 
     */
  public function moveButton(button:Button):void {
   this.moveEffect.target= button;
   this.moveEffect.play();
  }
 ]]>
</mx:Script>
 <mx:VBox id="box" clipContent="false" backgroundAlpha="1" backgroundColor="red">
  <mx:Button id="button1" label="Button 1" click="moveButton(button1)"/>
  <mx:Button id="button2" label="Button 2" click="moveButton(button2)"/>
  <mx:Button id="button3" label="Button 3" click="moveButton(button3)"/>
 </mx:VBox>
  <mx:Sequence id="moveEffect">
   <mx:Move duration="5000" xBy="300" yBy="300"/>
   <mx:Move duration="5000" xBy="-300" yBy="-300"/>
  </mx:Sequence>
</mx:Application>

Click here to run our example

In the example above we set clipContent to false to enable the button to be visible outside the VBox container after it moves.

To start the move effect you need to click in one of the buttons. You will then see the button you clicked move to the right bottom corner and then return to its original position. This example is not completely correct, but it works for this simple example.

To understand why it might not work lets add a Resize effect to the VBox to go along with the button Move effect:

<mx:Parallel id="moveEffect">
 <mx:Sequence>
  <mx:Move duration="5000" xBy="300" yBy="300"/>
  <mx:Move duration="5000" xBy="-300" yBy="-300"/>
 </mx:Sequence>
 <mx:Sequence>
  <mx:Resize target="{box}" duration="5000" heightBy="300" widthBy="300"/>
  <mx:Resize target="{box}" duration="5000" heightBy="-300" widthBy="-300"/>
 </mx:Sequence>
</mx:Parallel>

At the same time that we move the button to the right bottom corner and then back to its origin, we also increase the size of the VBox and then decrease it back to its original size. Note here the use of Sequence and Parallel. These two effects allow us to run a list of child effects in sequence or in parallel as you probably guessed.

Click here to try the example above

Did you notice anything wrong with the example above? Now the Move effect doens’t work anymore. This is because every time we change the size of the VBox, Flex needs to re-position the items inside the VBox. Because of this the x and y values of the button are re-calculated thus ruining our Move effect.
The solution to this problem is to set autoLayout=”false” immediately before the start of the effect:

<mx:VBox id="box" clipContent="false" backgroundAlpha="1" backgroundColor="red">
 <mx:Button id="button1" label="Button 1" click="moveButton(button1)"/>
 <mx:Button id="button2" label="Button 2" click="moveButton(button2)"/>
 <mx:Button id="button3" label="Button 3" click="moveButton(button3)"/>
</mx:VBox>




Click here to see the full working solution in action

The DisplayObject Blend Mode

The blend mode controls what happens when one display object is on top of another. The default setting in Adobe Flex is that the pixels of a display object override those that are displayed in the background. However you can change this behavior by modifying the blendMode setting. Read the language reference entry for DisplayObject. There you will find a detailed description of each blend mode.
In a nutshell the different blend modes available are:

  • BlendMode.NORMAL
  • BlendMode.LAYER
  • BlendMode.MULTIPLY
  • BlendMode.SCREEN
  • BlendMode.LIGHTEN
  • BlendMode.DARKEN
  • BlendMode.DIFFERENCE
  • BlendMode.DARKEN
  • BlendMode.OVERLAY
  • BlendMode.ADD
  • BlendMode.SUBTRACT
  • BlendMode.INVERT
  • BlendMode.ALPHA
  • BlendMode.ERASE
  • BlendMode.SHADE

Use the following flex code to try the different blend modes:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:core="*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical" height="600" width="600" horizontalAlign="left">
 <mx:ArrayCollection id="blendModes">
  <mx:Array>
   <mx:Object label="Normal" data="normal"/>
   <mx:Object label="Layer" data="layer"/>
   <mx:Object label="Multiply" data="multiply"/>
   <mx:Object label="Screen" data="screen"/>
   <mx:Object label="Lighten" data="lighten"/>
   <mx:Object label="Darken" data="darken"/>
   <mx:Object label="Difference" data="difference"/> 
   <mx:Object label="Overlay" data="overlay"/>
   <mx:Object label="ADD" data="add"/>
   <mx:Object label="Subtract" data="subtract"/>
   <mx:Object label="Invert" data="invert"/>
   <mx:Object label="Alpha" data="alpha"/>
   <mx:Object label="Erase" data="erase"/>
  </mx:Array>
 </mx:ArrayCollection>
 <mx:Canvas height="200" width="400">
  <mx:VBox height="100" width="100" backgroundColor="{color1.selectedColor}" backgroundAlpha="1" blendMode="{blendMode1.selectedItem.data}" />
  <mx:VBox height="100" width="100" backgroundColor="{color2.selectedColor}" backgroundAlpha="1"  x="50" y="50" blendMode="{blendMode2.selectedItem.data}"/>
 </mx:Canvas>
 <mx:Panel title="Blend Modes" layout="horizontal">
  <mx:ComboBox id="blendMode1" dataProvider="{blendModes}" />
  <mx:ComboBox id="blendMode2" dataProvider="{blendModes}"/>
 </mx:Panel>
 <mx:Panel title="Colors">
  <mx:ColorPicker id="color1"/>
  <mx:ColorPicker id="color2" selectedColor="blue"/>
 </mx:Panel>
</mx:Application>




Click here to try the example.

How to use verticalCenter and horizontalCenter in a container with absolute layout

In this tutorial I will explain how you can use the verticalCenter and horizontalCenter constraints in a container with absolute layout.

At this stage you probably already figured out how to use the left, right, top, bottom constraints. But what about horizontalCenter and verticalCenter?

horizontalCenter is the distance from the center of the container in the horizontal where you want your component to appear. If you give it a positive value, the component will show up in the right half of the container. If you give it a negative value the component will appear in the left half.

<mx:Canvas width="500" height="500">
   <mx:Button horizontalCenter="200" label="Right half of the container"/>
   <mx:Button horizontalCenter="-200" label="Left half of the container"/>
</mx:Canvas>

If you try to combine horizontalCenter with left or right constraints, they will always be ignored as the horizontalCenter constraint always takes precedence.

<mx:Canvas width="500" height="500">
   <mx:Button horizontalCenter="200" left="400" label="Button 1"/>
   <mx:Button horizontalCenter="200" right="200" label="Button 2"/>
</mx:Canvas>

In the example above the two buttons will overlap because the value of the left and right coordinate is ignored.

verticalCenter is the distance from the center of the container in the vertical where you want your component to appear. If you give it a positive value, the component will show up in the bottom half of the container. If you give it a negative value the component will appear in the top half.

<mx:Canvas width="500" height="500">
   <mx:Button verticalCenter="200" label="Bottom half of the container"/>
   <mx:Button verticalCenter="-200" label="Top half of the container"/>
</mx:Canvas>

If you try to combine verticalCenter with top or bottom constraints, they will always be ignored as the verticalCenter constraint always takes precedence.

<mx:Canvas width="500" height="500">
   <mx:Button verticalCenter="200" top="400" label="Button 1"/>
   <mx:Button verticalCenter="200" bottom="200" label="Button 2"/>
</mx:Canvas>

In the example above the two buttons will overlap because the value of the top and bottom constraint is ignored.

The code below shows possible combinations of horizontalCenter and verticalCenter that you can use to layout your components:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" height="520" width="520">
<mx:Button label="Left Center" horizontalCenter="-200" verticalCenter="0">
</mx:Button>

<mx:Button label="Center" horizontalCenter="0" verticalCenter="0">
</mx:Button>

<mx:Button label="Right Center" horizontalCenter="200" verticalCenter="0">
</mx:Button>


<mx:Button label="Top Left" horizontalCenter="-200" verticalCenter="-200">
</mx:Button>

<mx:Button label="Top Center" horizontalCenter="0" verticalCenter="-200">
</mx:Button>

<mx:Button label="Top Right" horizontalCenter="200" verticalCenter="-200">
</mx:Button>

<mx:Button label="Bottom Left" horizontalCenter="-200" verticalCenter="200">
</mx:Button>

<mx:Button label="Bottom Center" horizontalCenter="0" verticalCenter="200">
</mx:Button>

<mx:Button label="Bottom Right" horizontalCenter="200" verticalCenter="200">
</mx:Button>


<mx:Button label="Ignored right constraint" right="10" horizontalCenter="-200" verticalCenter="50">
</mx:Button>

<mx:Button label="Not ignored right constraint" right="10" verticalCenter="50">
</mx:Button>

<mx:Button label="Ignored top constraint" top="10" horizontalCenter="-200" verticalCenter="-50">
</mx:Button>

<mx:Button label="Not Ignored top constraint" top="10" horizontalCenter="-200">
</mx:Button>

</mx:Application>




Click here to try the example above

Creating a countdown clock with Timer

Are you trying to find a quick and easy way of creating a countdown clock in Adobe Flex. Don’t look any further. I’ll go straight to the point:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="creationComplete()">
 <mx:Script>
 <![CDATA[
  import mx.controls.Alert;
  [Bindable]
  private var n_seconds_left:int=30;
  private var timer:Timer;
   
  /**
   * Initialises the timer
   */
  private function creationComplete():void {
  this.timer= new Timer(1000,n_seconds_left);
                      timer.addEventListener(TimerEvent.TIMER,decrementSeconds);   
  timer.start();
  }
   
  /**
   * Decrements the number of seconds left if it still is
   * bigger than 0. Otherwise stop the timer.
   * 
   */
  private function decrementSeconds(event:TimerEvent):void {
   n_seconds_left--;
  }
   
  ]]>
 </mx:Script>
 <mx:Label text="Final Countdown: {n_seconds_left}" fontSize="18"/>
</mx:Application>

The constructor for the Timer class in Adobe Flex takes two arguments:

  1. delay– Time interval in milliseconds for generating a TimerEvent. In our example, we set that value as 1000 milliseconds=1 second as you would expect for a clock.
  2. count – number of times that a TimerEvent will be generated before a TimerEvent.TIME_COMPLETE is generated. In our example it was 30. This means that our countdown clock only runs for 30 seconds. If you don’t specify this value the Timer will run forever and the TimerEvent.TIME_COMPLETE will never be dispatched

If we don’t specify the second argument when creating a Timer we still can achieve the same results:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="creationComplete()">
 <mx:Script>
  <![CDATA[
  import mx.controls.Alert;
  [Bindable]
  private var n_seconds_left:int=30;
  private var timer:Timer;
   
  /**
   * Initialises the timer
   */
  private function creationComplete():void {
   this.timer= new Timer(1000);
   timer.addEventListener(TimerEvent.TIMER, decrementSeconds); 
   timer.start();
  }
   
  /**
   * Decrements the number of seconds left if it still is
   * bigger than 0. Otherwise stop the timer.
   * 
   */
  private function decrementSeconds(event:TimerEvent):void {
     if (this.n_seconds_left>0) {
   n_seconds_left--;
     }
     else {
        timer.stop();
      }
  }
   
  ]]>
 </mx:Script>
 <mx:Label text="Final Countdown: {n_seconds_left}" fontSize="18"/>
</mx:Application>

The biggest difference to the previous code is that we use Timer.stop() to interrupt the timer to prevent another TimerEvent from being called so that our countdown clock doesn’t go below 0.


Click here to see our countdown clock in action

Adobe Flex 3 UI Components Lifecycle

In adobe flex 3 there are certain events that are called when a component is created.
These events are raised in the following order:

1) preInitialize: This event is raised when the component has just been created but none of the child components exist.

2) initialize: This event is raised after the component and all its children have been created but before any dimensions have been calculated.

3) creationComplete: This even is dispatched after all the component and its children have been created and after all the layout calculations have been performed.

4) applicationComplete: Dispatched after all the components of an application have been successfully created

Events 1) to 3) are dispatched in the order mentioned above for all visible components.
Note however that for components that are not visible on the screen by default, none of these events will be raised. This is because depending on the creation policy the hidden components might not exist yet.
For example in a TabNavigator only the components that are inside the default tab are created because they are visible. If you do change to another non-visible tab then Flex creates the components one by one. In that case the events mentioned above are raised.

We have created a simple example to demonstrate this. The DataGrid shows you a list of the events that have been raised during the lifecycle of the main application window. If you change to the second tab you will see additional entries in the list due to the creation of a Label UI component.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" preinitialize="preInitialise()" initialize="initialise()" applicationComplete="applicationComplete()" creationComplete="creationComplete()" viewSourceURL="srcview/index.html">
 <mx:Script>
  <![CDATA[
   import mx.collections.ArrayCollection;

   [Bindable]
   private var eventsCalled:ArrayCollection=new ArrayCollection();

   public function preInitialise():void {
    eventsCalled.addItem({label:"preInitialise called",event:"preInitialise"});    
   }
 
   public function initialise():void {
    eventsCalled.addItem({label:"initialise called",event:"initialise"});    
   }
   
   public function creationComplete():void {
    eventsCalled.addItem({label:"creation complete called",event:"creationComplete"});    
   }

  
   public function applicationComplete():void {
    eventsCalled.addItem("applicationComplete called"); 
   }
      
  ]]>
 </mx:Script>
<mx:TabNavigator width="400" height="400">
  <mx:Canvas label="tab1">
   <mx:DataGrid id="datagrid" dataProvider="{eventsCalled}" top="10" left="10" width="300" />
  </mx:Canvas>
  <mx:Canvas label="tab2">
   <mx:Label text="This label is created only when it is displayed" creationComplete="creationComplete();" preinitialize="preInitialise();"  initialize="initialise();" />
  </mx:Canvas>
  
 </mx:TabNavigator>
</mx:Application>

To see this example in action click: here

You can change this behavior by setting the creationPolicy attribute, for example in the application mxml node. This attribute is only valid for containers.

There are 4 possible values for this attribute:

1) creationPolicy=”auto” – the container delays creating some or all of its descendants until they are needed. This is known as deferred instantiation

2) creationPolicy=”all” – the container immediately creates all its descendants even if they are not selected.

3) creationPolicy=”queued” – all components inside a container are added to a creation queue. The application waits until all children of a container are created before moving to the next container.

4. creationPolicy=”none” – all components inside a container are never created. Whoever decides to go with this strategy needs to manually create all items inside a container.

For example to change the behavior seen above we could set the creationPolicy=”all”:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationPolicy="all" preinitialize="preInitialise()" initialize="initialise()" applicationComplete="applicationComplete()" creationComplete="creationComplete()" viewSourceURL="srcview/index.html">
...
</mx:Application>