The advent of new client-server models and the demand for more complex UIs have made it imperative for even Web-based products to feature richer and more responsive application designs. These new kinds of applications, often referred to as Rich Internet Applications (RIAs), aim to bring many of the features and benefits of traditional desktop applications to their Web-based counterparts. Adobe Flex is at the forefront of those tools that help you create RIAs, whose features often include rich charting, drill-down animations, 3D effects, and a new level of responsive user interaction. Such features are especially important in the burgeoning areas of business intelligence (BI) and Web 2.0 content creation and delivery. Core Web 2.0 requirements typically include asynchronous processing, doing away with the request-response paradigm, content aggregation, and so on—things that applications like Flex can help you do with less effort and in less time.
Flex ships with many useful and robust components, but things get more difficult when you need to step outside the narrow bounds of what Flex provides for you and create domain-specific functionality. This introductory article provides an in-depth look at the architecture of Flex's rendering engine, detailing Flex's core benefits, walking you through the process of incorporating Flex components into your RIAs and explaining what you need to know to create new functionality from scratch. Specifically, I'll give you a guided tour of Flex and its capabilities. I'll walk you through the similarities between Flex and the Java™ programming language and then discuss the life cycle of a Flex component from its rendering engine's perspective. Along the way, I'll drill down on the details required to extend and build Flex components, including a careful look at a Flex component's life cycle and the subtle details related to a Flex component's commit, measure, and layout phases. There are many things that Flex components take care of for you automatically, but stepping outside the pre-built components can quickly leave you feeling a bit lost. I'll explain what some of these Flex components are doing for you behind the scenes, so you can know what you need to do when you're responsible for providing these behaviors.
Web development standards changing
Flex and competing technologies such as JavaFX, Ajax, and Silverlight are pioneering today's changing approaches to Web development and have played a significant role in changing how users like to visualize their data. Flex and its rivals are relatively nascent technologies and are still in the process of overcoming issues such as portability across browsers, security, limits on client-side caching, and so on.
One of the most significant competing technologies to Flex is Ajax. Their capabilities overlap some, but they also fill substantially different niches. Flex excels at targeting rich and complicated graphical structures, while Ajax is used primarily for text-based content. Ajax works closely with HTML, and its data is transferred in XML (or JSON) format, while Flex can support XML, JSON, and even a binary format of data transfer. The fact that Flex supports binary data transfer probably puts it far ahead of Ajax in terms of data transfer rates and compactness.
Flex delivers many significant benefits that are worth noting. Many (but by no means all) of Flex's most significant benefits derive from the fact that it is built on top of and extends Adobe's ubiquitous Flash Player. Key benefits of Flex include:
- Complete browser portability. Any browser that supports Flash Player—and that includes almost every browser—also supports Flex and its scripting language, ActionScript (AS). This is in distinct contrast to Ajax, which is affected by the incompatible implementations of JavaScript implemented in different browsers.
- Consistent look-and-feel. Flash Player is well known for delivering the same look-and-feel to all operating systems and browsers. Flex utilizes the Flash Player engine, so it benefits from that same consistent look-and-feel.
- Robust security. Flex leverages the highly tested Flash Player security model.
- Rich UI. Flex benefits from Halo Skins, gradient fills, vector graphics, and other Flash Player features.
- Scalable vector graphics (SVGs). Flex stands out from most other RIA-based technologies because it supports vector-based drawing and direct embedding of SVG mark-up files. SVG-based images look equally good at any resolution a given browser supports. This is in distinct contrast to bitmap-based images, which can degrade significantly as you scale up a given image's size.
- Asynchronous request/response model. Flex offers complete support for asynchronous processing of user requests. Asynchronous processing enables a Web site to break away from the page-centric model, where every user request spawns a time-consuming page refresh.
- Binary data communication. Flex offers complete support for binary data transfer between a Flex client and any back-end server. You can send this data using Abode's proprietary Action Message Format (AMF) (now with an open source specification) or any other format you might want to leverage or develop in-house. Flex even supports opening binary sockets from client-to-server, which enables you to achieve "real" data push. Note that this feature might not be able to use a browser's encryption facilities such as Secure Socket Layer (SSL).
- Runtime shared libraries (RSLs) and modularization. Flex's support for these features ensures that you can load modules dynamically, which means you can add new features to an already running application and even make use of RSLs loaded by other Flex applications running on the same client machine. This can help you reduce the start-up time of your application's features because it shrinks the size of the initial binary.
- Client-side caching. Flex offers superb support for client-side caching. With the user's permission, a Flex application can cache unlimited data on the client side, which reduces network round trips if the same data is requested again in the same or future sessions. You can cache any kind of data, including full object graphs, custom classes, maps, and arrays. This support is significantly more advanced than HTML cookies that allow an application to store only String-name value pairs and limit even those to a general restriction of four KBs per Web site.
- Cross-browser communication. Flex supports communication between applications running in the same type of browser, between different tabs of the same browser, and even between applications running in different browsers on a user's machine. This feature means many applications can share data among themselves, enabling an extremely rich end-user experience.
- Streaming. Flex offers excellent support for streaming binary data. Streaming can be a life saver for data-heavy applications that need to transfer large amounts of data to the end user because this approach lets an application display data in parts to the end user as soon as the data arrives.
- Strong back-end connectivity. From its inception, Flex has featured excellent support for popular back-end technologies such as the Java Platform Enterprise Edition, the Microsoft .NET platform, Cold Fusion, and PHP. This connectivity support has helped fuel the rate of Flex adoption for the client-side layer.
- Rich Framework. Flex offers a robust framework for component development and contains many off-the-shelf components already built-in for the developer's convenience. This facilitates rapid development of projects and timely deliveries.
- Debugging and Editor Support. Adobe has made an extremely smart decision by developing a robust Eclipse-based editor called Flex Builder, which greatly simplifies developing and debugging Flex applications.
This combination of features makes Flex a compelling choice for creating UIs now and for the foreseeable future. Another big plus for developers using Flex: You can create some interesting solutions aggregating and reusing already built components, which fits well within the spirit of Web 2.0. However, things get more difficult if you want to build new custom components from scratch. The power that Flex provides might inspire you to want to create rich, beautiful components that have thus far been solely the province of desktop applications you build with libraries such as openGL. Think about the possibilities of writing a gaming engine on the Web that builds on the concepts of the Electronic Arts racing game, Need for Speed. Or you might want to build an ISP topology with live views of data flows and link usages. I think you will see applications like these and many more in Flex's future, but the market and toolset aren't quite there yet. In any case, enterprise businesses have a lot riding on their Web-based UIs, and they can benefit tremendously from the power of Flex.
Similarities to the Java programming language
Adobe built Flex on top of its Flash Player, which is a proven technology for rendering rich UIs. Flash Player as it exists now is well suited for graphics designers, simplifying how you create MovieClips, time lines, banner ads, and so on. However, Flash Player applications aren't so appealing for enterprise developers who are accustomed to developing applications with object-oriented design and components that extend other components. On the other hand, Flash Player's native language is ActionScript, an ECMA-compliant scripting language that resembles JavaScript, and designers tend to be more attached to XML-tag-based languages. Adobe noticed this gap quickly and bridged it by introducing MXML. Adobe's approach with Flex resembles the approach Sun™ took with Servlets and JSP technologies. Code written in MXML gets translated to ActionScript code, which in turn gets compiled into something called Action Byte Code (ABC)—not unlike what happens with the Java Byte Code process. ABC is packed into the binary SWF format, which is delivered when users request it over a Web browser. These SWF-based files can run locally as well as in a stand-alone Flash Player plug-in. However, the Flash Security model doesn't allow local files to access network resources and vice versa.
This security precaution means an application delivered over
a network cannot access a local client's file system—a
capability that garnered Java Applets a significant amount of notoriety. Just
as a JVM requires a main method, a Flex application
requires a component that extends mx.core.Application as the entry point. Like a Java application
running in a JVM, the SWF file eventually runs in a virtual machine called
ActionScript Virtual Machine (AVM). Table 1 displays a short comparison of the
similarities of the virtual machines for the Java programming language and
Flex.
Table 1. Java versus Flex VMs
| Flex | Java |
|---|---|
| MXML tags and components | JSP tags and components |
| Translated as Action Script Files | Translated as Servlets Classes |
| Compiling to ABC (ActionScript Byte Code) | Compiled to JBC (Java Byte Code) |
| End result is a .swf file | End result is a .class file |
| Runs in ActionScript Virtual Machine | Runs in Java Virtual Machine |
Flex includes too many classes to try to cover them all in the space of a single article. However, it is instructive to take a closer look at a few of them for the insight they offer into the Flex object model. Figure 1 shows the core class hierarchy:
Figure 1. Flex's core class hierarchy
The top-level class is DisplayObject, which Flex
adds to the Stage object, or the universal display list, of Flash Player.
InteractiveObject handles user interaction,
including keyboard and mouse events. DisplayObjectContainer lets you add and move around child
components to any desired location within bounds. Sprite and FlexSprite are the base
classes that don't require a time line, and you can extend these to write your own
custom classes. However, the real hero in this set of classes is the UIComponent class, which provides the basic framework
you need to write custom components. It is this class that you typically
extend to write custom components, and this article's sample project extends
only from this class.
Flex adds all components you create as children (and grandchildren) of a single universal display list that is ultimately handled by Flash Player. Flex's architecture hides these elaborate details from you, but it does give you hooks into event listeners that let you process events at an advanced stage. Flash Player's event dispatch mechanism is robust, and it can handle tens of thousands of events.
Building a simple Flex component
So far, I've covered the main benefits Flex brings to Web development, its basic architecture, and some of its core classes. But all of that is only a prelude to what truly matters as a developer: writing code and building solutions. Creating a simple component in Flex is remarkably easy. Assume you want to create a pair of text-input boxes, where typing text into either input box displays the text the user enters in both text boxes. You can create such a component without writing a single line of event-handling code. Your simple UI looks like Figure 2.
Figure 2. Two interactive text boxes

