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

Creating a File Manager in Adobe Air

In my previous post I taught you how to create a simple Adobe Air application. This time we are going to be a bit more ambitious. I want to show how easy is to create an Adobe Air application. So let’s create a simple file manager with drag & drop capabilities using out of the box Adobe Air components.

Before we go into the code lets just briefly look at the out of the box components that are available to build a file manager:

  • FileSystemDataGrid – The file system data grid is an out of the box Air component that extends DataGrid and allows you to view the contents of a directory providing information such as file name, file type, size and creation date. This will be one of the main components that we will use in this file manager.
  • FileSystemHistoryButton – The file system history button is a PopupButton that allows to go back or forward in the history of selected directories for a file system. The button click event allows to navigate to the immediate ancestor or successor directory in the history of selections. While the combo box enables jumping to any previous selected directory.
  • FileSystemTree – The file system tree is a component that displays the contents of the file system as a tree. The FileSystemTree extends the out of the box Tree component in flex and inherits all the associated behavior.

Using the components above let’s create a very simple file system navigator:

<mx:Panel layout="vertical" height="100%" title="Copy From">
	<mx:ControlBar >
			 <mx:FileSystemHistoryButton label="Back"  click="fs1.navigateBack()" enabled="{fs1.canNavigateBack}" itemClick="fs1.navigateBack(event.index)" dataProvider="{fs1.backHistory}"/>
			 <mx:FileSystemHistoryButton label="Forward" enabled="{fs1.canNavigateForward}" dataProvider="{fs1.forwardHistory}" itemClick="fs1.navigateForward(event.index)" dragStart="mx.controls.Alert.show('Drag Start')"/>
			 <mx:Button label="Up" enabled="{fs1.canNavigateUp}" click="{fs1.navigateUp()}"/>
			 <mx:Button label="Delete" enabled="{fs1.selectedItem!= null}" click="handleDelete(fs1)"/>

	</mx:ControlBar>
	<mx:FileSystemDataGrid id="fs1" directory="{File.documentsDirectory}" >

	</mx:FileSystemDataGrid>
 </mx:Panel>

We use the FileSystemHistoryButton to go back or forward in the history of previous selected directories.

Notice that there are two event handlers that we need to register. The first event handler handles the click event and is used if the user simply clicks on the button. In that case FileSystemDataGrid.navigateBack() or FileSystemDataGrid.navigateForward() is called. This causes the file system data grid to go back to the previously selected directory.
The second event handler handles itemClick event. This is the event that is raised if the user selects one of the directories in the popup menu. So in this case the event handler calls FileSystemDataGrid.navigateBack(event.index) or FileSystemDataGrid.navigateForward(event.index) where event.index is the index of the menu item that the user selected.

The FileSystemDataGrid.canNavigateBack() and FileSystemDataGrid.canNavigateForward() functions indicate whether we have reached a dead-end in the history of directory selections. In that case the Forward and Back buttons will be disabled.

The Up button when clicked navigates to the parent directory, by calling FileSystemDataGrid.navigateUp(). Similarly FileSystemDataGrid.canNavigateUp() indicates whether we have reached the root directory. In that case the Up button will be disabled.

Any decent file manager should allow you to copy, move or delete a file. Let’s add these operations to our file manager:

Deleting files

 /**
    * Checks that there is at least one item selected and asks the user whether
    * he really wants to delete the file. 
    * 
    */
    private function handleDelete(fs:FileSystemDataGrid):void {
 		mx.controls.Alert.show("Do you really want to delete this file? Please be aware that the delete actually deletes... not part of the demo.","Delete confirmation",Alert.YES|Alert.CANCEL, 
 		this, function(event:CloseEvent):void {
 			 deleteConfirmation(event, fs );
 		});
     }
 		
    /**
       * If the user really wants to delete the file we do what they ask
       * 
       */
       private function deleteConfirmation(event:CloseEvent, fs:FileSystemDataGrid):void {
 	     if (event.detail==Alert.YES) {
 			if (fs.selectedItem != null) {
 			var file:File= fs.selectedItem as File;
 			if (file.isDirectory) {
 				file.deleteDirectory(false);
 			}
 			else { 						
 				file.deleteFile();	
 			}
 			fs.refresh();
 		}
 	}	
 }

 

