flex.scoutant.org


Keyword - DnD


2008/12/30

SharedObject to persist your Flash app state - A Design canvas sample

You need JavaScript enabled!

Move some figures arround, press Save, Close app, Re-open and notice how figure location is persisted... View source code [1]

Shared Object

Une application Flash a accès au file-system de façon très restreinte, via la technique des SharedObjet. A l'image des Cookies pour les applications web.

Dans le cas de l'application Flash, on dispose d'un espace de stockage, in fine, orienté objet : tout objet (ou grappe d'objet) AS3 peut être sérialisé en AMF3 pour être stocké sous forme de SharedObject. Pour que la dé-sérialisation opère correctement il convient d'enregistrer les classes, typiquement avec le meta-tag RemoteClass.

package model {
	[RemoteClass( alias="model.ImageVO")]
	public class ImageVO {
		public var source:String;
		public var x:Number;
		public var y:Number;
        }

Write

Ensuite, comme le monte la doc [2], il convient d'obtenir un SharedObject pour une nom donné, de lui adjoindre des données, et de réaliser l'écriture avec la méthode flush().

var mySO:SharedObject = SharedObject.getLocal("chessboard");
mySO.data['figure'] = new ImageVO('King.gif');
mySO.flush();

Read

Pour la lecture, c'est tout a fait similaire, sauf que l'on opèrere un down-cast pour promouvoir le typage fort.

var mySO:SharedObject = SharedObject.getLocal("chessboard");
var figure:ImageVO = mySO.data['figure'] as ImageVO;

Serializing UIComponents?

Notre exemple d'échiquier, est un simple Canvas qui contient des Images. L'exemple illustre également le déplacement d'image via Drag & Drop, explicité dans le post [3].

L'approche naïve, consiste dans un premier temps à tenter de sérialiser le Canvas ou les images. Cela ne fonctionnera pas. Un UIComponent comporte des centaines de propriétés et en générale on veut juste stocker quelques propriétés : dans notre exemple le positionnement x et y et la source pour une Image...

D'où la classe ImageVO, vue ci-dessus. C'est cette classe qui sera persistée. Il s'agit alors de prévoir l'instantiation d'une mx.controls.Image à partir de notre ImageVO. Plusieurs approches sont possibles ; on peut prévoir une sous-classe de Image qui ajoute simplement les accesseurs à la ValueObject correspondante :

public class ImageUI extends Image {
		public function get vo() : ImageVO {
			return new ImageVO( source, x, y, width, height);
		}
		public function set vo(ref:ImageVO):void {
			source = (ref!=null ? ref.source : null) ;
			width = ( ref!=null ? ref.width : 0);
			height = ( ref!=null ? ref.height : 0);
			x = ( ref!= null ? ref.x : 0);
			y = ( ref!= null ? ref.y : 0);
		}
       }

Pour bien faire, on aimerait bien ajouter un constructeur d'Image sur la base de son VO correspondant. Mais l'AS3 ne permet pas d'avoir plusieurs constructeurs, on peut en alternative opter pour une méthode statique de fabrication. Par exemple :

public static function newImage( vo:ImageVO, mouseDownCallback:Function=null) : ImageUI {
			var ref:ImageUI = new ImageUI();
			ref.vo = vo;
			return ref;
		}

Muni de cette classe l'instantiation d'un UIComponent à partir du SharedObjet correspondant est très simple. Reste simplement à voir comment se présente la persistance pour une collection d'instance.

Array of data

Comme on a affaire à une vrai sérialisation AMF3, on peut aussi sérialiser directement un tableau d'objets. Par exemple :

private function doSave():void {
            	mySO.data['figures'] = board.getChildren().map( function( item:ImageUI, index:int, arr:Array):ImageVO { 
                     return item.so
                 } );
            	mySO.flush();
            }

2008/10/25

No Move API for Flex : falling back to Drag & Drop API

Yes, i just want to let the user move an image arround its container canvas. Flex does not provide you a direct API for that. You need to fall back to Drag & Drop API. Right-clic below to see source code

You need JavaScript enabled!

Ci-dessus, vous pouvez déplacer les images qui réprésentent les pièces d'échecs : un échec et mat évident en un coup... Pour ce qui est du code Flex, commençons par le début du Drag & Drop.

Drag & Drop with Flex DnD-enabled controls

Ok, prenons les choses depuis le départ. Flex offre un bon support de Drag & Drop, ce qui permet une mise en oeuvre simple pour les composants tels List, DataGrid et Tree.

You need JavaScript enabled!
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="horizontal"> 
  <mx:Array id="fruits">['Orange', 'Apple', 'Blackberry', 'Cranberry', 'Banana']</mx:Array>
  <mx:Panel title="Available" width="50%" height="100%" >
    <mx:List dragEnabled="true" dataProvider="{fruits}" width="100%" height="100%" />
  </mx:Panel>
  <mx:Panel title="My selection" width="50%" height="100%" >
    <mx:List dropEnabled="true" width="100%" height="100%" />
  </mx:Panel>
</mx:Application>

Bref il suffit d'activer les propriétés dragEnabled et dropEnabled.

Moving an image

Flex DnD API is straitforward for those DnD-enabled controls. But in order to move arround an image, a DragManager will need to be used.

Dragging an image

Nous allons voir comment, dès qu'on sort du cadre des composants Dnd-enabled, il faut utiliser explicitement le DragManager [1]. Pour qu'une image soit éligible au Drag, on a typiquement, ceci.

<mx:Image mouseDown="dragging(event);" />
private function dragging(event:MouseEvent):void {                
  var dragInitiator:Image = event.currentTarget as Image;
  var ds:DragSource = new DragSource();
  ds.addData(dragInitiator, "img");
  DragManager.doDrag(dragInitiator, ds, event);
}

A partir de là, on peut effectivement déplacer l'image en maintenant le bouton gauche de la souris. Mais le rendu n'est pas terrible, car au moment du déplacement, notre image cible est alors simplement matérialisé par un rectangle. On peut vérifier avec l'extrait suivant :

Dragging with custom feedback proxy

Mais on peut préciser au DragManager une image à utiliser pendant le déplacement et qui sera affichée comme feedback pendant le drag. On peut valoriser le 4-ième paramètres (optionnel) de la méthode doDrag().

private function dragging(event:MouseEvent):void {                
  var dragInitiator:Image = event.currentTarget as Image;
  var ds:DragSource = new DragSource();
  ds.addData(dragInitiator, "img");
  var imageProxy:Image = new Image();
  imageProxy.source = event.currentTarget.source;
  imageProxy.width= dragInitiator.width;
  imageProxy.height= dragInitiator.height;
  DragManager.doDrag(dragInitiator, ds, event, imageProxy);
}

Et là, autant prendre l'image sur laquelle on veut précisément agir: oui, enfin..., on prendra une copie pour que ca marche.

Dropping

Reste à finaliser le déplacement de notre image, pour cela il faut que notre conteneur, ici un Canvas, accepte le Drop. Pour cela, on peut se contenter de ceci :

<mx:Canvas dragEnter="DragManager.acceptDragDrop( event.currentTarget as Canvas);"
  dragDrop="dragDropHandler(event);" />
private function dragDropHandler(event:DragEvent):void {
  var image:Image = event.dragInitiator as Image;
  var container:Canvas = event.currentTarget as Canvas;
  image.x = container.mouseX; 
  image.y = container.mouseY;
}

Là, on y est presque. Sauf que vous l'avez vu quand on lâche l'image, elle ne se pose pas à l'endroit naturelle. En fait c'est un peu se qui se passe aussi dans l'exemple de DnD d'image à la fin du billet de Xebia [2]. Le problème le voici : quand on initialise le Drag, on clique n'importe où dans l'image et quand on relâche la souris, le coin supérieur gauche de l'image prend la position de la souris. Mais pour compenser, il suffit de compléter comme suit :

<mx:Canvas dragEnter="DragManager.acceptDragDrop( event.currentTarget as Canvas);"
        mouseDown="recordPosition(event)"
        dragDrop="dragDropHandler(event);" />
private function recordPosition(event:MouseEvent):void {
  fromX = event.localX;
  fromY = event.localY;
}
private function dragDropHandler(event:DragEvent):void {
  var image:Image = event.dragInitiator as Image;
  var container:Canvas = event.currentTarget as Canvas;
  image.x = container.mouseX - fromX; 
  image.y = container.mouseY - fromY;
}

Ouf, là on y est!

Comme quoi le Drag&Drop est immédiat en Flex pour les composants List, DataGrid, Tree : activer les propriétés dragEnable et dropEnabled.

Par contre pour déplacer une image, il n'y a pas d'API direct et il faut mettre en oeuvre un Drag & Drop avec DragManager et là ce présent tutorial peut guider pour les quelques 15 lignes de code nécessaires.

Pour le code source : un simple clic droit sur l'échiquier en début de billet.