Listing 1 displays the handful of lines of code that create this simple application:
Listing 1. Create a simple Flex UI
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
<mx:TextInput id="box1" text="{box2.text"}>
<mx:TextInput id="box2" text={"box1.text}
</mx:Application>
|
This
code is as simple (and clean) as code gets, but don't be fooled: Quite a bit
happens in these few lines. Feel free to compile this code with the compiler
flag keep-generated-actionscript=true if you want
to see all the things that Flex does on your behalf. The Application container (look at the mx:Application tag) and the
TextInput control hide a fair amount of complex
code from you. This is fine as long as your boss is happy with you extending,
reusing, and aggregating already existing Flex components. However, things get
more complicated quickly once you start writing your own components from
scratch. Building custom components highlights just how much Flex does for you
automatically; many of the Flex features you've come to appreciate don't exist
in your bare-bones Flex component.
Reasons to create custom components
As Flex matures, I believe you'll see more developers begin writing their own custom Flex components for two reasons:
- Most Flex components appeal to a broad, general audience, and existing controls might not meet your specific requirements. Skinning and extending existing components can help you fill in some gaps, but it becomes imperative that you write your own component if you want to affect a fundamental change in a given component's behavior.
- Many new visualization paradigms require that you present data in novel formats that are either under-supported or don't exist yet. You'll need to supply that functionality yourself if you hope to take advantage of these new formats in your applications.
Understanding the life cycle of Flex components is essential if
you want to build your own custom components. The work horse that drives the
Flex life cycle is LayoutManager. Consider this
excerpt from the Flex APIs:
"LayoutManager is the engine behind Flex's measurement and layout strategy. Layout is performed in three phases: commit, measurement, and layout."
The first phase, commit, occurs when Flex adds a component
to the main display list (hereafter referred to as "stage") using addChild() or one of its variants in the DisplayObject
class. Note that Flex doesn't add a component directly to this list; several
intermediate steps occur before that happens. First, Flex validates all the
properties of the component. LayoutManager calls
validateProperties() on the parent object and then
each of the child objects iteratively. This is called top-down
ordering. Should there be "dirty" properties, LayoutManager allows you to commit these before validation occurs.
You do this by calling the commitProperties()
protected method in UIComponent. A custom component
must override this method to commit properties before validation happens.
Examining the Label control can help you appreciate
better what's going on in this case. A label is a text field that renders one
line of text in an application. You're able to change several values in a
label, including its font, font size, font color, font family, its text, and
so on. Making any font-related changes in a label requires that you provide a
new font context and a new UITextField that
correspond to your new font; this requirement helps ensure that your new font
is rendered correctly.
How Flex handles commit changes
Now consider what happens when an application
changes a label's font. Keep in mind that Flex's UI relies on asynchronous
processing, so any changes made might not take effect immediately. When you
change a font, you can create a new UITextField at
the same time. Or, you can store the new font context and a flag indicating
that fonts have changed. The next time the component is rendered (typically on
next screen refresh), you can apply the font changes then. Simply check
whether the flag indicating a change in font status has changed and then
create the corresponding UITextField at that time.
It's possible for these properties to change multiple times between two screen
refreshes, which makes it inefficient to process such changes immediately. A
better strategy is to wait until the time comes for the next screen refresh, and
apply the changes then. Note that the end user doesn't notice anything because
the screen doesn't refresh any more often than that. This is a typical use
case for any asynchronous, event-driven UI. The application stores the changes
that need to be applied, queues them up, and, when the appropriate time comes,
applies those changes. You might wonder how you know when the appropriate time
is at hand. That is precisely what LayoutManager
handles. When you receive a callback to a component's commitProperties(), you can be sure that this is the FINAL call
before the component is rendered again; all other changes to properties
after this call won't appear in the imminent screen refresh. Those
changes must wait for another screen refresh and another call to commitProperties().
All that's fine, but you
might wonder how you tell LayoutManager to perform
validation against a component. Assume someone changed the font of the label,
that you stored the new font and the related flag, and now you want LayoutManager to invoke the validation phase again.
You do this by invoking a method called invalidateProperties() for the component in question. Now let's look at the code
required to change the text of a label. Listing 2 shows you how to handle the
validation phase (with some code omitted for
clarity).
Listing 2. Handle the validation phase
public function set
text(value:String): void {
_text = value;
textChanged = true
invalidateProperties();
}
|
Note
that _text is the field where you temporarily store the new text value,
while textChanged is the flag that you set to mark this change. Also note
the call you make to invalidateProperties() to
inform the LayoutManager to begin validation phase.
Listing 3 shows the corresponding code snippet in the commitProperties() of the Label class
(note that I've omitted some code for the sake of
clarity):
Listing 3. Use
commitProperties
override protected function
commitProperties():void{
if (textChanged) {
textField.text = _text;
textChanged = false
}
|
This
overridden method illustrates how, if the flag is marked true, the real UITextField (textField) is updated, and the flag is
reset to false for future markings. This is a critical point to understand if
you want to use Flex to create your own controls. The validation phase covered
in Listing 2 and Listing 3 represents one of the three fundamental
pillars of the Flex rendering engine. It is also a widely ignored phase, and
it's possible you will see many advanced components written that never touch
on the validation phase. This neglect might be partly an efficiency
consideration. It is likely that nothing will go wrong if you "apply" the
changes right at the point when they are made, but it's important to
understand that your changes could be unintentionally and unnecessarily
applied multiple times, causing a small (or possibly large) performance issue
in your Flex applications.
Developers typically write components in two contexts. In the first context, you write a component that is tightly bound to your application's use case. Often, this kind of app isn't general enough for reuse, and it's okay to suffer a little efficiency loss here and there instead of making your component overly complicated. In the second context, you write a component for use at the framework level or by other developers. In the second context, it pays to make your component as efficient as possible. Taking care of validation phase probably belongs to the second context.
Step through the measurement phase
The measurement phase is the another
important Flex phase, one that is more commonly used than the commit phase.
Once a component has been validated, LayoutManager
proceeds to measure that component so it can display it in a particular
context. For example, LayoutManager might display
it as the child of some container. You need the measurement phase because it
helps LayoutManager decide whether there should be
scroll bars on the parent container. Some components can resize themselves
depending on how much space their children require to be properly visible.
Consider the example of a VBox, which is a container that lays out its
children vertically, one below the other. If you don't assign height or width values to
your VBox, then it resizes every time you add a child to it. Its height is sufficient to contain the sizes of all the
vertically laid children, unless you specify some constraints that make this
impossible. Flex includes a protected method called measure() that you can use to measure your components. LayoutManager calls this method at the appropriate
time (generally before next screen refresh), at which point you need to
measure your component and set the height and width values using the measuredWidth and measuredHeight. The
parent container then uses these properties to measure itself.
In contrast to
the validation phase, the measurement phase must happen in bottom-up fashion.
This is because the children must be measured before the parent can be
measured. This ensures that when the parent container receives a call for a
measurement, its children will have been measured already. So far, so good.
But now assume you specify a component's height and
width explicitly. Flex assigns these values
stored as the explicitWidth or explicitHeight properties. Flex respects the
width and height
values that you specify in such a case. However, sometimes setting explicit
dimensions might not be wise for a container because you might not be able to
foresee the combined size of all a container's children. In such a case, Flex
adds scroll bars to a container for the children that go beyond the specified
boundaries. Note that these scroll bars appear only for those containers that
extend the Container class. For other controls,
scroll bars might appear or Flex might chop the content area (a process called
content clipping) to maintain the dimensions you
specify.
Set properties values explicitly
Setting width and height values explicitly leads to an interesting
issue. If you're writing a component that's going to contain other components
and you need to measure the container's size, it would help to know the width and height of each
child. You might wonder how you can know whether a particular component's
dimensions have been specified explicitly or have been measured by
overriding the measured method. Recall that measured dimensions are contained
in properties like measuredWidth and measuredHeight, while explicitly defined
dimensions are in explicitWidth and explicitHeight. Only one pair of
measurements will contain real values, while the other pair will contain
Not-a-Numbers (NaNs). Flex solves this riddle by providing two methods: getExplicitOrMeasuredWidth() and getExplicitOrMeasuredHeight(). You need to call these methods only,
and you don't need to worry about whether the height and width values have been measured or
set explicitly. Once a component has been measured, you need to call the setActualSize() method, which sets the width and height
properties. These properties get set anyway if the height and width values were set
explicitly. Listing 4 illustrates how straightforward it is to create a
text-input component with explicit width and height values. Listing 4 accepts
four properties:
Listing 4. Input
width and height <mx:TextInput width="200" height="45"/> |
Four
properties are set in this case: width, height, explicitWidth,
and explicitHeight. Note that measuredWidth and measuredHeight remain
as NaNs.
Now consider Listing 5, where you don't set any explicit height or width
values:
Listing 5. Let the component measure
<mx:TextInput />
|
In
this example, the component itself provides the measurement. Here, as in other
areas of the Flex framework, "default" width and
height values get assigned as measuredWidth and measuredHeight. The explicit values remain as NaNs. In any case,
getExplicitOrMeasuredWidth() and getExplicitOrMeasuredHeight() return the correct
values. In a similar vein, Flex sets minimum dimensions for explicitMinWidth or measuredMaxWidth, which provide min and max bounds for a component.
Scroll bars appear to show the content that's not visible with the current
view area once the size of a component grows bigger than maxWidth. Note that LayoutManager makes
no calls to measure() when setting explicit
dimensions. That makes sense because components don't need to be measured
again once you define the measurements explicitly.
The call to
invalidateSize() tells LayoutManager to initiate
the measurement phase. LayoutManager keeps three
separate queues: invalidatePropertiesQueue, invalidateSizeQueue, and invalidateDisplayListQueue. The queues correspond to components
that call invalidateProperties(), invalidateSize(), and invalidateDisplayList() at some point of time in their life cycles.
LayoutManager then processes each component from
each of these queues and calls the
validateProperties(), validateSize(), and
validateDisplayList() methods on each of the
components. The default implementation of these methods in UIComponent then calls commitProperties(), measure(), and updateDisplayList(). You can override each of these
methods to write custom logic.
Measuring a component can also require
measuring text. However, text behaves differently than other controls.
Measuring text requires that you consider things such as ascent, descent,
leading, gutter clearance, and so on. Fortunately, Flex provides a utility to
simplify this process. UIComponent exposes methods
called measureText() and measureHtmlText() to help you measure text controls. Listing 6
shows you how to measure a text
field:
Listing 6. Measure a text field
package components {
import flash.text.TextLineMetrics;
import mx.core.UIComponent;
public class MyLabel extends UIComponent {
private var _text : String;
public function set text(value :
String) : void {
_text = value;
invalidateSize()
}
public function get text() : String {
return _text
}
override protected function measure(): void {
if (!_text) return super.measure();
//measure text here!
var textLineMetrics : TextLineMetrics =
super.measureText(_text);
super.setActualSize(textLineMetrics.width,
textLineMetrics.height);
}
|
This
code creates a simple MyLabel class that extends
UIComponent, so you can concentrate solely on
the measurement phase. Note that you call
invalidateSize() when text is set, which signals LayoutManager to add your component to its
invalidateSizeQueue() during the next screen refresh. LayoutManager then calls the validateSize() method of your component, which is defined in UIComponent. Eventually,
validateSize() calls measure(), which you
have overridden. That call gives you a chance to set the size of your control.
You could simply set width to 200 and height to 45, and then accept the consequences. All
instances of your MyLabel class would share the
same size, an unattractive proposal that somewhat defeats the point of using a
robust UI development tool like Flex in the first place. But if you do go with
fixed sizes, you don't have to override the measure() method, and you can even define the width and height when adding the
component to its parent. Listing 7 shows a couple variations of acquiring
measurements for label
controls.
Listing 7. Get label measurements
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
xmlns:components="article1.components.*">
<components:MyLabel id="label1"/>
<components:MyLabel ="label2" width="200"
height="50"/>
</mx:Application>
|
In
this example, label1 would have its measure() method called because you don't define the
size explicitly. However, measure isn't called in
the case of label2 because you define that
control's size explicitly. Note that the measure()
implementation in Flex's Label class does much more
than call measureText(). You need to take care of paddings on the left, right,
top, and bottom. However, the basic philosophy remains the same.
The measurement phase is an important phase of a Flex component's life cycle. It is a critically important phase, but you seldom need to focus on this phase unless you're writing your components from scratch. Most custom Flex components will probably ignore this phase and leave it to Flex to take care of. Flex does an excellent job of taking care of these kinds of nuances and spares you from doing tedious measurement tasks. However, you need a good understanding of this phase if you want to write brand new components for the Flex framework.
Layout phase: Where the action happens
The layout phase is the third of the Flex framework's three life cycle phases. You spend so much time in this phase that it's hard to think of it as a phase at all. All graphics work, including skinning and art-based enhancements, occur in this phase.
Assume you have a component that is properly validated and measured. The next step is to lay it out. The layout phase handles many aspects of a component. For example, you handle all background graphics during this phase. You also wrap any components that need skins in this phase. This is also the phase for moving a component to a desired location. Consider again the case of a VBox, a vertical-layout container. When you add children to it, you must move them below one another in a vertical orientation. That movement happens during the layout phase.
How the Flash coordinate system works
I'll walk you through how to move children into a VBox using Flex, but first let's backtrack a step and review the Flash Player coordinate system. The top left corner of any component is represented by x and y values. Each component's x and y values are relative to its immediate parent. The x axis is positive in the right direction, while the y axis is positive in the downward direction (again, this is in contrast to its traditional upward direction). So, if you place a label inside a VBox and its x and y values are 30 and 40 respectively, the label's top left corner is 30 pixels to the right and 40 pixels down from the top left corner of VBox itself. Each component has mouseX and mouseY properties, which tell you the relative location of the mouse pointer with respect to the component. For example, if mouseX is -46 and mouseY is 78, the mouse pointer is 46 pixels left of and 78 pixels down in the component's coordinate system. In addition to its local coordinate system, Flex also features a global-coordinate system that measures the x and y coordinates from the top left corner of entire application. These coordinates are called globalX and globalY.
During its life cycle, a
component undergoes a series of translations, rotations, scaling, shearing,
and stretching before it's finally displayed on the screen. Each component has
a matrix property associated with it; this property
holds information about these adjustments. Therefore, x and y and globalX and
globalY are related to each other through these matrix transformations.
Getting x and y typically involves applying matrix transformations to globalX
and globalY. Getting globalX and globalY values from x and y typically
requires performing reverse transformations. Conveniently, Flex hides all
these details and gives you two methods that simplify carrying out these
transformations: localToGlobal()and globalToLocal(). You don't need to worry about the
underlying transformations to use these methods flawlessly. There is one
important caveat, though: Functions work at a point level, not a component
level. Let's look at an example that relies on the VBox again. Listing 8
creates a label inside a
VBox:
Listing 8. Create a label inside a VBox
<mx:VBox id="box">
<mx:Label id="myLabel" text="I am a label"/>
</mx:VBox>
|
Both
box and myLabel have
localToGlobal() and its counterpart available.
Assume you want to get the global location of label. Your first thought might be to use the localToGlobal of the label's parent
because x and y of the label are relative to the x
and y of its parent, box. However, myLabel also has the same method available, so you
might as well invoke that. It turns out that both approaches are correct, but
you need to invoke them with different parameters. You can use either box.localToGlobal(new Point(myLabel.x,myLabel,y)), or
myLabel.localToGlobal(new Point(0,0)). When you
invoke box.localToGlobal(new
Point(myLabel.x,myLabel.y)), you're asking Flex to provide the
global location of a point in VBox, the coordinates of which are myLabel.x and myLabel.y.
Note that this has nothing to do with myLabel.
Get precise global coordinates
Assume myLabel's x and y
coordinates are 40 and 30, respectively. In this case, you're asking for the
precise global location of (40,30), which is a point in the VBox. The transformations that get applied here are
those of VBox matrix. The top left corner in myLabel's coordinate system is at (0,0)—which is
always true for any Flex component. This means that in the second option,
where the coordinate system of myLabel is considered, you're asking for Flex
to provide the global location of a point that is at (0,0) in
myLabel's coordinate system. The different
approaches yield the same answer because they basically pick the same point
from two different coordinate systems. Failing to understand that distinction
could lead to errors and confusion down the road.
There are several important reasons
that might require you to get a global position when using Flex. For example,
consider a typical use case where you show a tooltip for a component. The
tooltips are added to a Flex application's systemManager, so they reside in the global coordinate system.
However, you need to show the tooltip for a component that lies in the
local-coordinate system of its parent. Therefore, Flex picks up the local
coordinates of a point near the bottom right corner of the component in
question and applies "local to global" conversions to it. Finally, a tooltip
component is drawn and added as a child of a tooltip for a component. The
tooltips are added to a Flex application's systemManager, and then placed at the global point thus calculated.
Consider another case where you display a menu after someone clicks on a
control, such as the File menu in the top left corner of any browser. You
cannot add such a menu as the child of anything related to the label showing
the File option. For example, if you add the menu as a child of the
label's parent, then Flex would place the control vertically beneath that
point because that is VBox's default behavior. If
you add the menu as a child of the label itself, then the menu might not show
up at all because the label will measure the text that it holds and create a
dimension that fits the text only. Instead, you need to add your menu to systemManager, where it behaves similarly to pop-up
children controls. You can place controls you add to systemManager anywhere inside the entire application area. To place
such a control exactly below the label, you need to translate the desired
location to global coordinates. For example, the bottom point of a label is
(0,label.height) in a label's coordinate system.
You then translate this point using localToGlobal()
of the label and then place the menu at that point. You place the menu using a
move() method, which maps the top left corner of
any component to the desired location. Listing 9 shows you how to add a menu
to a label
control.
Listing 9. Add a menu to a label
<?xml version="1.0" encoding="utf-8"?>
<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml"
click="showMenu()">
<mx:Script>
<![CDATA[
import mx.controls.Menu;
private function showMenu() : void {
var m : Menu = new Menu();
// populate the menu control with options:
systemManager.addChild(m);
var global : Point = localToGlobal(new
Point(0,height));
m.move(global.x,global.y);
}
]] >
</mx:Script>
</mx:Label>
|
Clicking
the label in this sample calls an event handler, which creates a new Menu
control, populates it (I omitted some of this code for the sake of clarity),
and adds it to the systemManager as its child. This
component shows up in systemManager's rawChildren
list. Finally, this code moves the menu control to a global coordinate that is
just below the label control. Note the translation that happens from the
local-coordinate system to the global-coordinate system, enabling the
appropriate placement of the control in question.
When working with
containers, you need to worry about a third coordinate system in addition to
the local and global systems. A container silently creates a content pane that
holds a control's children. Therefore, a child added to a container is not
placed in the local-coordinate system of a container, but in its content pane,
which has its own coordinate system. You might wonder why there is a content
pane in the first place, as well as why it's required. In a container, Flex
needs to be able to add many things in addition to the container, such as
scroll bars, borders, overlays, and so on. Such elements are added to the
container, and the content pane takes care of child additions only. If a child
isn't visible in the container's visible area due to scroll bars, it still
exists in the content pane. You need some extra methods to convert that item
from its local-coordinate system to its content-coordinate system. These
additional methods are localToContent() and contentToLocal(). However, the content-coordinate
system is generally used in the cases where container uses absolute
positioning of its children.
Like the commit and measurement phases, the layout
phase is signaled by calling invalidateDisplayList() phase. This tells LayoutManager that the current display list is no
longer valid and that it should be updated. Flex adds the component that
signals LayoutManager to invalidatedDisplayListQueue and processes this instruction in the
next layout UI update. LayoutManager calls validateDisplayList() on each component in the queue,
and this method in turn calls a protected, updateDisplayList() method. You need to override this method after
calling super.updateDisplayList() if you're
creating a custom layout, and then substitute the desired logic. Pay special
attention to the parameters that updateDisplayList() takes: unscaledWidth
and unscaledHeight. These naming of these two
values give you a good idea of where you need to place the content for
this component. Together, the parameters define the bounds of the component in
question. Note that they are unscaled bounds. Flex takes care of scaling
later, once this method completes execution. The scaleX and scaleY properties allow you
to change the scale of the vector drawing, if you so desire. By default, scaleX and scaleY have
values of 1. Every component contains a protected graphics object, which Flex uses to perform vector drawing. The
vector drawing is employed for a variety of tasks, such as filling the
background color, border color, gradients, fills, and strokes. Vector-based
drawing is a big topic in itself and beyond the scope of this article. Suffice
it to say that this graphics object exposes methods like drawCircle(), which you can use to draw basic shapes and
boundaries. For example, assume you want to extend the Flex Label control by creating a boundary and background
color for it. Both of these options are unavailable in standard Label control implementation, but Listing 10 shows you
how to do
this:
Listing 10. Extend Flex's
Label class
package article1.components {
import mx.controls.Label;
public class MyLabel extends Label {
override protected function updateDisplayList(
unscaledWidth:Number, unscaledHeight:Number): void {
super.updateDisplayList(unscaledWidth,unscaledHeight);
graphics.lineStyle(1);
graphics.beginFill(0xcccccc);
graphics.drawRoundRect (0,0,unscaledWidth,unscaledHeight,5,5);
graphics.endFill();
|
This
code extends a Label class and overrides the
updateDisplayList() method. In it, you draw a rounded rectangle, set the width to unscaledWidth, the height to unscaledHeight, and the parameters for corner rounding,
ellipseWidth and ellipseHeight, to 5,5. Finally, this snippet paints the
rectangle's background as grey. You might have noticed one thing the code
doesn't do, which is call invalidateDisplayList().
This is unnecessary in the class that extends Flex's Label class because invalidations happen silently at many places in
Flex. For example, check out the scope of the code in Listing 11, which is
taken from the Label class the previous snippet
extends:
Listing 11. Invalidation in the base class
// some code omitted for clarity
public function set text(value:String): void {
_text = value;
textChanged = true
invalidateProperties();
invalidateSize();
invalidateDisplayList();
|
Note
how Flex calls all three invalidation methods when the text for the label is
set. This means you don't have to call them explicitly. It's helpful to
understand why all three invalidations are called here. When text is set,
the older text is "dirty" and the new text must be
"committed"—hence, the commit phase. The new text can be larger
or smaller than the older text, so the bounds must be remeasured. At some
point, the component must also change some parts of its layout. For example,
if the earlier label displayed HTML-formatted text and the new text is plain
and simple, the layout must change accordingly. If the text is too big for the
allowed size, then label might need to be truncated and a tooltip set. All
these issues are handled in the updateDisplayList()
(or layout) phase. Also, all three of these invalidations take place
automatically whenever addChild() is called. The
Flex APIs offer some insight into how the invalidation methods
work:
"Invalidation methods rarely get called. In general, setting a property on a component automatically calls the appropriate invalidation method."
The take-home point here is that most of the components written in Flex signal the proper invalidation method whenever their properties are changed. This holds true as long as you're extending already existing Flex controls. But you need to remember to call the invalidation methods at the appropriate time if you create your own Flex controls from scratch.
The
sequence in which these phases are called also tells you something about when
you shouldn't call them. Consider a case where a developer is extending a
Label and inadvertently chooses to set the text property in the updateDisplayList() method. The practical consequence
of this could be gruesome. Setting the text property calls validateProperties(), which in turn calls commitProperties(), then
invalidateDisplayList(), and, eventually, validateDisplayList() and updateDisplayList(). The end result is an infinite recursive loop,
and Flash Player will hang, freezing the entire application and possibly the
user's system, too. So be careful not to cross the boundaries of a phase.
Fortunately, you can ensure your components respect phase boundaries by
adhering to three simple rules:
- Deal with all the properties except those related to size and positioning in the commit phase. This includes setting styles, border layouts, corner radii, text, fonts, colors, and other, similar activities. Never change a property here that can call invalidateProperties() again.
- Deal only with size-related properties in the measurement phase. Never change a property here that can call invalidateSize() again.
- Deal with positioning, paddings, movements, vector drawings, bitmap fills, and so on in this phase. Never change any property here that can call invalidateDisplayList() again.
validateNow() requires special
attention
Another method that warrants special mention is validateNow(). Sometimes when you create a component,
you need to measure it immediately to get its size. Note that the component
hasn't been added to the main display list (or stage) yet, so LayoutManager hasn't had a chance to perform
measurements. Consider a scenario where you need to place an image at the
extreme top right corner of a canvas. You create an image instance, and you
move it to the right-hand corner. Then you do something like this: Image.move(canvas.width-image.width,canvas.y);
This snippet
doesn't work because the image has just been created and never measured;
hence, the image.width is zero. If you call image.validateNow() before calling image.move(), Flex invokes LayoutManager
forcefully, prompting it to go through all three phases, including the
measurement phase where the image gets measured properly, and width and height
are assigned valid values. Fortunately, a Flex canvas employs something called
constraint-based layout, which lets you specify a constraint for the image
such as 'right=0'. This instruction basically means
that whenever image is laid out in Flex's canvas, it will have a constraint
that its distance from the canvas right edge should be zero. You can achieve
this result by specifying right=0 and top=0 constraints for an image. Please bear in mind
that validateNow() is a resource-intensive method,
and you shouldn't call it unless you have a good reason for doing
so.
Build a component from scratch
So far you've learned about the benefits of using Flex and the use cases where it makes sense, how to manipulate and extend Flex components, and you've drilled down on its component life cycle. The final step is to put the rendering features covered so far to use in a custom component that you build from scratch. I'll walk you through all the required steps, showing you how to build a tree chart that presents the hierarchy of an organization. Note that this sample app is filled with dummy data.
Your first step is to make a node of the organization tree. Figure 3 shows how you can enhance the node's jazziness by putting a background image in it. It's not important which image editor you use to create such images, so use whatever you or your graphics designer feels comfortable with.
Figure 3. Add a background image
Call your node class Node.as. Listing 12 creates a
node element and uses Figure 3 to compose the node's
background.
Listing 12. Create a node element
override protected function updateDisplayList(
unscaledWidth:Number, unscaledHeight:Number): void {
graphics.clear();
var bitmapData : BitmapData = image.content[
"bitmapData"] as BitmapData;
graphics.beginBitmapFill(bitmapData);
graphics.drawRect(0,0,bitmapData.width,bitmapData.height);
graphics.endFill();
graphics.beginFill(0xff0000,.3);
graphics.drawRoundRect(-3,3,bitmapData.width+6,
bitmapData.height+6,10,10);
graphics.drawRoundRect(0,0,bitmapData.width,
bitmapData.height,10,10);
graphics.endFill();
|
The
graphics.beginBitmapFill() method is what
creates the bitmap fill as the background of your node element. Figure 4 shows
you how Flex renders this component.
Figure 4. Flex renders an element node
Listing 13 shows you how to add a roll-over effect to your component, so that the component shows a "focus" selection whenever a user rolls over the component:
Listing 13. Add a roll-over effect
public class Node extendsUIComponent {
private var rollOver : Boolean;
public function Node() {
addEventListener(MouseEvent.ROLL_OVER,
function(e : Event) : void {
rollOver = true;
invalidateDisplayList();
});
addEventListener(MouseEvent.ROLL_OUT,
function(e : Event) : void {
rollOver = false;
invalidateDisplayList();
});
}
override protected function
updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number): void {
if(rollOver) {
graphics.beginFill(0x0000ff,.2);
graphics.drawRoundRect(
-10,-10,bitmapData.width+20,
bitmapData.height+20,10,10);
graphics.drawRoundRect(
-3,-3,bitmapData.width+6,
bitmapData.height+6,10,10);
graphics.endFill();
}
}
}
|
In
this code, you define a boolean variable rollover and attach event listeners
for ROLL_OVER and ROLL_OUT. In the former case, you set rollover as true; in the
latter case, you set rollover as false. Note that you invalidate the
display list in either case because you want Flex to render (or remove)
the focus rectangle around the box.
Next, you want to add some text to
your application. For this step, you define a label inside the Node and
override the createChildren() method to add that label as a child. You define
a nodeName attribute in the Node class and in commitProperties(),
and then populate the label's text field with the nodeName. Next, you override the measure() method and measure the height
and width of the text using text-line metrics. You
also measure the Node component here and set its
measuredWidth and measuredHeight equal to the bitmapData
that your embedded image contains. Listing 14 walks you through how to add
text to your
application:
Listing 14. Add text to your Flex application
private var _nodeName : String;
private function set nodeName(
nodeName : String) : void {
_nodeName = nodeName;
invalidateProperties();
}
override protected function createChildren():void {
text = new Text();
addChild(text);
}
override protected function commitProperties(): void {
super.commitProperties();>
text.text = nodeName;
}
override protected function measure(): void {
super.measure();
var metrics : TextLineMetrics = measureText(nodeName);
text.setActualSize(metrics.width+10,metrics.height+5);
measuredHeight = image.height;
measuredWidth = image.width;
}
|
Note
how you invalidate the properties while you set the nodeName. You do this
because you want to signal to LayoutManager that
the properties have changed, and it's time to call commit. You set the real
text of the label during the commit phase using the (commitProperties() method. Finally, Listing 15 shows you how to add
your node element to the main application
file:
Listing 15. Add node to the main file
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical"
xmlns:components="article1.components.*">
<components:Node nodeName="John Michael"/>
</mx:Application>
|
Figure 5 shows you the final version of the node element Flex control, complete with its rollover focus rectangle.
Figure 5. The finished node element
You're almost finished. One of the final steps is to create a
link to connect your nodes. Building a link is much easier than building a
node. Listing 16 shows you how to extend from a UIComponent, override updateDisplayList(), and create lines using the graphics object of the link component.
Listing 16. Build a link
package article1.components {
import mx.core.UIComponent;
public class Link extends UIComponent {
public var fromNode : Node;
public var toNode : Node;
override protected function updateDisplayList
(unscaledWidth:Number,
unscaledHeight:Number): void {
graphics.clear();
graphics.lineStyle(4,0x0000ff,.5);
graphics.moveTo(fromNode.x+fromNode.width/2,
fromNode.y+fromNode.height/2);
graphics.lineTo(toNode.x+toNode.width/2,
toNode.y+toNode.height/2);
}
}
}
|
You create a pure vector-based drawing here and render a line here from source node to destination node. Listing 17 shows you how to put together the nodes and links and create a tree chart.
Listing 17. Put everything together
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute" xmlns:components="article1.components.* ">
<components:Link fromNode="{node1}"
toNode="{node2}"/>
<components:Link fromNode="{node2}"
toNode="{node3}"/>
<components:Link fromNode="{node1}"
toNode="{node3}"/>
<components:Node id="node1"
nodeName="John Michael"
x="400" y="300"/>
<components:Node id="node2"
nodeName="Harris Scott"
x="700" y="600"/>
<components:Node id="node3"
nodeName="Paul Simpson"
x="100" y="600"/>
</mx:Application>
|
And finally, Figure 6 shows the finished version of your custom Flex organizational tree view application.
Figure 6. The final UI
You can download the complete source for the custom Flex component described in this article from the Download section.
At this point, you've created a tree chart component with three nodes. Adding more nodes and links is a simple matter of having the requisite data and is unrelated to the Flex rendering engine per se, so I'll leave the rest to you. You can use this kind of component for expressing the kinds of relationships you find in an organization chart, a router-link topology, and similar kinds of applications. Even if you don't need to create that kind of application, you can use the techniques and samples discussed in this article to build extendable and custom Flex components that serve your specific business needs.
This article covered Flex's rendering engine in some depth, including how you extend the built-in Flex components and how you create your own Flex components from scratch. But there's so much more to Flex to explore and understand. For example, the layout phase is an important part of Flex component's life cycle, but there are other important phases, too, including the initialization phase and creation phases. The Flex Language Reference (see Resources) is a great place to learn about what Flex offers in general. Understanding the event-dispatch model is also important when it comes to building sophisticated RIAs. Finally, you can find many blogs and other Web sites that showcase amazing Flex components, often with source code—I've listed some of these in the resources section as well.
| Description | Name | Size | Download method |
|---|---|---|---|
| sample code | sample.zip | 575KB | HTTP |
Information about download methods
-
Adobe Flex 3.3 Language
Reference: The exhaustive listing of Flex classes with ASDocs
-
Adobe Flex: The Flex Product
listing
-
Flex.org: A great place to look for what's new
in Flex
-
Flex Application Showcase: Look for
latest sample Flex applications here.
-
Article
on Flex vector-based drawing and bitmap rendering: Find out how
vector-based drawing and rendering relate to each other in Flex
(developerWorks, March 2009).
-
Flex Dev Net: A network of
developers who discuss new and important aspects of using Flex
-
Custom Flex Components: A
collection of great Flex components
- My
developerWorks: Personalize your developerWorks experience.

Sandeep Malik is a Tech Lead for IBM Cognos NOW! and works out of India Software Labs, Pune. He has been involved in the design and architecture phase of new generation UI for the Cognos NOW!, and in memory and real-time streaming OBI (Operation Business Intelligence) engine. Sandeep has extensive experience in heavy duty graphics, charting libraries, client-side streaming, non-blocking I/Os, and in general, asynchronous systems. Prior to IBM, Sandeep worked in a network security domain, analyzing the patterns that change the network traffic distribution (for example, botnets, worm scans, and so on). He has also worked on implementing Servlets 2.4 spec in one of his previous companies. During his free time, he enjoys watching cricket and wishes that in his next life he could become a cricketer, too!




