Clipboard Management using the StatefulToolkit

Tutorial Overview

This tutorial will build upon the Introduction to using the StatefulToolkit example; it will add the ability to utilize the Clipboard in our example. The example will use the ClipboardUtil, which is a utility class included in the toolkit that allows the user to copy and paste objects using the clipboard. The example application will build upon the last one; it will allow the user to position four circles, to save and load the state of the circles (i.e. where the circles are positioned) and to copy and paste the circles. The example can be seen here and source files are available.

Preparation

Before you being implementing this tutorial make sure you have the latest version of the StatefulToolkit SWC, and that you’re project is set to target Flash Player 10 (if you want to target FP9, simple replace the Vector with an array in the circle container) . This tutorial also uses Keith Peter’s Minimal Comps component set to create the buttons in the application so you may want to grab the SWC for that too, but you can use your own buttons if you prefer. Both SWC files are included in the available flex project.

Creating the Statable Object

The original circle class could be left as it is, however it would be helpful to know which circle we have selected, so we know which one we are adding to the clipboard. In order to do show this we will add two new methods to the Circle; select and deselect. Each method draws the circle in a different colour. The full contents of this class can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
package couk.markstar.examples.ui
{
	import couk.markstar.statefultoolkit.core.IStatable;
 
	import flash.display.Sprite;
 
	public class Circle extends Sprite implements IStatable
	{
		/**
		 * The constructor draws the circle with a grey outline
		 */
		public function Circle():void
		{
			super();
 
			drawCircle( 0x999999 );
		}
 
		/**
		 * Change the display of the circle to show it's selected
		 */
		public function select():void
		{
			drawCircle( 0x990000 );
		}
 
		/**
		 * Change the display of the circle to the default
		 */
		public function deselect():void
		{
			drawCircle( 0x999999 );
		}
 
		/**
		 * Retrieve the state of the circle storing the x and y properties
		 *
		 * @return The current state of the circle as an XML string
		 */
		public function retrieveState():String
		{
			var xml:String = '<object>';
			xml += '<x>' + x + '</x>';
			xml += '<y>' + y + '</y>';
			xml += '</object>';
			return xml;
		}
 
		/**
		 * Set the state of the circle by getting it's x and y values from the xml
		 *
		 * @param state The new state of the circle
		 */
		public function setState( state:XML ):void
		{
			x = Number( state.x );
			y = Number( state.y );
		}
 
		/**
		 * Used internally for creating the circles' graphics
		 * 
		 * @param lineColor The color of the line around the circle
		 */
		protected function drawCircle( lineColor:uint ):void
		{
			graphics.clear();
			graphics.lineStyle( 1, lineColor )
			graphics.beginFill( 0xFFFFFFF );
			graphics.drawCircle( 0, 0, 10 );
			graphics.endFill();
		}
	}
}

Creating the Container

Previously the circle container implemented the IStatable interface but it now implements the IStateContainer interface, because it needs to interact with the ClipboardUtil. The IStateContainer interface inherits functionality from the IStatable interface, so the circle container is still required to have the methods setState and retrieveState, but it is also required to have the methods addObjectByState, hasSelectedObject and retrieveSelectedObject.

The addObjectByState method was already implemented in the previous function, but was used internally, now it will be used by the ClipboardUtil when the user is pasting an object. In order to implement hasSelectedObject and retrieveSelectedObject we must add to the circleDownListener. After setting the _currentCircle we can call the select method of the circle to show that it has been selected. Then, before we set the _currentCircle to the one the user has mouse downed, we must deselect the old one by calling it’s deselect method. The full contents of this class can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
package couk.markstar.examples.ui
{
	import couk.markstar.statefultoolkit.core.IStatable;
	import couk.markstar.statefultoolkit.core.IStateContainer;
 
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;
 
	public class CircleContainer extends Sprite implements IStateContainer
	{
		protected var _circles:Vector.<Circle>;
 
		protected var _currentCircle:Circle;
		protected var _dragOffset:Point;
 
		/**
		 * The constructor the vector containing the circles shown in the container
		 */
		public function CircleContainer():void
		{
			_circles = new Vector.<Circle>();
			super();
		}
 
		/**
		 * Retrieve the selected object
		 *
		 * @return The selected object
		 */
		public function retrieveSelectedObject():IStatable
		{
			return _currentCircle;
		}
 
		/**
		 * Check whether the container has a selected object
		 *
		 * @return Boolean denoting if the container has a selected object
		 */
		public function hasSelectedObject():Boolean
		{
			return( _currentCircle ) ? true : false;
		}
 
		/**
		 * Retrieve the state of the container and all contained circles
		 *
		 * @return The current state of the container, including the state's of all circles
		 */
		public function retrieveState():String
		{
			var xml:String = "<container>";
 
			for( var i:uint = 0; i < _circles.length; i++ )
			{
				xml += _circles[ i ].retrieveState();
			}
			xml += "</container>";
			return xml;
		}
 
		/**
		 * Add an object to the container by it's state.
		 *
		 * @param state The state of the object to add, in this case the circle
		 */
		public function addObjectByState( state:XML ):void
		{
			var circle:Circle = new Circle();
			circle.addEventListener( MouseEvent.MOUSE_DOWN, circleDownListener );
			circle.setState( state );
			addChild( circle );
			_circles[ _circles.length ] = circle;
		}
 
		/**
		 * Set the state of the container by adding all circles contained within the state. The container will be cleared first.
		 *
		 * @param state The new state of the container
		 */
		public function setState( state:XML ):void
		{
			clearContainer();
 
			for( var i:uint = 0; i < state.object.length(); i++ )
			{
				addObjectByState( XML( state.object[ i ] ) );
			}
		}
 
		/**
		 * Clear the container of all circles
		 *
		 */
		protected function clearContainer():void
		{
			for( var i:uint = 0; i < _circles.length; i++ )
			{
				removeChild( _circles[ i ] );
			}
			_circles.splice( 0, _circles.length );
		}
 
		/*
		 * The following methods are all event listeners to enable the circles to be dragged
		 */
		protected function circleDownListener( e:MouseEvent ):void
		{
			// Deselect the current circle (if there is one)
			if( _currentCircle )
			{
				_currentCircle.deselect();
			}
 
			_currentCircle = Circle( e.currentTarget );
			_currentCircle.select();
 
			stage.addEventListener( MouseEvent.MOUSE_MOVE, circleMouseMoveListener );
			stage.addEventListener( MouseEvent.MOUSE_UP, circleMouseUpListener );
 
			_dragOffset = new Point( _currentCircle.x - mouseX, _currentCircle.y - mouseY );
		}
 
		protected function circleMouseMoveListener( e:MouseEvent ):void
		{
			_currentCircle.x = mouseX + _dragOffset.x;
			_currentCircle.y = mouseY + _dragOffset.y;
			e.updateAfterEvent();
		}
 
		protected function circleMouseUpListener( e:MouseEvent ):void
		{
			stage.removeEventListener( MouseEvent.MOUSE_MOVE, circleMouseMoveListener );
			stage.removeEventListener( MouseEvent.MOUSE_UP, circleMouseUpListener );
		}
	}
}