Deleting a file is irreversible, therefore it is always a good idea to double check that the user really wants to delete the file by using an Alert dialog box.
If the user confirms the deletion of the file then we get the current selected file by calling FileSystemDataGrid.selectedItem. A File object represents a path to a directory or to a file. By calling File.isDirectory() we know whether the file is a directory or not.

  • If the File object points to a directory we call File.deleteDirectory(false), where false indicates that we do not want to delete the directory if it contains any files. Note that this delete is done synchronously meaning that the user interface will block until the deletion is complete. If you don’t want this to happen call File.deleteDirectoryAsync() instead and register event handlers for Event.COMPLETE and IOErrorEvent.IO_ERROR
  • If the File object points to a file, in that case we call File.deleteFile() and that deletes the file.

Copying Files

  /**
    * Copies the file to a new location
    * 
    */
    private function copyFile(fileOrig:File, fileDest:File):void {
 	// once copying completes we want to refresh both file system data grids to reflect
       // any changes
       fileOrig.addEventListener(Event.COMPLETE, refreshFileSystems);

      if (!fileDest.exists) {
 		fileOrig.copyToAsync(fileDest);
 	}
 	else {
 		mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this, 
 				function(event:CloseEvent):void {
 					// overwrite file
 					if (event.detail== Alert.YES) {
 						fileOrig.copyToAsync(fileDest, true);
 					}
 					else {
 						fs1.refresh();
 						fs2.refresh();
 					}
 				});
 			}
 		}

Copying a file can be done using File.copyTo() or File.copyToAsync(). File.copyTo() copies a file from one location to another but blocks until the copying finishes. On the other hand the File.copyToAsync() method returns immediately and performs the copy in the background. But in this case one should register event handlers for Event.COMPLETE and Event.PROGRESS so that we know when the copy operation is finished.

The first argument of File.copyTo() or File.copyToAsync() is the destination directory/file to copy to. There is an optional overwrite argument that you can use to specify whether you want to overwrite the destination file if one exists. If you set it to false and a file is present an IOError exception or IOErrorEvent is thrown.

Moving Files

 	/**
 	 * Moves the file to a new location
 	 * 
 	 */
 	private function moveFile(fileOrig:File, fileDest:File):void {
  		fileOrig.addEventListener(Event.COMPLETE, refreshFileSystems);
                if (!fileDest.exists) {
 			fileOrig.moveToAsync(fileDest);
 		}
 		else {
 			mx.controls.Alert.show("Are you sure you want to replace the existing file?","Replace with new file?", Alert.YES|Alert.CANCEL, this, 
 			function(event:CloseEvent):void {
 				// overwrite file
 				if (event.detail== Alert.YES) {
 					fileOrig.moveToAsync(fileDest, true);
 				}
 				else {
 					fs1.refresh();
 					fs2.refresh();
 				}
 			});
 		}
 
 	}

Moving a file can be done using File.moveTo() or File.moveToAsync(). File.moveTo() moves a file from one location to another but blocks until the move operation is finished. On the other hand the File.moveToAsync() method returns immediately and performs the move operation in the background. But in this case one should register event handlers for Event.COMPLETE and Event.PROGRESS so that we know when the move operation is finished.

The first argument of File.moveTo() or File.moveToAsync() is the destination directory/file to move to. There is an optional overwrite argument that you can use to specify whether you want to overwrite the destination file if one exists. If you set it to false and a file is present an IOError exception or IOErrorEvent is thrown.




Click here to download the example AirFileManager

Next we want to add drag & drop support to this file system manager. So that we don’t always have to click the buttons. If you are interested in how you can add that kind of functionality then read my tutorial on Adding Drag and Drag functionality in Adobe Air

Generating a random number in actionscript 3

Generating a random number in actionscript 3 is very similar to java.
To generate a random number betwen 0 and 100:

var number:Number = Math.round(Math.random() * 100);

Where Math.random() generates a random number between 0 and 1 and Math.round(), rounds the result to the nearest integer.
If you want to generate a number within a given range [start, end]:

var number:Number =Math.round( start +  Math.random() * (end - start));