|
|
||||||||||
| Ultrashock Tutorials > Flash MX 2004 > Introduction to the v2 component architecture | ||||||||||
|
||||||||||
|
|
Introduction to the v2 component architecture |
|
||||||||
I. Setup and Core ImplementationUIObject is a subclass of MovieClip, therefore, you must make sure to have a symbol in your library for this class as you would if you were building a regular MovieClip class. Usually if you were just building a regular MovieClip with an associated class, you would create that symbol and associate them using the library. When your component inherits from UIObject though, you will need to do some extra work to ensure all the architecture is included in your component. You can find details on setting up a component FLA here. To start, we will write the most basic structure of a component class, this the class associated to your library symbol. <example>
import mx.core.UIObject;
class com.rewindlife.controls.TextBox extends UIObject
{
};
</example>
The above example demonstrates an empty component, one that doesn’t do anything. This file must be saved in the appropriate folder structure with a filename of “TextBox.as”. NOTE: If you have used or plan to use Metadata tags at some point, you should also note the importance of the “import mx.core.UIObject” line. In theory, you could use the fully-qualified class name to reference UIObject. However, the Flash MX 2004 environment requires that you import the class you are extending if you plan for it to inherit the Metadata Tag definitions correctly. Because of this, it is good practice to always import the parent class. Metadata tags are used to describe what capabilities our component contains. This will not be discussed in this tutorial, but you can find more details about Metadata Tags at http://livedocs.macromedia.com/flash/mx2004/main/05_cre29.htm a. Required PropertiesWhen using UIObject as a base class, your component must declare and define properties with certain values. symbolName The “symbolName” property should be set to the linkage name of the library symbol that your component will use. This property is used by the createClassObject() method to find the appropriate symbol in the library when attaching a new instance of your component onto the stage at runtime. If the corresponding MovieClip(component symbol) in your library has a linkage of “YourComponent”, that is the value you would set the property to.
<example> NOTE : A word of caution about linkage ID’s for your component. Macromedia uses the class name throughout their components as the linkage ID. Although this works fine, you may run into difficulties if you decide to follow the same naming convention for your components when your linkage IDs collide with other symbols in the library. In the prior version of Flash, component developers usually distinguished their symbol identifiers by prefixing them with their own identifier. In Flash MX 2004, you can use that method or a fully qualified class name as the linkage ID. For example, if you were developing your own button component, you could use “usButton”, or “com.ultrashock.controls.Button”. I prefer the fully-qualified class-name method but that is purely personal. I would definitely advise against using just “Button” as that is already used by the Macromedia button component and you most likely want your component to happily co-exist with the Macromedia one. It also is very likely that Macromedia will recommend the fully packaged method in the future too. symbolOwner The “symbolOwner” property is also required by createClassObject(), and is used when the component is attached dynamically. The component framework associates the symbolOwner with the component’s symbol using Object.registerClass(). You always set the value to the fully qualified class name. <example> public static var symbolOwner:Object = com.rewindlife.controls.TextBox; </example> className The “className” property is not actually required but you should always specify it anyways so I put it under this section. This property is used by the getStyle() method implementation in the component architecture. If styles are set in _global.[className] then getStyle() will recognize that and return those values when appropriate. Most of the time the value of this property is the unqualified class name although you may also decide to use a more unique identifier if you feel other components use the same className. For example, if you were creating a new button component, you would probably want it to be unique and would use something like “myButton” as the className. <example> private var className:String = "YourComponent"; </example> clipParameters & mergedClipParameters When a component initializes, if it has any setters which are called before the component has completed initializing, those setters may not function properly under Flash Player 6 in certain circumstances. If your component is a Flash 7+ only component, then you can ignore this requirement. Generally though it is good practice to always include it. <example>
/*
Updated code with the required properties and packed into com.rewindlife.controls
*/
import mx.core.UIObject;
class com.rewindlife.controls.TextBox extends UIObject
{
public static var symbolName:String = "com.rewindlife.controls.TextBox";
public static var symbolOwner:Object = com.rewindlife.controls.TextBox;
private var className:String = "TextBox";
//You specify each setter property
private var clipParameters:Object = {someSetter:1,someOtherSetter:1};
//Make sure to include the static call to mergeClipParameters(). What this function
//does is merge the clipParameters of two classes (components)
//You pass it a reference to this class’s clipParameters, and the parent class’s
//clipParameters.
private static var mergedClipParameters :Boolean =
UIObject.mergeClipParameters(TextBox.prototype.clipParameters,
UIObject.prototype.clipParameters);
};
</example>
You actually don’t need to bother with the details of what is happening behind the scenes as the component architecture will take care of it for us. If you are interested in the details of what happens during initialization, read on. To understand exactly why the below code is required and what it exactly does, it is first important to understand what the issue is. The issue surrounds instantiating MovieClips dynamically via code in Flash Player 6. In Flash Player 6 if you use createClassObject() or attachMovie() to instantiate your component and pass it an initObject, and the initObject includes values for setter properties, those setter methods won’t be called. Instead the property is set on the instance and soon after wiped out by the setter function becoming available. This causes property values to be lost. <example>
createClassObject(mx.controls.Button,"myButton",1,{label:"Hello World"});
//the label setter property will never be called
</example>
This issue has been fixed in Flash Player 7, but because there are times when we want to create components that will be published to Flash Player 6, the component architecture provides us with a solution. The solution involves you providing a list of setter properties that the component contains (clipParameters). This informs the component architecture which properties it will need to handle for situations where the calls may fail (This is handled when you call super.init()). Since sometimes you have base classes with their own setter properties, you use mergeClipParameters() to merge the clipParameters object of the current class with the parent’s. Next, since the value for the properties are set before the setter is available, the component architecture will store those values for later use. Finally, when the setter is available, the component architecture will automatically call the setter and pass in the stored values. b. Initialization, required methods and a bit of theory Several things happen during the initialization process of a component. Having good knowledge of what happens at this point in the life of your component is crucial for building an efficient component. From the previous tutorial, you might remember that your component had init(), createChildren(), and draw() methods. What you probably didn’t realize is how these methods were being automatically called. When a class is a descendant of UIObject, these methods are called when a component is instantiated. You will need to understand in what order they are called, and what each one does. Component initialization order: ![]()
I. init() The init method is usually the first method you will implement in your components. In the init() method you must always call super.init(). That will call UIObject’s init() implementation which will setup the component framework. This method is only called once during initialization and never again. Another purpose of this method is to create member variables. All instance variables that need to be instantiated for a component instance should be done at this point. MovieClips and TextFields (Sub-Object) are not instantiated during the init() method. II. createChildren() In this method, you will create and configure all sub-objects (MovieClip subclasses). Sub-objects are graphical nested objects like other Components, MovieClips, or TextFields. Ideally, when creating sub-objects, you would use createClassObject() when appropriate, and createLabel(). You could use attachMovie() and createTextField(), but it is advisable that within the component you use the methods provided by the framework. createClassObject() is a convenient way of referencing components through their class name and createLabel(), provided by UIObject as a good way to create TextField’s that inherit their styling from your component automatically. You are probably already familiar with createClassObject(), createLabel() is covered in more detail further on in this tutorial. In this method you may also want to set up some initial property values for your component that do not change during the course of your components life. This method is called only once during initialization and never again. III. Invalidation Invalidation is one of the core features that the architecture provides. During initialization, the component is invalidated for the first time, which in turn calls the draw() method. You may be wondering why we need invalidation or what its benefits are. The best way to explain this is to take an example. Let’s say that a user sets the values of multiple properties and calls several methods in a single frame, each of which needs to in turn call the draw() method to make sure the component’s view state is up to date with the new state information. Normally, the draw() function would be called each time a property is set or a method is called and will cause your draw() function to be called several time within a single frame. In Flash, rendering is only updated once per frame, so there is no need for the view state to update immediately or several times within a single frame. Instead it is better to take those multiple calls and make a single call to draw() after all the calls have been made. Not only is it wasteful but it causes performance issues if every property that is set will immediately call the draw function. That is the problem that invalidate solves. With invalidation, if each setter property or method called stored the update value for later use, and called invalidate() within a single frame, the component architecture will consolidate all those calls within a frame into a single call to the draw() function in the next frame, thus making the process more efficient. The draw() method will then make use of those stored value’s (usually referred to as the model state) to update the view state. The invalidate() method is available to any sub-class of UIObject and is responsible for initiating the invalidation of the component. It is also good to keep in mind that invalidate() is, at times, called by other things happening that your own code may not be responsible. An example is support for styling, which the component architecture gracefully handles for us. Whenever a user sets a style that your component needs to know about, the component architecture will implicitly invalidate your component, thus causing the draw() function to be called. ![]() Without invalidation ![]() With invalidation With that knowledge, you should never call the draw() function directly, instead just call invalidate(). There may be some times when you need the component to be redrawn immediately, if so you can tell the component to redraw immediately by calling redraw(true). This will cause the draw() function to be called immediately. So now you may wonder why you are still not calling the draw() function directly if you need it to be called immediately, but instead calling redraw(true). I have to admit that I may have purposely misled you on one minor detail. The process of invalidation requires that the invalidateFlag property be set to true before the draw() is allowed to be called and that an event is dispatched to all listeners after the draw() function has been called. This is all taken care of by calling invalidate() or redraw(true) for us. We will see later what other events other than “draw” are implemented for us, you also may want to check out what exactly happens by studying the implementation of the redraw() function in UIObject. IV. draw() The draw method is usually where the core implementation of updating your view state for your component is found. From the above discussion, we know that the draw() method is called whenever a component is invalidated/redrawn. When writing this method, it is important to keep in mind that this is usually the last method to be called when your component is initializing and is also called every time your component’s state changes (invalidate). Because of this, you have to make sure this method is efficient and capable of being called more than once without your component being affected. Other Important Methods to Implement size() Although this method is not called during instantiation, you must usually implement it in your component and always consider it when writing a component. The size method is called implicitly when the setSize() method is called. Your component implements the size() method which only handles the sizing of the component and nothing else. This ensures that your component’s code executes efficiently by keeping its sizing code separate from its drawing. If you are creating a small component that does not require much logic, you may decide to place all the sizing implementation in the draw function and just call invalidate() from within the size() method. There is nothing wrong with this, but if your component is more complicated, you will want to handle some of the sizing needs and call invalidate() for the component to redraw, or implement all your sizing logic in the size() method. Finally, you should never call the size() method from within the draw() method as this will cause an infinite frame loop. n the size method you have access to two properties, the __width and __height properties. These properties represent the value’s the user has requested the width and height be set to. You should use these two properties when in need of the new width and height property values. You also have access to the oldWidth, and oldHeight properties, which will give you the value of the width and height properties before the requested change.
|
||||||||||
©2004 Ultrashock.com - All rights reserved |