Creating the Document Class

The document class for this application very similar to the previous one. It was already responsible for creating the save and load buttons, creating the circle container, registering the circle container with the FileUtil and initialising the container with a default state. The only thing we need to add is the registration of the circle container with the ClipboardUtil. The full contents of this class can be seen below.

1
2
3
4
5
6
7
8
9
10
11
12
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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
package {
	import com.bit101.components.PushButton;
 
	import couk.markstar.examples.ui.CircleContainer;
	import couk.markstar.statefultoolkit.utils.ClipboardUtil;
	import couk.markstar.statefultoolkit.utils.FileUtil;
	import couk.markstar.statefultoolkit.utils.IClipboardUtil;
	import couk.markstar.statefultoolkit.utils.IFileUtil;
 
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.text.TextField;
	import flash.text.TextFieldType;
 
	[SWF( backgroundColor='#F3F3F3',frameRate='30',width='592',height='300' )]
	public class ClipboardExample extends Sprite
	{
		protected var _saveButton:PushButton;
		protected var _loadButton:PushButton;
 
		protected var _circleContainer:CircleContainer;
		protected var _fileUtil:IFileUtil;
		protected var _clipboardUtil:IClipboardUtil;
 
		/**
		 * Create the save and load buttons to save and load the state. Then create the circle container and register it with the FileUtil.
		 * By registering the container with the FileUtil it means the FileUtil knows which statable object to retrieve and set the state on
		 * when save and load are executed.
		 * Register the container with the ClipboardUtil so it can capture keyboard shortcuts.
		 */
 
		public function ClipboardExample():void
		{
			// PushButton( parent, x, y, label, MouseEvent.CLICK event listener )
			_saveButton = new PushButton( this, 196, 275, "Save", saveClickListener );
			_loadButton = new PushButton( this, 295, 275, "Load", loadClickListener );
 
			_circleContainer = new CircleContainer();
 
			_fileUtil = new FileUtil();
			_fileUtil.registerObject( _circleContainer );
 
			// ClipboardUtil requires reference to the stage so it can manage the keyboard shortcuts
			_clipboardUtil = new ClipboardUtil( stage );
			_clipboardUtil.registerContainer( _circleContainer );
 
			addChild( _circleContainer );
 
			initDefaultState();
		}
 
		protected function saveClickListener( e:MouseEvent ):void
		{
			_fileUtil.save();
		}
 
		protected function loadClickListener( e:MouseEvent ):void
		{
			_fileUtil.load();
		}
 
		/**
		 * Initialise the circle container with four circles
		 */
		protected function initDefaultState():void
		{
			var defaultState:String = '<container>';
			defaultState += '<object><x>196</x><y>100</y></object>';
			defaultState += '<object><x>196</x><y>200</y></object>';
			defaultState += '<object><x>396</x><y>100</y></object>';
			defaultState += '<object><x>396</x><y>200</y></object>';
			defaultState += '</container>';
 
			_circleContainer.setState( new XML( defaultState ) );
		}
	}
}

Testing the application

You will now be able to run the application and test that the state of the application can be saved and loaded and that you can copy and paste circles. The structure of the project can be seen below. If you do have any questions about the tutorial, or constructive criticism on the toolkit, don’t hesitate leave a comment below.

ClipboardExample Project Structure

Compiled Application

Once you have compiled the application, you should get the same output as below. Try copying and pasting the circles, you will also be able to save and load the state like before.

Source Files

Source files are available from GitHub, under the heading ClipboardExample.

Posted by Mark on February 16th, 2010
 

Leave a Reply