Copying Objects in AS3, part2

So, last time we looked at how to run an objects constructor to create a duplicate – which is what Adobe recommends as a replacement for duplicateMovieClip.

The problem is that even tho this solves a majority of the situations it does not let us copy an Objects state (such as a Display Objects position, graphics property or the content of an Array).

Flash copies primitive data types such as String and Int when they are assigned to a variable:

1
2
3
4
5
var one:String = "hello";
var two:String = one;
 
one = "bye";
trace(two); // "hello" (and not "bye")

However, when you assign a complex data type, such as a Vector, what you really get is a reference:

1
2
3
4
5
6
7
8
9
10
var one:Vector. <string> = new Vector.<string>()
 
one.push("something");
 
one.push("something else");
 
var two:Vector.<string> = one;
 
one.push("and another thing");
trace(two); // something, something else, and another thing

How do we get around this? We can’t get a copy of the Vector by running it’s constructor; all we would get is another blank Vector.

Copying Objects with .clone()

A common way of enabling copying on objects is to implement a clone() method on the object in question, making sure that said method writes the current objects states on a new one.

Several classes have this built in, for example: Most Events, Bitmap Data, Filters and some Components.

Copy with ObjectUtil.copy()

If you are using Flex, you have the option of using ObjectUtil’s method copy() ( mx.utils.ObjectUtils.copy() ), which returns a reference to a copy of the supplied object.

It does this by essentially copying the Object at byte-level. This is not the most efficient way to copy something, and thus it takes up some resources,

as the Object first have to be deserialized to be written to a Byte Array. Adobe has this to say:

This method is designed for copying data objects, such as elements of a collection. It is not intended for copying a UIComponent

object, such as a TextInput control. If you want to create copies of specific UIComponent objects, you can create a subclass of the component and

implement a clone() method, or other method to perform the copy.

So in other words, it’s not a silver bullet.

Copy with ByteArray.readObject / writeObject

This is a pretty sure fire way to copy something, even a deep matrix (many layered array) can be copied this way. However this method is slow, and it does not work on Display Objects. Let’s try it on our Vector:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var one:Vector.<string> = new Vector.<string>()
 
one.push("something");
 
one.push("something else");
var btArray:ByteArray = new ByteArray();
 
btArray.writeObject( one );
 
btArray.position = 0;
 
var two:String
 
two = btArray.readObject() as String;
 
one.push("and another thing");
 
trace(two); // something, something else

The problem is that when we serialize an object and write it down in byte code we loose all meta data, such as type information. So we need to know what kind of class the copyed object belongs to and coarse it. This is not a problem here, but if the Object had been deeply clustered we would have lost a lot of the time saved by this on re-setting all the associated classes within the Object. How to solve this problem is discussed by Daron Schall here

Copy DisplayObjects with Senoculars duplicateDisplayObject class

Since there is no built-in way in Flash to copy a DisplayObject with states preserved we turn to a custom class written by famous-among-flash-developers

Senocular of kirupa.com who has written the duplicateDisplayObject class:

http://www.kirupa.com/forum/showpost.php?p=1939827&postcount=172

Essentially it does things the hard way; it uses the constructor property that I discussed in my last article to make a blank copy of an item of the same class and then assigns properties of the original object to the new one (filters, eventListeners and so on).

This means that the class will not work for custom extensions of Display Objects without a small rewrite. Nor can it handle deep copying, for that you will need to run it on every reference to a Display Object inside the one you are duplicating.

Copying a MovieClip in AS3

Copying a Movie Clip in ActionScript 3. Can’t be done. Right? Wrong.

Well, duplicateMovieClip is certainly gone, but there are still ways of making copies – running an objects constructor for example.

The basic way of doing this (which you no doubt know) is running the constructor of an object directly:

1
2
3
4
5
private var _circle = new customCircleClass
 
// "copy"
 
private var _circle2 = new customCircleClass

This, however, requires you to know the name of the constructor you are running. In many cases you might not. Lets look at this likely scenario:

You are using the TileList component as a menu for a drag-and-drop application.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package examples{
 
 import flash.display.MovieClip;
 import fl.controls.TileList;
 import fl.data.DataProvider;
 import fl.events.ListEvent;
 
public class dragDropper extends MovieClip{
 
	private var _tileList: 		TileList;
 
	private var _dataProvider: 	DataProvider;
 
	public function dragDropper(){
 
 		// to be added
 
 	}
 }
 
}

Next, lets make some objects to put in our lists. This is customCircle.as :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package examples{
 
import flash.display.Sprite;
 
public class customCircle extends Sprite{
 
public function customCircle(){
 		graphics.beginFill(0x440000,1);
 		graphics.drawCircle(10,10,10);
 		graphics.endFill();
 	}
}
 
}

I’m sure you can guess the rest. Let’s continue with the constructor:

13
public function dragDropper(){
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// Create some different shapes to add to TileList
 
 var circle = new customCircle();
 
 var square = new customSquare();
 
 var triangle = new customTriangle();
 
// Set up Data Provider
 
 _dataProvider = new DataProvider();
 
 _dataProvider.addItem( {source:circle} );
 
 _dataProvider.addItem( {source:square} );
 
 _dataProvider.addItem( {source:triangle} );
 
// Create TileList and set Data Provider
 
 _tileList = new TileList();
 
 _tileList.width = 160;
 
 _tileList.height = 60;
 
 _tileList.setStyle("contentPadding", 5);
 
 _tileList.setRendererStyle("imagePadding", 5);
 
_tileList.dataProvider = _dataProvider;
 
addChild( _tileList );
 
}

Which gives us this:



Which is great. But we need it to do more than just look cool, so we add a listener:

13
_tileList.addEventListener(fl.events.ListEvent.ITEM_CLICK, listItemClick);

Now we can get to the point of all this, that is how to copy Display Objects in AS3. Tile Lists dispatches List Events when they are clicked; they are different from ordinary Events in that they carry a reference to the object that has been clicked. Many people write custom Event classes for this exact purpose, but that is another chapter.

We can now access and manipulate the clicked object:

40
41
42
43
public function listItemClick(e:ListEvent){
	addChild( e.item.source);
	e.item.source.startDrag();
}

This will make the object draggable but also removes the object from the Tile List, as it can not be a child to both the stage and the Tile List.

So were do we go from here? We can’t know which tile the user will click ahead of time. Luckily all Objects have the constructor property, which holds a reference to their Constructor:

40
41
42
43
44
public function listItemClick(e:ListEvent){
	var newCursor = new e.item.source.constructor
	addChild( newCursor );
	newCursor.startDrag();
}

Good times. But this only creates a blank instance Object you are copying. For copying objects with states preserved check back next week!
You can read the follow up here!