You need Flash Player 9.0.28 or greater to see some of the content inside this blog

Flex Component Architecture by example (II)

Written by Gonzalo Rodriguez Pezzi in Flex Add comments

In my last post, I explained how to create a simple component: a shape inside the blackboard. As we want to enable the user to draw diferent shapes, we will have to learn how to create the blackboard component, in wich we will be able to draw shapes using the mouse. So the blackboard component will hold some instances of the BlackboardShape component we built in the previous part of this tutorial.

Let’s go with the next part of this article

You can see the whole example working here. Press right button -> View Source to see the full source code.

MOUSE EVENTS

I assume you have already worked with Actionscript events. However, let’s explain it just in case you don’t remember.
You can listen to any events dispatched by an Actionscript object just by writing this code:

object.addEventListener (eventType, eventHandler)

The eventHandler is a function that will be executed everytime the event is dispatched.

For example:

object.addEventListener (MouseEvent.MOUSE_DOWN, mouseDownEventHandler);

private function mouseDownHandler (event : MouseEvent) : void {

}

The event object we receive as a parameter contains useful information to handle the event and perform some actions.

In our example, we will use these events:

  • MouseEvent.MOUSE_DOWN: it happens when we press the left button of the mouse
  • MouseEvent.MOUSE_UP: it happens when we release the left mouse button
  • MouseEvent.MOUSE_OUT: it is dispatched when we move the cursor out of the component
  • MouseEvent.MOUSE_MOVE: it is dispatched when we move the cursor along the component.

If we create an empty component and try to listen to mouse events, we will see that no event is dispatched. This happens because they will only be dispatched if we have drawn something inside the component. So, in order to catch the mouse events, I have drawn a transparent sprite on top of the component that will listen to the mouse events (cristal).

COMPONENT STATES

When creating our component, there are some rules that we should keep in mind:

  • Update the visualization of the component only in the updateDisplayList function.
  • Never call directly to the updateDisplayList function. We should call the invalidateDisplayList function instead.

This is a very simple component. If we create more complex components showing complex data, it will be necessary to use the commitProperties function.
The easiest way to handle the diferent states of our component is to store the current state in a variable (blackboardState).
We will use three states:

  • BASE
  • STARTING_PICTURE: the user is starting a new shape inside the blackboard
  • DRAWING: the user is drawing

Whenever we want to make a change to the component, we will change the state variable and call the invalidateDisplayList function if the visualization of the component must be refreshed.

THE CODE

Enough explanation. Let’s see the code:

package comp
{
	import flash.display.Shape;
	import flash.display.Sprite;
	import flash.events.MouseEvent;
	import flash.geom.Point;

	import mx.controls.Alert;
	import mx.controls.Button;
	import mx.core.UIComponent;

	public class Blackboard extends UIComponent
	{
		/* BLACKBOARD AVAILABLE STATES */
		private static const BASE : String = "";
		private static const STARTING_PICTURE : String = "startingPicture";
		private static const DRAWING : String = "drawing";

		// The state variable will store the current state of the variable
		private var blackboardState : String = BASE;

		// We will use a transparent Sprite that will be displayed on top of the blackboard and will
		// capture mouse events
		private var cristal : Sprite;
		// The background
		private var background : Shape;

		// Reference to the shape we are working with
		private var currentShape : BlackboardShape;
		// The location of the mouse cursor
		private var cursorPosition : Point;

		public function Blackboard()
		{
			super();
		}

		// In this method we create the children of this component and make sure everything is correctly initialized
		override protected function createChildren():void {
			cristal = new Sprite ();
			cristal.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
			cristal.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
			cristal.addEventListener(MouseEvent.MOUSE_OUT, mouseOutHandler);
			cristal.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler);

			background = new Shape ();
		}

		// When we detect a mouseDown event, it means the user is starting a new drawing
		private function mouseDownHandler (event : MouseEvent) : void {
			blackboardState = STARTING_PICTURE;
			cursorPosition = new Point (event.localX, event.localY);
			currentShape = new BlackboardShape ();
			currentShape.x = cursorPosition.x;
			currentShape.y = cursorPosition.y;
			currentShape.addPoint (cursorPosition.x - currentShape.x, cursorPosition.y - currentShape.y);
			invalidateDisplayList();
		}

		// When we capture a mouseUp, it means the user stops drawing, so my component will return to the base state
		private function mouseUpHandler (event : MouseEvent) : void {
			blackboardState = BASE;
		}

		// If the user moves the mouse out of this component, the component will return to the base state
		// and the shape the user was drawing is finished
		private function mouseOutHandler (event : MouseEvent) : void {
			blackboardState = BASE;
		}

		// If the user was drawing a shape and moves the mouse, we must register a new point in the shape
		private function mouseMoveHandler (event : MouseEvent) : void {
			if (blackboardState==DRAWING) {
				cursorPosition = new Point (event.localX, event.localY);
				currentShape.addPoint (cursorPosition.x - currentShape.x, cursorPosition.y - currentShape.y);
				invalidateDisplayList();
			}
		}

		// This method configures the minimum and default size of the component
		override protected function measure():void {
			minHeight = measuredMinHeight = 300;
			minWidth = measuredMinWidth = 400;
		}

		// This method renders changes in the visualization of the component when necessary
		override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
			background.graphics.clear();
			background.graphics.beginFill(0xFFFFFF, 1);
			background.graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
			background.graphics.endFill();
			if (!this.contains(background)) {
				this.addChild(background);
			}

			if (blackboardState==STARTING_PICTURE) {
				this.addChildAt(currentShape, this.numChildren - 1);  // We make sure not to add the shape on top of the cristal that dispatches mouseEvents
				blackboardState = DRAWING;
			}

			// Update the cristal size to capture events. Just in case the size of the component has changed, I redraw it
			cristal.graphics.clear();
			cristal.graphics.beginFill(0xFFFFFF, 0);
			cristal.graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
			cristal.graphics.endFill();
			if (!this.contains(cristal)) {
				this.addChild(cristal);
			}

		}

	}
}

You can see the whole example working here. Press right button -> View Source to see the full source code.

Leave a Reply

Original WordPress Theme & Icons by N.Design Studio, modified by Carlos Rovira
Entries RSS Comments RSS Log in