Dialing for Components

A while ago I wrote a DevNote article and a blog entry describing how you can build a component. What started out as a simple component, got increasingly more complex. To the basic component I showed how you can style and skin it and add events. All in all, it wasn’t a bad bit of writing. I hope it was helpful.

I want to offer you another look at building components. This time using Flash to make the graphic skins and MXML for the structure. This technique does use ActionScript, but it is kept to a minimum and does not use ActionScript classes.

DialDetails.gif
The Dial Component

Click here to download the components and examples: Download file


The component up for inspection is the Dial component: a round disk with a programmatically positioned needle. See the first figure. The Dial is very plain, but that is a result of my lack as an artist. I created the visual elements of the Dial – the skins – in Flash. I chose Flash for two reasons: all of the assets are stored in 1 file; the assets are scalable since they are vector graphics and not bitmaps. You can however, use any image editor and make PNGs, JPEGs, or GIFs as your assets.

To begin, I created a new Flash document and made three symbols: the Dial face, the needle, and the cap which covers the base of the needle. You could do away with the cap or even make the cap part of the needle. I made my symbols to specific sizes: the Dial face is 150×150 pixels. The cap is 25×25 pixels. The needle is 63×3 pixels and was drawn pointing to the right, so it can easily be rotated (this is the 0 degree position).

Once the assets were drawn and saved as symbols within the Flash document, I published the document to a SWF file and placed it into the same directory as the Flex component source code.

The Dial component is an MXML file. The root tag is a Canvas. I chose Canvas because I can position its children using (x,y) coordinates and because I can easily position one child on top of another; the cap has to be above the needle which is above the face.

You can make the Dial component by creating a new MXML file and set the root to be a Canvas. For its children, add the following controls:

<mx:Image id="face" x="0" y="0" source="{dialFaceSkin}" />
<mx:Image id="needle" x="75" y="75" source="{dialNeedleSkin}" />
<mx:Image id="cap" x="62.5" y="62.5" source="{dialCapSkin}" />
<mx:Text />

Notice how I explicity position the components based on their sizes. The cap for example, is at (62.5,62.5) – the center of the dial is at (75,75) and the cap is 25×25 with its center being (12.5,12.5). So the position of the cap’s upper left corner is (75-12.5,75-12.5) or (62.5,62.5). Notice that the source property for the Image tags are data bindings to the skins. You bring in the skins with a Script block:

[Embed(source="DialAssets.swf",symbol="dialFace")]
public var dialFaceSkin:String;

The Embed compile directive will cause the compiler to locate the symbol in the asset file and bring it into the final SWF. The variable below the Embed directive will be associated with the asset. Since the asset is dynamically bound to the Image, you see the dial face in the canvas.

Now you can save the Dial.mxml file and use it in a test application.

<mx:Application xmlns:mx="" xmlns:local="*">
<local:Dial id="myDial" />
</mx:Application>

Now you can write the code to position the needle. We want the Dial to have a minimum and maximum value just like other similar Flex controls (sliders and NumericStepper, for example). I wrote set and get methods for these properties. This follows the design pattern for Flex components and allows the properties to work with data binding since they will emit ChangeEvents when set.

This is the code snippet for the minimum property; the maximum and value properties are similar.

[ChangeEvent("minimumChanged")]
private var _minimum:Number;
public function set minimum(n:Number) : Void
{
_minimum = n;
dispatchEvent({type:"minimumChanged"});
invalidate();
}
public function get minimum() : Number
{
return _minimum;
}

The metadata ChangeEvent tells the compiler that this value can be used with data binding. Internally, we use _minimum to hold the minimum value as “minimum” is the name of the set function. This means you can do:

<local:Dial id="myDial" minimum="32" />

When that happens, the minimum set function is called, _minimum is set, and the change event is dispatched. So

<mx:Text text="{myDial.minimum}" />

will change with the new value.

The invalidate() call tells the Flex framework to re-draw the control. This may result in the position of the needle changing. For instance, if the previous value of minimum was 0 and is now 32, the needle will need move further clockwise.

function positionNeedle() : Void
{
var n:Number = (_value - _minimum)/(_maximum - _minimum);
var pos:Number = 135 + n*270;
needle._rotation = pos;
}

The positionNeedle method handles the draw event. Make sure you change the root tag to include this event handler: draw=”positionNeedle()”.

This method uses the current values of _minimum, _maximum, and _value to position the needle. You can see that since these values contribute to the rotation of the needle skin, you need to invoke invalidate() whenever any of these properties are changed.

That’s all there is to making this component: you create your graphic skins in Flash, use a Canvas to position the elements of the component, and use a little ActionScript to set the properties and initialize the component.

Going Further

To liven things up a little, I created a couple more skins in another Flash document (I could have used the same Flash document, but I wanted to keep the assets for different components separate).

In your test application, add another instance of the Dial, but this time change the skins:

[Embed(source="TempAssets.swf",symbol="dialFaceSkin")]
var tempFace:String;
[Embed(source="TempAssets.swf",symbol="dialNeedleSkin")]
var tempNeedle:String;

<local:Dial id="tempDial" dialFaceSkin="{tempFace}" dialNeedleSkin="{tempNeedle}" />

When you run the application, you’ll see this more colorful dial in addition to the original dial. If you think you would use these skins more than once, create a new component (see TemperatureDial.mxml), but use Dial as its root tag. You will find that all you need to do is embed the skin assets and assign them properly. By basing your new component on an existing component, you get all of that component’s features and code, plus your extensions.

One thing you should have noticed right away is that I hard-coded the positions of the children (Images and Text) based on my knowledge of the size of the graphic skins. That’s perfectly OK. But if you want real flexibility, you’ll have to calculate those values.

Conclusion

Writing specialized components for Flex does not have to be difficult. You also do not have to follow the rules precisely, but use the framework as intended and make it work for you and use as much of it as you need. While I did not make my component so it can be any size, I did make it do what I wanted and I allowed it to be customized further.

Using a graphic editor, such as Flash, allowed me to concentrate on the function of the component, rather than on its looks.