You can create advanced visual components for use in applications built with Flex.
Simple visual components are subclasses of existing Flex components that modify the appearance of the component by using skins or styles, or add new functionality to the component. For example, you add a new event type to a Button control, or modify the default styles or skins of a DataGrid control. For more information, see Create simple visual components in ActionScript.
In advanced components, you typically perform the following actions:
Modify the visual appearance or visual characteristics of an existing component.
Create a composite component that encapsulates two or more components within it.
Create a component by creating a subclass of the UIComponent class.
You usually create a component as a subclass of an existing class. For example, to create a component that is based on the Button control, you create a subclass of the mx.controls.Button class. To make your own component, you create a subclass of the mx.core.UIComponent class.
All Flex visual components are subclasses of the UIComponent class. Therefore, visual components inherit the methods, properties, events, styles, and effects defined by the UIComponent class.
To create an advanced visual component, you must implement a class constructor. Also, you optionally override one or more of the following protected methods of the UIComponent class:
|
UIComponent method |
Description |
|---|---|
commitProperties() |
Commits any changes to component properties, either to make the changes occur at the same time or to ensure that properties are set in a specific order. For more information, see Implementing the commitProperties() method. |
createChildren() |
Creates any child components of the component. For example, the ComboBox control contains a TextInput control and a Button control as child components. For more information, see Implementing the createChildren() method. |
layoutChrome() |
Defines the border area around the container for subclasses of the Container class. For more information, see Implementing the layoutChrome() method. |
measure() |
Sets the default size and default minimum size of the component. For more information, see Implementing the measure() method. |
updateDisplayList() |
Sizes and positions the children of the component on the screen based on all previous property and style settings, and draws any skins or graphic elements used by the component. The parent container for the component determines the size of the component itself. For more information, see Implementing the updateDisplayList() method. |
Component users do not call these methods directly; Flex calls them as part of the initialization process of creating a component, or when other method calls occur. For more information, see About the component instantiation life cycle.
During the lifetime of a component, your application might modify the component by changing its size or position, modifying a property that controls its display, or modifying a style or skin property of the component. For example, you might change the font size of the text displayed in a component. As part of changing the font size, the component's size might also change, which requires Flex to update the layout of the application. The layout operation might require Flex to invoke the commitProperties(), measure(), layoutChrome(), and the updateDisplayList() methods of your component.
Your application can programmatically change the font size of a component much faster than Flex can update the layout of an application. Therefore, you should only want to update the layout after you are sure that you've determined the final value of the font size.
In another scenario, when you set multiple properties of a component, such as the label and icon properties of a Button control, you want the commitProperties(), measure(), and updateDisplayList() methods to execute only once, after all properties are set. You do not want these methods to execute when you set the label property, and then execute again when you set the icon property.
Also, several components might change their font size at the same time. Rather than updating the application layout after each component changes its font size, you want Flex to coordinate the layout operation to eliminate any redundant processing.
Flex uses an invalidation mechanism to synchronize modifications to components. Flex implements the invalidation mechanism as a set of methods that you call to signal that something about the component has changed and requires Flex to call the component's commitProperties(), measure(), layoutChrome(), or updateDisplayList() methods.
The following table describes the invalidation methods:
|
Invalidation method |
Description |
|---|---|
invalidateProperties() |
Marks a component so that its commitProperties() method gets called during the next screen update. |
invalidateSize() |
Marks a component so that its measure() method gets called during the next screen update. |
invalidateDisplayList() |
Marks a component so that its layoutChrome() and updateDisplayList() methods get called during the next screen update. |
When a component calls an invalidation method, it signals to Flex that the component must be updated. When multiple components call invalidation methods, Flex coordinates updates so that they all occur together during the next screen update.
Typically, component users do not call the invalidation methods directly. Instead, they are called by the component's setter methods, or by any other methods of a component class as necessary. For more information and examples, see Implementing the commitProperties() method.
The component instantiation life cycle describes the sequence of steps that occur when you create a component object from a component class. As part of that life cycle, Flex automatically calls component methods, dispatches events, and makes the component visible.
The following example creates a Button control in ActionScript and adds it to a container:
// Create a Box container. var boxContainer:Box = new Box(); // Configure the Box container. // Create a Button control. var b:Button = new Button() // Configure the button control. b.label = "Submit"; ... // Add the Button control to the Box container. boxContainer.addChild(b);
The following steps show what occurs when you execute the code to create the Button control, and add the control to the Box container:
You call the component's constructor, as the following code shows:
// Create a Button control. var b:Button = new Button()
You configure the component by setting its properties, as the following code shows:
// Configure the button control. b.label = "Submit";
Component setter methods might call the invalidateProperties(), invalidateSize(), or invalidateDisplayList() methods.
You call the addChild() method to add the component to its parent, as the following code shows:
// Add the Button control to the Box container. boxContainer.addChild(b);
Flex then performs the following actions:
Sets the parent property for the component to reference its parent container.
Computes the style settings for the component.
Dispatches the preinitialize event on the component.
Calls the component's createChildren() method.
Calls the invalidateProperties(), invalidateSize(), and invalidateDisplayList() methods to trigger later calls to the commitProperties(), measure(), or updateDisplayList() methods during the next render event.
The only exception to this rule is that Flex does not call the measure() method when the user sets the height and width of the component.
Dispatches the initialize event on the component. At this time, all of the component's children are initialized, but the component has not been sized or processed for layout. You can use this event to perform additional processing of the component before it is laid out.
Dispatches the childAdd event on the parent container.
Dispatches the initialize event on the parent container.
During the next render event, Flex performs the following actions:
Calls the component's commitProperties() method.
Calls the component's measure() method.
Calls the component's layoutChrome() method.
Calls the component's updateDisplayList() method.
If steps 13 and 14 are not required, which is the most common scenario, dispatches the updateComplete event on the component.
Flex dispatches additional render events if the commitProperties(), measure(), or updateDisplayList() methods call the invalidateProperties(), invalidateSize(), or invalidateDisplayList() methods.
After the last render event occurs, Flex performs the following actions:
Makes the component visible by setting the visible property to true.
Dispatches the creationComplete event on the component. The component is sized and processed for layout. This event is only dispatched once when the component is created.
Dispatches the updateComplete event on the component. Flex dispatches additional updateComplete events whenever the layout, position, size, or other visual characteristic of the component changes and the component is updated for display.
Most of the work for configuring a component occurs when you add the component to a container by using the addChild() method. That is because until you add the component to a container, Flex cannot determine its size, set inheriting style properties, or draw it on the screen.
You can also define your application in MXML, as the following example shows:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark">
<mx:Box>
<mx:Button label="Submit"/>
</mx:Box>
</s:Application>
The sequence of steps that Flex executes when creating a component in MXML are equivalent to the steps described for ActionScript.
You can remove a component from a container by using the removeChild() method. If there are no references to the component, it is eventually deleted from memory by the garbage collection mechanism of Adobe® Flash® Player or Adobe® AIR™.
When you implement a component, you override component methods, define new properties, dispatch new events, or perform any other customizations required by your application.
To implement your component, follow these general steps:
If necessary, create any skins for the component.
Create an ActionScript class file.
Extend one of the base classes, such as UIComponent or another component class.
Specify properties that the user can set by using an MXML tag property.
Embed any graphic and skin files.
Implement the constructor.
Implement the UIComponent.createChildren() method.
Implement the UIComponent.commitProperties() method.
Implement the UIComponent.measure() method.
Implement the UIComponent.layoutChrome() method.
Implement the UIComponent.updateDisplayList() method.
Add properties, methods, styles, events, and metadata.
Deploy the component as an ActionScript file or as a SWC file.
For more information about MXML tag properties and embedding graphic and skin files, see Create simple visual components in ActionScript.
You do not have to override all component methods to define a new component. You only override the methods required to implement the functionality of your component. If you create a subclass of an existing component, such as Button control or VBox container, you must implement the methods necessary for you to add any new functionality to the component.
For example, you can implement a custom Button control that uses a new mechanism for defining its default size. In that case, you only need to override the measure() method. For an example, see Implementing the measure() method.
Or you might implement a new subclass of the VBox container. Your new subclass uses all of the existing sizing logic of the VBox class, but changes the layout logic of the class to lay out the container children from the bottom of the container to the top, rather than from the top down. In this case, you only need to override the updateDisplayList() method. For an example, see Implementing the updateDisplayList() method.
Flex uses interfaces to divide the basic functionality of components into discrete elements so that they can be implemented piece by piece. For example, to make your component focusable, it must implement the IFocusable interface; to let it participate in the layout process, it must implement ILayoutClient interface.
To simplify the use of interfaces, the UIComponent class implements all of the interfaces defined in the following table, except for the IFocusManagerComponent and IToolTipManagerClient interfaces. However, many subclasses of UIComponent implement the IFocusManagerComponent and IToolTipManagerClient interfaces.
Therefore, if you create a subclass of the class or subclass of UIComponent, you do not have to implement these interfaces. But, if you create a component that is not a subclass of UIComponent, and you want to use that component in Flex, you might have to implement one or more of these interfaces.
The following table lists the main interfaces implemented by Flex components:
|
Interface |
Use |
|---|---|
|
Indicates that the component supports the advanced style subsystem. |
|
|
Indicates that a component is an object within the automation object hierarchy. |
|
|
Indicates the number of children in a container. |
|
|
Indicates that the component support layout constraints. |
|
|
Indicates that a component or object can effect deferred instantiation. |
|
|
Specifies the interface for skin elements. |
|
|
indicates that the component can be used with module factories |
|
|
Indicates that a component or object can use the invalidation mechanism to perform delayed, rather than immediate, property commitment, measurement, and drawing or layout. |
|
|
Indicates that a component or object can participate in the LayoutManager's commit, measure, and update sequence. |
|
|
Indicates that a component supports a specialized form of event propagation. |
|
|
Indicates that a component or object can be used with the Repeater class. |
|
|
Indicates that the component supports view states. |
|
|
Indicates that a component has a toolTip property, and therefore is monitored by the ToolTipManager. |
|
|
Defines the basic set of APIs that you must implement in order to be a child of layout containers and lists. |
|
|
Indicates that a component can listen for validation events, and therefore show a validation state, such as a red border and error tooltips. |
|
|
Indicates that the component can be laid out and displayed in a Spark application. |
When you create a custom component in ActionScript, you have to override the methods of the UIComponent class. You implement the basic component structure, the constructor, and the createChildren(), commitProperties(), measure(), layoutChrome(), and updateDisplayList() methods.
The following example shows the basic structure of a Flex component:
package myComponents
{
public class MyComponent extends UIComponent
{
....
}
}
You must define your ActionScript custom components within a package. The package reflects the directory location of your component within the directory structure of your application.
The class definition of your component must be prefixed by the public keyword. A file that contains a class definition can have one, and only one, public class definition, although it can have additional internal class definitions. Place any internal class definitions at the bottom of your source file below the closing curly brace of the package definition.
Your ActionScript class should define a public constructor method for a class that is a subclass of the UIComponent class, or a subclass of any child of the UIComponent class. The constructor has the following characteristics:
No return type
Should be declared public
No arguments
Calls the super() method to invoke the superclass' constructor
Each class can contain only one constructor method; ActionScript does not support overloaded constructor methods. For more information, see Defining the constructor.
Use the constructor to set the initial values of class properties. For example, you can set default values for properties and styles, or initialize data structures, such as Arrays.
Do not create child display objects in the constructor; you should use it only for setting initial properties of the component. If your component creates child components, create them in the createChildren() method.
A component that creates other components or visual objects within it is called a composite component. For example, the Flex ComboBox control contains a TextInput control to define the text area of the ComboBox, and a Button control to define the ComboBox arrow. Components implement the createChildren() method to create child objects (such as other components) in the component.
You do not call the createChildren() method directly; Flex calls it when the call to the addChild() method occurs to add the component to its parent. Notice that the createChildren() method has no invalidation method, which means that you do not have to call it a second time after the component is added to its parent.
For example, you might define a new component that consists of a Button control and a TextArea control, where the Button control enables and disables user input to the TextArea control. The following example creates the TextArea and Button controls:
// Declare two variables for the component children.
private var text_mc:TextArea;
private var mode_mc:Button;
override protected function createChildren():void {
// Call the createChildren() method of the superclass.
super.createChildren();
// Test for the existence of the children before creating them.
// This is optional, but do this so a subclass can create a different
// child.
if (!text_mc) {
text_mc = new TextArea();
text_mc.explicitWidth = 80;
text_mc.editable = false;
text_mc.addEventListener("change", handleChangeEvent);
// Add the child component to the custom component.
addChild(text_mc);
}
// Test for the existence of the children before creating them.
if (!mode_mc) {
mode_mc = new Button();
mode_mc.label = "Toggle Editing";
mode_mc.addEventListener("click", handleClickEvent);
// Add the child component to the custom component.
addChild(mode_mc);
}
}
Notice in this example that the createChildren() method calls the addChild() method to add the child component. You must call the addChild() method for each child object.
After you create a child component, you can use properties of the child component to define its characteristics. In this example, you create the Button and TextArea controls, initialize them, and register event listeners for them. You could also apply skins to the child components. For a complete example, see Example: Creating a composite component.
You use the commitProperties() method to coordinate modifications to component properties. Most often, you use it with properties that affect how a component appears on the screen.
Flex schedules a call to the commitProperties() method when a call to the invalidateProperties() method occurs. The commitProperties() method executes during the next render event after a call to the invalidateProperties() method. When you use the addChild() method to add a component to a container, Flex automatically calls the invalidateProperties() method.
Calls to the commitProperties() method occur before calls to the measure() method. This lets you set property values that the measure() method might use.
The typical pattern for defining component properties is to define the properties by using getter and setter methods, as the following example shows:
// Define a private variable for the alignText property.
private var _alignText:String = "right";
// Define a flag to indicate when the _alignText property changes.
private var bAlignTextChanged:Boolean = false;
// Define getter and setter methods for the property.
public function get alignText():String {
return _alignText;
}
public function set alignText(t:String):void {
_alignText = t;
bAlignTextChanged = true;
// Trigger the commitProperties(), measure(), and updateDisplayList()
// methods as necessary.
// In this case, you do not need to remeasure the component.
invalidateProperties();
invalidateDisplayList();
}
// Implement the commitProperties() method.
override protected function commitProperties():void {
super.commitProperties();
// Check whether the flag indicates a change to the alignText property.
if (bAlignTextChanged) {
// Reset flag.
bAlignTextChanged = false;
// Handle alignment change
}
}
As you can see in this example, the setter method modifies the property, calls the invalidateProperties() and invalidateDisplayList() methods, and then returns. The setter itself does not perform any calculations based on the new property value. This design lets the setter method return quickly, and leaves any processing of the new value to the commitProperties() method.
Changing the alignment of text in a control does not necessarily change the control's size. However, if it does, include a call to the invalidateSize() method to trigger the measure() method.
The main advantages of using the commitProperties() method are the following:
To coordinate the modifications of multiple properties so that the modifications occur synchronously.
For example, you might define multiple properties that control the text displayed by the component, such as the alignment of the text within the component. A change to either the text or the alignment property requires Flex to update the appearance of the component. However, if you modify both the text and the alignment, you want Flex to perform any calculations for sizing or positioning the component once, when the screen updates.
Therefore, you use the commitProperties() method to calculate any values based on the relationship of multiple component properties. By coordinating the property changes in the commitProperties() method, you can reduce unnecessary processing overhead.
To coordinate multiple modifications to the same property.
You do not necessarily want to perform a complex calculation every time a user updates a component property. For example, users modify the icon property of the Button control to change the image displayed in the button. Calculating the label position based on the presence or size of an icon can be a computationally expensive operation that you want to perform only when necessary.
To avoid this behavior, you use the commitProperties() method to perform the calculations. Flex calls the commitProperties() method when it updates the display. That means you perform the calculations once when Flex updates the screen, regardless of the number of times the property changed between screen updates.
The following example shows how you can handle two related properties in the commitProperties() method:
// Define a private variable for the text property.
private var _text:String = "ModalText";
private var bTextChanged:Boolean = false;
// Define the getter method.
public function get text():String {
return _text;
}
//Define the setter method to call invalidateProperties()
// when the property changes.
public function set text(t:String):void {
_text = t;
bTextChanged = true;
invalidateProperties();
// Changing the text causes the control to recalculate its default size.
invalidateSize();
invalidateDisplayList();
}
// Define a private variable for the alignText property.
private var _alignText:String = "right";
private var bAlignTextChanged:Boolean = false;
public function get alignText():String {
return _alignText;
}
public function set alignText(t:String):void {
_alignText = t;
bAlignTextChanged = true;
invalidateProperties();
invalidateDisplayList();
}
// Implement the commitProperties() method.
override protected function commitProperties():void {
super.commitProperties();
// Check whether the flags indicate a change to both properties.
if (bTextChanged && bAlignTextChanged) {
// Reset flags.
bTextChanged = false;
bAlignTextChanged = false;
// Handle case where both properties changed.
}
// Check whether the flag indicates a change to the text property.
if (bTextChanged) {
// Reset flag.
bTextChanged = false;
// Handle text change.
}
// Check whether the flag indicates a change to the alignText property.
if (bAlignTextChanged) {
// Reset flag.
bAlignTextChanged = false;
// Handle alignment change.
}
}
The measure() method sets the default component size, in pixels, and optionally sets the component's default minimum size.
Flex schedules a call to the measure() method when a call to the invalidateSize() method occurs. The measure() method executes during the next render event after a call to the invalidateSize() method. When you use the addChild() method to add a component to a container, Flex automatically calls the invalidateSize() method.
When you set a specific height and width of a component, Flex does not call the measure() method, even if you explicitly call the invalidateSize() method. That is, Flex calls the measure() method only if the explicitWidth property or the explicitHeight property of the component is NaN.
In the following example, because you explicitly set the size of the Button control, Flex does not call the Button.measure() method:
<mx:Button height="10" width="10"/>
In a subclass of an existing component, you might implement the measure() method only if you are performing an action that requires modification to the default sizing rules defined in the superclass. Therefore, to set a new default size, or perform calculations at run time to determine component sizing rules, implement the measure() method.
You set the following properties in the measure() method to specify the default size:
|
Properties |
Description |
|---|---|
measuredHeightmeasuredWidth |
Specifies the default height and width of the component, in pixels. These properties are set to 0 until the measure() method executes. Although you can leave them set to 0, it makes the component invisible by default. |
measuredMinHeightmeasuredMinWidth |
Specifies the default minimum height and minimum width of the component, in pixels. Flex cannot set the size of a component smaller than its specified minimum size. |
The measure() method only sets the default size of the component. In the updateDisplayList() method, the parent container of the component passes to it its actual size, which may be different than the default size.
Component users can also override the default size settings in an application by using the component in the following ways:
Setting the explicitHeight and exlicitWidth properties
Setting the width and height properties
Setting the percentHeight and percentWidth properties
For example, you can define a Button control with a default size of 100 pixels wide and 50 pixels tall, and a default minimum size of 50 pixels by 25 pixels, as the following example shows:
package myComponents
{
// asAdvanced/myComponents/BlueButton.as
import mx.controls.Button;
public class BlueButton extends Button {
public function BlueButton() {
super();
}
override protected function measure():void {
super.measure();
measuredWidth=100;
measuredMinWidth=50;
measuredHeight=50;
measuredMinHeight=25;
}
}
}
The following application uses this button in an application:
<?xml version="1.0"?>
<!-- asAdvanced/ASAdvancedMainBlueButton.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*" >
<mx:VBox>
<MyComp:BlueButton/>
<mx:Button/>
</mx:VBox>
</s:Application>
In the absence of any other sizing constraints on the button, the VBox container uses the default size and default minimum size of the button to calculate its size at run time. For information on the rules for sizing a component, see Introduction to containers.
You can override the default size settings in an application, as the following example shows:
<?xml version="1.0"?>
<!-- asAdvanced/MainBlueButtonResize.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*" >
<mx:VBox>
<MyComp:BlueButton width="50%"/>
<mx:Button/>
</mx:VBox>
</s:Application>
In this example, you specify that the width of the button is 50% of the width of the VBox container. When 50% of the width of the container is smaller than the minimum width of the button, the button uses its minimum width.
The example in Implementing the measure() method uses static values for the default size and default minimum size of a component. Some Flex components use static sizes. For example, the TextArea control has a default size of 100 pixels wide by 44 pixels high, regardless of the text it contains. If the text is larger than the TextArea control, the control displays scroll bars.
Often, you set the default size based on characteristics of the component or information passed to the component. For example, the Button control's measure () method examines its label text, margin settings, and font characteristics to determine the control's default size.
In the following example, you override the measure() method of the TextArea control so that it examines the text passed to the control, and calculates the default size of the TextArea control to display the entire text string in a single line:
package myComponents
{
// asAdvanced/myComponents/MyTextArea.as
import mx.controls.TextArea;
import flash.text.TextLineMetrics;
public class MyTextArea extends TextArea
{
public function MyTextArea() {
super();
}
// The default size is the size of the text plus a 10 pixel margin.
override protected function measure():void {
super.measure();
// Calculate the default size of the control based on the
// contents of the TextArea.text property.
var lineMetrics:TextLineMetrics = measureText(text);
// Add a 10 pixel border area around the text.
measuredWidth = measuredMinWidth = lineMetrics.width + 10;
measuredHeight = measuredMinHeight = lineMetrics.height + 10;
}
}
}
For text strings that are longer than the display area of your application, you can add logic to increase the height of the TextArea control to display the text on multiple lines. The following application uses this component:
<?xml version="1.0"?>
<!-- asAdvanced/MainMyTextArea.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*"
width="1000">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<MyComp:MyTextArea id="myTA" text="This is a long text strring that would normally cause a TextArea control to display scroll bars. But, the custom MyTextArea control calcualtes its default size based on the text size."/>
<mx:TextArea id="flexTA" text="This is a long text strring that would normally cause a TextArea control to display scroll bars. But, the custom MyTextArea control calcualtes its default size based on the text size."/>
</s:Application>
The Container class, and some subclasses of the Container class, use the layoutChrome() method to define the border area around the container.
Flex schedules a call to the layoutChrome() method when a call to the invalidateDisplayList() method occurs. The layoutChrome() method executes during the next render event after a call to the invalidateDisplayList() method. When you use the addChild() method to add a component to a container, Flex automatically calls the invalidateDisplayList() method.
Typically, you use the RectangularBorder class to define the border area of a container. For example, you can create the RectangularBorder object, and add it as a child of the component in your override of the createChildren() method.
When you create a subclass of the Container class, you can use the createChildren() method to create the content children of the container; the content children are the child components that appear within the container. You then use updateDisplayList() to position the content children.
You typically use the layoutChrome() method to define and position the border area of the container, and any additional elements that you want to appear in the border area. For example, the Panel container uses the layoutChrome() method to define the title area of the panel container, including the title text and close button.
The primary reason for dividing the handling of the content area of a container from its border area is to handle the situation when the Container.autoLayout property is set to false. When the autoLayout property is set to true, measurement and layout of the container and of its children are done whenever the position or size of a container child changes. The default value is true.
When the autoLayout property is set to false, measurement and layout are done only once, when children are added to or removed from the container. However, Flex executes the layoutChrome() method in both cases. Therefore, the container can still update its border area even when the autoLayout property is set to false.
The updateDisplayList() method sizes and positions the children of your component based on all previous property and style settings, and draws any skins or graphic elements that the component uses. The parent container for the component determines the size of the component itself.
A component does not appear on the screen until its updateDisplayList() method gets called. Flex schedules a call to the updateDisplayList() method when a call to the invalidateDisplayList() method occurs. The updateDisplayList() method executes during the next render event after a call to the invalidateDisplayList() method. When you use the addChild() method to add a component to a container, Flex automatically calls the invalidateDisplayList() method.
The main uses of the updateDisplayList() method are the following:
To set the size and position of the elements of the component for display.
Many components are made up of one or more child components, or have properties that control the display of information in the component. For example, the Button control lets you specify an optional icon, and use the labelPlacement property to specify where the button text appears relative to the icon.
The Button.updateDisplayList() method uses the settings of the icon and labelPlacement properties to control the display of the button.
For containers that have child controls, the updateDisplayList() method controls how those child components are positioned. For example, the updateDisplayList() method on the HBox container positions its children from left to right in a single row; the updateDisplayList() method for a VBox container positions its children from top to bottom in a single column.
To size components in the updateDisplayList() method, you use the setActualSize() method, not the sizing properties, such as width and height. To position a component, use the move() method, not the x and y properties.
To draw any visual elements necessary for the component.
Components support many types of visual elements such as skins, styles, and borders. Within the updateDisplayList() method, you can add these visual elements, use the Flash drawing APIs, and perform additional control over the visual display of your component.
The updateDisplayList() method has the following signature:
protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
The properties have the following values:
Specifies the width of the component, in pixels, in the component's coordinates, regardless of the value of the scaleX property of the component. This is the width of the component as determined by its parent container.
Specifies the height of the component, in pixels, in the component's coordinates, regardless of the value of the scaleY property of the component. This is the height of the component as determined by its parent container.
Scaling occurs in Flash Player or AIR, after updateDisplayList() executes. For example, a component with an unscaledHeight value of 100, and with a scaleY property of 2.0, appears 200 pixels high in Flash Player or AIR.
The VBox container lays out its children from the top of the container to the bottom, in the order in which the children are added to the container. The following example overrides the updateDisplayList() method, which causes the VBox container to layout its children from the bottom of the container to the top:
package myComponents
{
// asAdvanced/myComponents/BottomUpVBox.as
import mx.containers.VBox;
import mx.core.EdgeMetrics;
import mx.core.UIComponent;
public class BottomUpVBox extends VBox
{
public function BottomUpVBox() {
super();
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Get information about the container border area.
// The usable area of the container for its children is the
// container size, minus any border areas.
var vm:EdgeMetrics = viewMetricsAndPadding;
// Get the setting for the vertical gap between children.
var gap:Number = getStyle("verticalGap");
// Determine the y coordinate of the bottom of the usable area
// of the VBox.
var yOfComp:Number = unscaledHeight-vm.bottom;
// Temp variable for a container child.
var obj:UIComponent;
for (var i:int = 0; i < numChildren; i++)
{
// Get the first container child.
obj = UIComponent(getChildAt(i));
// Determine the y coordinate of the child.
yOfComp = yOfComp - obj.height;
// Set the x and y coordinate of the child.
// Note that you do not change the x coordinate.
obj.move(obj.x, yOfComp);
// Save the y coordinate of the child,
// plus the vertical gap between children.
// This is used to calculate the coordinate
// of the next child.
yOfComp = yOfComp - gap;
}
}
}
}
In this example, you use the UIComponent.move() method to set the position of each child in the container. You can also use the UIComponent.x and UIComponent.y properties to set these coordinates. The difference is that the move() method changes the location of the component and then dispatches a move event when you call the method immediately; setting the x and y properties changes the location of the component and dispatches the event on the next screen update.
The following application uses this component:
<?xml version="1.0"?>
<!-- asAdvanced/MainBottomVBox.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*" >
<MyComp:BottomUpVBox>
<mx:Label text="Label 1"/>
<mx:Button label="Button 1"/>
<mx:Label text="Label 2"/>
<mx:Button label="Button 2"/>
<mx:Label text="Label 3"/>
<mx:Button label="Button 3"/>
<mx:Label text="Label 4"/>
<mx:Button label="Button 4"/>
</MyComp:BottomUpVBox>
</s:Application>
Every Flex component is a subclass of the Flash Sprite class, and therefore inherits the Sprite.graphics property. The Sprite.graphics property specifies a Graphics object that you can use to add vector drawings to your component.
For example, in the updateDisplayList() method, you can use methods of the Graphics class to draw borders, rules, and other graphical elements:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Draw a simple border around the child components.
graphics.lineStyle(1, 0x000000, 1.0);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
}
A growing requirement for web content is that it should be accessible to people who have disabilities. Visually impaired people can use the visual content in Flash applications by using screen reader software, which provides an audio description of the material on the screen.
When you create a component, you can include ActionScript that enables the component and a screen reader for audio communication. When developers use your component to build an application in Flash, they use the Accessibility panel to configure each component instance.
Flash includes the following accessibility features:
Custom focus navigation
Custom keyboard shortcuts
Screen-based documents and the screen authoring environment
An Accessibility class
To enable accessibility in your component, add the following line to your component's class file:
mx.accessibility.ComponentName.enableAccessibility();
For example, the following line enables accessibility for the MyButton component:
mx.accessibility.MyButton.enableAccessibility();
For additional information about accessibility, see Accessible applications.
When releasing components, you can define a version number. This lets developers know whether they should upgrade, and helps with technical support issues. When you set a component's version number, use the static variable version, as the following example shows:
static var version:String = "1.0.0.42";
If you create many components as part of a component package, you can include the version number in an external file. That way, you update the version number in only one place. For example, the following code imports the contents of an external file that stores the version number in one place:
include "../myPackage/ComponentVersion.as"
The contents of the ComponentVersion.as file are identical to the previous variable declaration, as the following example shows:
static var version:String = "1.0.0.42";
Use the following practices when you design a component:
Keep the file size as small as possible.
Make your component as reusable as possible by generalizing functionality.
Use the Border class rather than graphical elements to draw borders around objects.
Assume an initial state. Because style properties are on the object, you can set initial settings for styles and properties so your initialization code does not have to set them when the object is constructed, unless the user overrides the default state.
Composite components are components that contain multiple components. They might be graphical assets or a combination of graphical assets and component classes. For example, you can create a component that includes a button and a text field, or a component that includes a button, a text field, and a validator.
When you create composite components, you should instantiate the controls inside the component's class file. Assuming that some of these controls have graphical assets, you must plan the layout of the controls that you are including, and set properties such as default values in your class file. You must also ensure that you import all the necessary classes that the composite component uses.
Because the class extends one of the base classes, such as UIComponent, and not a controls class like Button, you must instantiate each of the controls as children of the custom component and arrange them on the screen.
Properties of the individual controls are not accessible from the MXML author's environment unless you design your class to allow this. For example, if you create a component that extends the UIComponent class and uses a Button and a TextArea component, you cannot set the Button control's label text in the MXML tag because you do not directly extend the Button class.
This example component, called ModalText and defined in the file ModalText.as, combines a Button control and a TextArea control. You use the Button control to enable or disable text input in the TextArea control.
Custom components implement the createChildren() method to create children of the component, as the following example shows:
override protected function createChildren():void {
super.createChildren();
// Create and initialize the TextArea control.
if (!text_mc) {
text_mc = new TextArea();
...
text_mc.addEventListener("change", handleChangeEvent);
addChild(text_mc);
}
// Create and initialize the Button control.
if (!mode_mc) {
mode_mc = new Button();
...
mode_mc.addEventListener("click", handleClickEvent);
addChild(mode_mc);
}
}
The createChildren() method also contains a call to the addEventListener() method to register an event listener for the change event generated by the TextArea control, and for the click event for the Button control. These event listeners are defined within the ModalText class, as the following example shows:
// Handle events that are dispatched by the children.
private function handleChangeEvent(eventObj:Event):void {
dispatchEvent(new Event("change"));
}
// Handle events that are dispatched by the children.
private function handleClickEvent(eventObj:Event):void {
text_mc.editable = !text_mc.editable;
}
You can handle an event dispatched by a child of a composite component in the component. In this example, the event listener for the Button control's click event is defined in the class definition to toggle the editable property of the TextArea control.
However, if a child component dispatches an event, and you want that opportunity to handle the event outside of the component, you must add logic to your custom component to propagate the event. Notice that the event listener for the change event for the TextArea control propagates the event. This lets you handle the event in your application, as the following example shows:
<?xml version="1.0"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*">
<fx:Script>
<![CDATA[
import flash.events.Event;
function handleText(eventObj:Event)
{
...
}
]]>
</fx:Script>
<MyComp:ModalText change="handleText(event);"/>
</s:Application>
The following code example implements the class definition for the ModalText component. The ModalText component is a composite component that contains a Button control and a TextArea control. This control has the following attributes:
You cannot edit the TextArea control by default.
You click the Button control to toggle editing of the TextArea control.
You use the textPlacement property of the control to make the TextArea appear on the right side or the left side of the control.
Editing the textPlacement property of the control dispatches the placementChanged event.
You use the text property to programmatically write content to the TextArea control.
Editing the text property of the control dispatches the textChanged event.
Editing the text in the TextArea control dispatches the change event.
You can use both the textPlacement property or the text property as the source for a data binding expression.
You can optionally use skins for the up, down, and over states of the Button control.
The following is an example MXML file that uses the ModalText control and sets the textPlacement property to left:
<?xml version="1.0"?>
<!-- asAdvanced/ASAdvancedMainModalText.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*" >
<MyComp:ModalText textPlacement="left" height="40"/>
</s:Application>
You can handle the placementChanged event to determine when the ModalText.textPlacement property is modified, as the following example shows:
<?xml version="1.0"?>
<!-- asAdvanced/ASAdvancedMainModalTextEvent.mxml -->
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:MyComp="myComponents.*" >
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import flash.events.Event;
private function placementChangedListener(event:Event):void {
myEvent.text="placementChanged event occurred - textPlacement = "
+ myMT.textPlacement as String;
}
]]>
</fx:Script>
<MyComp:ModalText id="myMT"
textPlacement="left"
height="40"
placementChanged="placementChangedListener(event);"/>
<mx:TextArea id="myEvent" width="50%"/>
<mx:Label text="Change Placement" />
<mx:Button label="Set Text Placement Right"
click="myMT.textPlacement='right';" />
<mx:Button label="Set Text Placement Left"
click="myMT.textPlacement='left';" />
</s:Application>
The following example shows the ModalText.as file that defines this control:
package myComponents
{
// asAdvanced/myComponents/ModalText.as
// Import all necessary classes.
import mx.core.UIComponent;
import mx.controls.Button;
import mx.controls.TextArea;
import flash.events.Event;
import flash.text.TextLineMetrics;
// ModalText dispatches a change event when the text of the child
// TextArea control changes, a textChanged event when you set the text
// property of ModalText, and a placementChanged event
// when you change the textPlacement property of ModalText.
[Event(name="change", type="flash.events.Event")]
[Event(name="textChanged", type="flash.events.Event")]
[Event(name="placementChanged", type="flash.events.Event")]
/*** a) Extend UIComponent. ***/
public class ModalText extends UIComponent {
/*** b) Implement the class constructor. ***/
public function ModalText() {
super();
}
/*** c) Define variables for the two child components. ***/
// Declare two variables for the component children.
private var text_mc:TextArea;
private var mode_mc:Button;
/*** d) Embed new skins used by the Button component. ***/
// You can create a SWF file that contains symbols with the names
// ModalUpSkin, ModalOverSkin, and ModalDownSkin.
// If you do not have skins, comment out these lines.
[Embed(source="Modal2.swf", symbol="blueCircle")]
public var modeUpSkinName:Class;
[Embed(source="Modal2.swf", symbol="blueCircle")]
public var modeOverSkinName:Class;
[Embed(source="Modal2.swf", symbol="greenSquare")]
public var modeDownSkinName:Class;
/*** e) Implement the createChildren() method. ***/
// Test for the existence of the children before creating them.
// This is optional, but we do this so a subclass can create a
// different child instead.
override protected function createChildren():void {
super.createChildren();
// Create and initialize the TextArea control.
if (!text_mc)
{
text_mc = new TextArea();
text_mc.explicitWidth = 80;
text_mc.editable = false;
text_mc.text= _text;
text_mc.addEventListener("change", handleChangeEvent);
addChild(text_mc);
}
// Create and initialize the Button control.
if (!mode_mc)
{ mode_mc = new Button();
mode_mc.label = "Toggle Editing Mode";
// If you do not have skins available,
// comment out these lines.
mode_mc.setStyle('overSkin', modeOverSkinName);
mode_mc.setStyle('upSkin', modeUpSkinName);
mode_mc.setStyle('downSkin', modeDownSkinName);
mode_mc.addEventListener("click", handleClickEvent);
addChild(mode_mc);
}
}
/*** f) Implement the commitProperties() method. ***/
override protected function commitProperties():void {
super.commitProperties();
if (bTextChanged) {
bTextChanged = false;
text_mc.text = _text;
invalidateDisplayList();
}
}
/*** g) Implement the measure() method. ***/
// The default width is the size of the text plus the button.
// The height is dictated by the button.
override protected function measure():void {
super.measure();
// Since the Button control uses skins, get the
// measured size of the Button control.
var buttonWidth:Number = mode_mc.getExplicitOrMeasuredWidth();
var buttonHeight:Number = mode_mc.getExplicitOrMeasuredHeight();
// The default and minimum width are the measuredWidth
// of the TextArea control plus the measuredWidth
// of the Button control.
measuredWidth = measuredMinWidth =
text_mc.measuredWidth + buttonWidth;
// The default and minimum height are the larger of the
// height of the TextArea control or the measuredHeight of the
// Button control, plus a 10 pixel border around the text.
measuredHeight = measuredMinHeight =
Math.max(mode_mc.measuredHeight,buttonHeight) + 10;
}
/*** h) Implement the updateDisplayList() method. ***/
// Size the Button control to the size of its label text
// plus a 10 pixel border area.
// Size the TextArea to the remaining area of the component.
// Place the children depending on the setting of
// the textPlacement property.
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
// Subtract 1 pixel for the left and right border,
// and use a 3 pixel margin on left and right.
var usableWidth:Number = unscaledWidth - 8;
// Subtract 1 pixel for the top and bottom border,
// and use a 3 pixel margin on top and bottom.
var usableHeight:Number = unscaledHeight - 8;
// Calculate the size of the Button control based on its text.
var lineMetrics:TextLineMetrics = measureText(mode_mc.label);
// Add a 10 pixel border area around the text.
var buttonWidth:Number = lineMetrics.width + 10;
var buttonHeight:Number = lineMetrics.height + 10;
mode_mc.setActualSize(buttonWidth, buttonHeight);
// Calculate the size of the text
// Allow for a 5 pixel gap between the Button
// and the TextArea controls.
var textWidth:Number = usableWidth - buttonWidth - 5;
var textHeight:Number = usableHeight;
text_mc.setActualSize(textWidth, textHeight);
// Position the controls based on the textPlacement property.
if (textPlacement == "left") {
text_mc.move(4, 4);
mode_mc.move(4 + textWidth + 5, 4);
}
else {
mode_mc.move(4, 4);
text_mc.move(4 + buttonWidth + 5, 4);
}
// Draw a simple border around the child components.
graphics.lineStyle(1, 0x000000, 1.0);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
}
/*** i) Add methods, properties, and metadata. ***/
// The general pattern for properties is to specify a private
// holder variable.
private var _textPlacement:String = "left";
// Create a getter/setter pair for the textPlacement property.
public function set textPlacement(p:String):void {
_textPlacement = p;
invalidateDisplayList();
dispatchEvent(new Event("placementChanged"));
}
// The textPlacement property supports data binding.
[Bindable(event="placementChanged")]
public function get textPlacement():String {
return _textPlacement;
}
private var _text:String = "ModalText";
private var bTextChanged:Boolean = false;
// Create a getter/setter pair for the text property.
public function set text(t:String):void {
_text = t;
bTextChanged = true;
invalidateProperties();
dispatchEvent(new Event("textChanged"));
}
[Bindable(event="textChanged")]
public function get text():String {
return text_mc.text;
}
// Handle events that are dispatched by the children.
private function handleChangeEvent(eventObj:Event):void {
dispatchEvent(new Event("change"));
}
// Handle events that are dispatched by the children.
private function handleClickEvent(eventObj:Event):void {
text_mc.editable = !text_mc.editable;
}
}
}
This means that the compiler could not find the SWC file, or the contents of the SWC file did not list the component. Ensure that the SWC file is in a directory that Flex searches, and ensure that your xmlns property is pointing to the right place. Try moving the SWC file to the same directory as the MXML file and setting the namespace to "*" as the following example shows:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns="*">
For more information, see Flex compilers.
Ensure that the attribute is spelled correctly. Also ensure that it is not private.
Verify that the component was instantiated. One way to do this is to put a Button control and a TextArea control in the MXML application and set the text property to the ID for the component when the button is clicked. For example:
<!-- This verifies whether a component was instantiated. --> <zz:mycomponent id="foo"/> <mx:TextArea id="output"/> <mx:Button label="Print Output" click="output.text = foo.id;"/>
In some cases, helper classes are not ready by the time your component requires them. Flex adds classes to the application in the order that they must be initialized (base classes, and then child classes). However, if you have a static method that gets called as part of the initialization of a class, and that static method has class dependencies, Flex does not know to place that dependent class before the other class, because it does not know when that method is going to be called.
One possible remedy is to add a static variable dependency to the class definition. Flex knows that all static variable dependencies must be ready before the class is initialized, so it orders the class loading correctly.
The following example adds a static variable to tell the linker that class A must be initialized before class B:
public class A {
static function foo():Number {
return 5;
}
}
public class B {
static function bar():Number {
return mx.example.A.foo();
}
static var z = B.bar();
// Dependency
static var ADependency:mx.example.A = mx.example.A;
}
Verify that the measuredWidth and measuredHeight properties are nonzero. If they are zero or NaN, ensure that you implemented the measure() method correctly.
You can also verify that the visible property is set to true. If visible is false, ensure that your component called the invalidateDisplayList() method.
Navigation
Adobe, Adobe AIR, Adobe Flash and Adobe Flash Player are either registered trademarks or trademarks of Adobe Systems Incorporated in the United States and/or other countries and are used by permission from Adobe. No other license to the Adobe trademarks are granted.