FLEXYGEN

Adobe Flex and AIR development

Immutability and Binding

An application I am developing makes heavy use of date ranges. I created a a simple DateRange class containing start and end dates. Besides being stored in models, date ranges are used in events and passed around to controller and service methods. I didn’t like the idea of using a relatively heavyweight subclass of EventDispatcher for these purposes, so I chose not to make DateRange bindable. But I went a step further and made it an immutable class, with no setters. This won’t be everyone’s cup of tea, but is common in the Java world, where immutable objects can help out with thread-safety, obviously not an issue in Flex.

At first, I created a companion wrapper class BindableDateRange for use in models, but it became rather messy. In the course of re-factoring it away, I ran into the well-known warning:

warning: unable to bind to property ’start’ on class ‘DateRange’ (class is not an IEventDispatcher)

In one MXML component, I was binding to the start property:

<mx:DateField selectedDate=”{range.start}” change=”useDate(event)” />

But, but, but “range” is what changes! Why can’t I bind to “range.start”? Well, you can. Once I got rid of a few unrelated bugs, I was able to verify that the binding was working (i.e. the DateField updated when the range changed. To get rid of the warning, I had to create [Bindable] getters for “start” and “end” within the component itself, and replace the expression with {this.start}. Unfortunate, but there you have it.

Update: It appears that another way to get rid of the warning is to add [Bindable(event="startChanged"] to the  getter for the “start” property, without necessarily deriving from EventDispatcher (which is unnecessary since events aren’t dispatched for this read-only property).

May 21, 2008 Posted by richard | Uncategorized | | No Comments Yet

Shapes and Sprites

In recent weeks I’ve been diving into the world of Flex custom components, and the drawing APIs. Many years ago I worked on the Transform Panel in InDesign, but it’s been a while since I did any graphics-intensive programming. It’s great fun building slick user experiences with the standard Flex components, but I’d also love to work on a juicy RIA like Buzzword or SproutBuilder.

One of my personal projects is a board game. I have the board (a subclass of UIComponent) rendering a collection of tiles, which are subclasses of Sprite. It was necessary (I think) to use Sprite, because each tile has a child text label and the underlying Graphics object doesn’t have any sort of drawText() method. The next step was to look at handling selection and dragging of tiles.

Chet Haase recently joined the Flex SDK team and posted a drawing sample called TopDrawer on his blog. This was most helpful! TopDrawer has an ArtCanvas class (also a UIComponent), and the drawn shapes (rectangles, lines etc.) are represented by subclasses of Shape added as children of the canvas.

I cheerfully copied a couple of lines from the ArtCanvas event handler for MouseDown events:

            	var selectPoint:Point = localToGlobal(new Point(event.localX, event.localY));
                for each (var shape:ArtShape in shapes)
                {
                    if (shape.hitTestPoint(selectPoint.x, selectPoint.y, true))
                    {
                        selectShape(shape);
                        break;
                    }
                }

In my game, the hitTestPoint() call did not succeed when I clicked on a tile (a Sprite) but did, if I changed it to a Shape. The simple fix was to call event.target.localToGlobal(). So one difference between Sprites and Shapes is that a child Sprite may be the “target” property of a MouseEvent, but a Shape won’t. The code worked in TopDrawer because the the shapes are Shapes, and the localX and localY properties were already in the coordinate system of the ArtCanvas.

Before I figured this out, I tried to create an AS3-only test case with a Shape child and Sprite child. The mouse event handler wasn’t even reached. I learned (via O’Reiily’s AS3 Cookbook) that the main application class does not receive mouse events, though it does receive enterFrame, for example. So here’s a one-file non-object-oriented test that does work as expected. You can pop this in an AS3-only FlexBuilder project and run it in the debugger. I hope this is helpful to someone.


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

    // Testing Hit detection

    public class TestHitDetection extends Sprite
    {
        private var _rootSprite:Sprite;
        private var _circleSprite:Sprite;
        private var _circleShape:Shape; 

        public function TestHitDetection()
        {
            super();

            _rootSprite = new Sprite(); // This is necessary because the main application class does not receive mouse events
            _rootSprite.x = 50;
            _rootSprite.y = 50;

            _rootSprite.graphics.beginFill(0xDDDDDD);
            _rootSprite.graphics.drawRect(0,0,500,500);
            _rootSprite.graphics.endFill();

            _circleSprite = new Sprite();
            _circleSprite.graphics.beginFill(0x00FF00);
            _circleSprite.graphics.drawCircle(60,60,50);
            _circleSprite.graphics.endFill();
            _circleSprite.x = 10;
            _circleSprite.y = 10;

            _rootSprite.addChild(_circleSprite);

            _circleShape = new Shape();
            _circleShape.graphics.beginFill(0xFF0000);
            _circleShape.graphics.drawCircle(0,0,75);
            _circleShape.graphics.endFill();
            _circleShape.x = 200;
            _circleShape.y = 200;

            _rootSprite.addChild(_circleShape);

            this.addChild(_rootSprite);

            _rootSprite.addEventListener(MouseEvent.MOUSE_DOWN, handleMouseDown);
        }

        private function handleMouseDown(event:MouseEvent):void{
            trace("mousedown");
            var selectPoint:Point = event.target.localToGlobal(new Point(event.localX, event.localY));
            if (_circleSprite.hitTestPoint(selectPoint.x, selectPoint.y, true)) {
                trace("hit sprite");
            }
            if (_circleShape.hitTestPoint(selectPoint.x, selectPoint.y, true)) {
                trace("hit shape");
            }
        }
    }
}

March 27, 2008 Posted by richard | Uncategorized | | No Comments Yet

Simple MVC Sample

I decided not to be a perfectionist, and posted the Flex sample code I wrote for my presentation to the Seattle Flex User Group last month. I hope someone finds it helpful.

It’s the beginnings of a very simple slide show editor/viewer, using the RichTextEditor and TextArea controls. I actually ran into an issue with RichTextEditor when the htmlText property is bound to external data. Vote here.

But the usefulness of the application (which has no server component currently) is not the point. Much like Joe Berkovitz and James Echmalian do here, my goal was to provide readable source code showing the MVC pattern in Flex, without using Cairngorm or any other frameworks .

You can run the application here, and right-click within it to view the source.

February 7, 2008 Posted by richard | Uncategorized | | 1 Comment

SeaFlex

On Thursday, January 10th, I’ll be presenting at the Seattle Flex User Group. I’m working  on a little sample application, and will use it to illustrate the MVC design pattern. Details of the meeting are here.

January 4, 2008 Posted by richard | Uncategorized | | No Comments Yet