I'm used to building applications using pure AS3. I always pass dependencies into the constructor of classes I make, but this method seems to not work out well for Flex MXML views.
It seems like I should define setters on the MXML class, which map to attributes in the tag/class instantiation. But using this method I cannot specify which properties are required and in what order I expect them etc.
What is the preferred method to give a Flex view it's dependencies?
A pattern I've used a couple times was to define a public init() method in the MXML which takes the argument that would normally have gone in the constructor. Then, whatever instantiates that MXML component is responsible for calling init() before using it.
Another way would be to create setters for the properties like you mentioned. In those setters store the values that are passed, then call invalidateProperties(). Then override the commitProperties() method in the MXML, and the first time that's called do your initialization (and maybe throw an exception if the needed properties weren't provided). As long as the user of your class sets all the properties before adding the component to the display list then it will work fine (I don't believe commitProperties() is called until after a component is added to the display list, either by being declared in MXML or by passing it to an addChild() call).
I haven't ever tried that second method (only just thought of it now), but it should work.
You can't force people to use parameters in the constructor, but you can force then to set properties before adding the item to the stage.
How's this:
<mx:HBox
added="{checkProps()}">
<mx:Script>
<![CDATA[
public var prop1:String;
public var prop2:String;
private function checkProps():void
{
if( !( prop1 && prop2 ) )
{
throw new Error( "Prop1 and prop2 must be set before "+
"adding this to the stage" );
}
}
]]>
</mx:Script>
</mx:HBox>
Realistically, if you're interested in forcing people to do something before adding it to the display list, then you're going to have to do something like this anyway.
There are a few things in Flex that you can override or listen to that are really important.
FlexEvent.CREATION_COMPLETE - set an eventListener for this (I usually do it in the constructor but you could do it in MXML as creationComplete attribute) and it acts like your constructor. Use getters and setters to pass through references to your dependencies as MXML attributes and store them locally then inside this handler you will apply them.
override protected function createChildren - this is called when it is time to add display list items to the component. You shouldn't do that during the constructor or creationComplete handlers. It is always tempting to addChild outside of this function but Adobe best practice is only to do so directly in this function.
override protected function updateDisplayList - this is where your drawing logic should happen (if there is any) or positioning/alpha/rotation/etc of children. This will get called if a CSS property changes, a child changes size or position or anything else that the Flex framework thinks may cause you to want to redraw the screen. You can force an updateDisplayList to get called by calling invalidateDisplayList
override protected function commitProperties - this is called when the dataProvider for a class is changed. Any time data within the component means you want to update internal data structures this should be called. You can force this to be called using invalidateProperties.
FlexEvent.ADDED_TO_STAGE - If you need to know when the component is actually added to the stage you can listen for this. In practice I can't remember ever actually using it ...
Always remember to call the super equivalents -- forgetting to do so will often cause the component to fail to appear at all (this happens to me at least 4 or 5 times a project). Also be aware that if you first invalidateProperties and then commitProperties and then invalidateDisplayList and then updateDisplayList you may see some jerkyness ... that is, invalidateDisplayList as soon as you know you'll want a redraw to avoid delay.
Also don't get too invested in Flex 3 since Flex 4 is just around the corner and it is quite a bit different. I have a feeling that much of this will no longer apply in the new component framework (names Spark).
edit a typical class stub:
package
{
import mx.containers.Canvas;
import mx.events.FlexEvent;
public class TestComponent extends Canvas
{
public function TestComponent()
{
super();
addEventListener(FlexEvent.CREATION_COMPLETE, init);
}
// acts as constructor
private function init(event:FlexEvent):void
{
// might as well be clean
removeEventListener(FlexEvent.CREATION_COMPLETE, init);
// do init stuff here
}
override protected function createChildren():void
{
super.createChildren();
// do any addChilds here that are necessary
}
override protected function commitProperties():void
{
super.commitProperties();
// update internal state when data changes
}
override protected function updateDisplayList(w:Number, h:Number):void
{
super.updateDisplayList(w, h);
// do any drawing, positioning, rotation etc.
}
}
}
Related
In Flex 3 I have a SWFLoader:
<mx:SWFLoader id="player" source="http://youtube.com/v/..." />
and after some time I invoke player.unloadAndStop(). And I always get this error:
ReferenceError: Error #1056: Cannot create property __tweenLite_mc on _swftest_mx_managers_SystemManager.
What does it mean and how to avoid this?
UPD: AIR 2 doesn't have this problem
Maybe try the Loader class? I'm not sure if it will help but I do all my loading via ActionScript. Generally speaking, I do "heavyWeight" programming/logic/cotrol stuff in ActionScript and leave Flex for more simplistic layout code. That is, flex puts things in place and actionscript controls it all. When loading clips in our Flex 3 project, I have control code along the lines of:
import flash.display.Loader;
private var loader:Loader;
public function init() {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, loadFailed);
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, loadCompleted);
}
with calls to things like:
//here, pop returns a string like "/path/to/movie.swf"
loader.load(new URLRequest(clipsToPlay.pop()));
...
loader.unload();
contained in functions like:
private function loadNextClip():void {
if(clipsToPlay.length == 0) {
dispatchEvent(new PlayBackCompleteEvent(PlaybackCompleteEvent.ALL));
return;
}
loader.load(new URLRequest(clipsToPlay.pop()));
}
private function loadCompleted(event:Event):void {
currentClip = event.target.content as MovieClip;
loader.unload();
displayClip();
}
private function displayClip():void {
applyEffects();
currentClip.addEventListener(Event.ENTER_FRAME, monitorForCompletion);
addChild(currentClip);
}
I'm not sure if Loader can be used instead of SWFLoader but if so I hope that helps you or someone else, in some way...
EDIT:
I just looked it up and mx.controls.SWFLoader and flash.display.Loader have very similar functionality. I'd try using Loader, as prescribed above, and see if it fixes the problem. You could probably initialize the loader via MXML, too, but I wouldn't recommend it since it's not a visual component, I think it's better to let MXML handle visual things while ActionScript handles logical things.
well, I have a combobox which I have bind his selectedItem property to a value object object, like this
<fx:Binding source="styles_cb.selectedItem.toString()" destination="_uniform.style"/>
<fx:Declarations>
<fx:XML id="config_xml" xmlns="" source="config.xml" />
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<mx:ComboBox x="66.15" y="63.85" editable="false" id="styles_cb" dataProvider="{config_xml.styles.style}" />
the value object is a custom class with some setters and getters, and I want to set a property based of the value of the selectedItem of the combo, so inside the value object I have something like this
[Bindable]
public function set style(value:String):void
{
_style = value;
trace(value);
}
my problem is that each time I change the combobox selection which in fact change the style property of the value object it does it 3 times, if I trace the value of the setter it actually do the trace 3 times, why?? how can I avoid this? I'm doing something wrong? or there is a better way to do it, please help, thanks for any help
It's common for data binding expressions to fire many times more than one would expect. I don't know the exact reason. If this causes issue for your app, then don't bind the source directly to the target, instead use invalidation. Bind the source to set a flag, stylesSelectedItemChanged and call invalidateProperties(). Then override commitProperties() and inside your commitProperties(), check if stylesSelectedItemChanged is true, and if so, propagate the new value forward to the destination and reset the flag to false. Be sure to also call super.commitProperties() or else many things would break.
Invalidation is extremely common in the Flex framework, every component uses it internally, and it helps a lot with these kinds of issues.
wow!!, some times writing a question let you think about it twice and let you find the answer by yourself, so I find my own solution, in the documentation said I can make all the properties of an object bindables if I put [Bindable] in the class declaration, so I did it like this
[Bindable]
[RemoteClass(alias='Uniform')]
public class Uniform extends Object implements IEventDispatcher
however when I was trying to dispatch an event in the setters I found in the docs that I must add the event name like this
[Bindable("styleChanged")]
public function get style():String
{
return _style;
}
public function set style(value:String):void
{
_style = value;
dispatchEvent(new Event("styleChanged"));
}
now I found that doing this, mark the property with a double bind and that was making me set the property many times, hugg!, but now I know I can avoid using the second [Bindable] and still the event get dispatch, so now I wonder why I need to use [Bindable("styleChanged")] in the first place if I still can dispacth the event with only [Bindable] and the dispatch method?, weird
hope this help to someone
I have a BaseComponentClass that I am using as the class that all my custom components extend.
For some reason, none of my custom components show up at runtime. I am not getting any compile or runtime errors either.
I am implementing all the protected UIComponent methods.
My code looks like this:
public class BaseComponentClass extends UIComponent
{
public function BaseComponentClass()
{
super();
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
}
override protected function createChildren():void
{
super.createChildren();
for (var i:uint=0; i < super.numChildren; i++)
{
var childObj:DisplayObject = super.getChildAt(i);
addChild(childObj);
}
}
override protected function commitProperties():void
{
super.commitProperties();
}
override protected function measure():void
{
super.measure();
}
}
Then I use it as the Base class in my mxml custom components somewhat like this:
<local:BaseComponentClass xmlns:local="local.com.*" xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Button id="btn" label="My Button" />
</local:BaseComponentClass>
The Button never shows up at runtime.
Apparently you want to add child objects to your BaseComponent.
Why don't you inherit from a class that supports this functionality, such as Box or Canvas?
Did you try to set a width and height to that component?
As default, UIComponent width and height are set to 0.
you can see the default values documentation here, and change it accordingly on your base component:
http://livedocs.adobe.com/flex/3/langref/mx/core/UIComponent.html
Good luck!
I'm not sure of the best practices, so do research this before implementing it, but if it were me I would create an array property (children is available from uicomponent), then somewhere in the class use the defaultProperty metadata tag ( [DefaultProperty("children")] ).
If you were to debug your code and put a breakpoint in the for loop, you would never hit the addChild code. infact, goto definition (f3) of createChildren (uiComponent.createChildren) and you will find it empty. You have to explicitly call addChild in the default property setter that you create. Your better bet, if you're always going to use this component as a container-like class, is to just extend Container. F3 down into those classes to get a feel for best practices.
What I am trying to accomplish to to get financial data in my Flex Datagrid to be color-coded--green if it's positive; red if it's negative. This would be fairly straightforward if the column I want colored was part of the dataProvider. Instead, I am calculating it based on two other columns that are part of the dataProvider. That would still be fairly straightforward because I could just calculate it again in the ItemRenderer, but another part of the calculation is based on the value of a textBox. So, what I think I need to be able to do is send the value of the textBox to the custom ItemRenderer, but since that value is stored in the main MXML Application, I don't know how to access it. Sending it as a parameter seems like the best way, but perhaps there's another.
Here is the current code for my ItemRenderer:
package {
import mx.controls.Label;
import mx.controls.listClasses.*;
public class PriceLabel extends Label {
private const POSITIVE_COLOR:uint = 0x458B00 // Green
private const NEGATIVE_COLOR:uint = 0xFF0000; // Red
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
/* Set the font color based on the item price. */
setStyle("color", (data.AvailableFunding >= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
}
}
(data.AvailableFunding doesn't exist)
So does anyone know how I would go about accomplishing this?
You may want to look into ClassFactory from the Flex APIs:
This allows you to set a prototype Object with arbitrary types / values each of which will be passed to the item renderer. From the sample:
var productRenderer:ClassFactory = new ClassFactory(ProductRenderer);
productRenderer.properties = { showProductImage: true };
myList.itemRenderer = productRenderer;
The above code assumed that "ProductRenderer" has a public property called "showProductImage" which will be set with a value of "true."
Ah, so I knew about outerDocument but not parentDocument. I was able to just use parentDocument.*whatever I want from the main App and I can access it as long as it's public.
Example:
setStyle("color", (parentDocument.availableFunding >= 0) ? POSITIVE_COLOR : NEGATIVE_COLOR);
Sweet! :)
You can access the value of the TextBox directly, if you need to, by using the static Application.application object, which is accessible from anywhere in your application.
For example, if you wanted the renderers to be notified when the value of the TextInput control changes, you could do something like this (from within your ItemRenderer, and where myTextInput is the ID of the control defined in your main MXML class):
<mx:Script>
<![CDATA[
import mx.core.Application;
private function creationCompleteHandler(event:Event):void
{
Application.application.myTextInput.addEventListener(TextEvent.TEXT_INPUT, handleTextInput, false, 0, true);
}
private function handleTextInput(event:TextEvent):void
{
if (event.currentTarget.text == "some special value")
{
// Take some action...
}
}
]]>
</mx:Script>
With this approach, each item-renderer object will be notified when the TextInput's text property changes, and you can take appropriate action based on the value of the control at that time. Notice as well that I've set the useWeakReference argument to true in this case, to make sure the listener assignments don't interfere unintentionally with garbage collection. Hope it helps!
There's another technique, which, while it initially feels a little hacky is perhaps less cumbersome and cleaner in actual use.
It involves the little-observed fact that an event dispatch is, of course, synchronous and the event object can be treated as a value object populated by any event handler.
i.e. the ItemRenderer can do something like:
...
var questionEvt:DynamicEvent = new DynamicEvent('answerMeThis', true, true);
if (dispatchEvent(questionEvt))
{
if (questionEvent.answer == "some value")
....
With a corresponding handler somewhere up the view hierarchy above the renderer that has a listener on the event and does something like:
function handleAnswerMeThis(event:DynamicEvent):void
{
event.answer = "another value";
event.dataHelper = new DataHelperThingy();
}
etc.
It need not be a DynamicEvent - I'm just using that for lazy illustrative purposes.
I vote up for cliff.meyers' answer.
Here's another example on setting the properties of an itemRenderer from MXML by building a function that wraps a ClassFactory around the itemRenderer class and that injects the necessary properties.
The static function:
public static function createRendererWithProperties(renderer:Class,
properties:Object ):IFactory {
var factory:ClassFactory = new ClassFactory(renderer);
factory.properties = properties;
return factory;
}
A simple example that adds a Tooltip to each item in a list:
<mx:List dataProvider="{['Foo', 'Bar']}" itemRenderer="{createRendererWithProperties(Label, {toolTip: 'Hello'})}"/>
Reference:
http://cookbooks.adobe.com/post_Setting_the_properties_of_an_itemRenderer_from_MXM-5762.html
You use outerDocument property. Please see the fx:Component reference.
You could create an 'AvailableFunding' static variable in the ItemRenderer and then set it in the parent document.
public class PriceLabel extends Label {
public static var availableFunding:int;
...
...
SetStyle("color", (PriceLabel.availableFunding >= 0) ? NEGATIVE_COLOR : POSITIVE_COLOR);
}
In your parent document, set it when your text box gets updated
PriceLabel.availableFunding = textBox.text;
Obviously it'll be the same value for every ItemRenderer but it looks like that might be what you're doing anyway.
I like to override the set data function of the item renderer to change the renderer when the data provider changes as shown here
When you override the function you could cast the object to your object to make the availableFunding property available.
To access the text box you could try creating a public property and binding the property to the text box in the mxml file:
public var textVar:String;
<mx:itemRenderer>
<mx:Component>
<customrenderer textVar="{txtBox.text}" />
</mx:Component>
</mx:itemRenderer>
Nice ClassFactory Example here
See this example:
itemRenderer="{UIUtils.createRenderer(TextBox,{iconSrc:IconRepository.linechart,headerColor:0xB7D034,subHeaderColor:0xE3007F,textColor:0x75757D})}"
im writting an actionScript class to handle my web service calls. When i retrieve a result i want to call a setter method in my main mxml application. My problem is that i dont know how to access the methods in the actionScript section of my main mxml class from my actionscript class, any ideas?
David is right -- while you can access the public members of your Application.mxml object statically and from anywhere in your application, design-wise that's a bit of a no-no. It's better to strive for loose coupling between your objects, and the way that's done in the Flex idiom is generally to extend EventDispatcher and to dispatch events. So for example, your WebService wrapper might look something like this:
public class MyWrapperClass extends EventDispatcher
{
[Event(name="webserviceComplete", type="flash.events.Event")]
public function MyWrapperClass(target:IEventDispatcher=null)
{
super(target);
}
private function handleWebServiceLoadComplete(event:ResultEvent):void
{
dispatchEvent(new Event("webserviceComplete"));
}
public function doWork():void
{
// Load the service, etc., and ultimately call handleWebServiceLoadComplete()...
}
}
... and your Main.mxml file like this:
<mx:Script>
<![CDATA[
private function app_creationComplete(event:Event):void
{
var myWrapper:MyWrapperClass = new MyWrapperClass();
myWrapper.addEventListener("webserviceComplete", mywrapper_webServiceComplete, false, 0, true);
myWrapper.doWork();
}
private function mywrapper_webServiceComplete(event:Event):void
{
// Do the work you would've otherwise done in the public method
}
]]>
</mx:Script>
In this case, the end result is the same -- completing the web-service load triggers the function in Main.mxml. But notice how mywrapper_webServiceComplete() is declared privately -- it's not called directly by MyWrapperClass. Main.mxml simply subscribes (with addEventListener()) to be notified when MyWrapperClass is finished doing its work, and then does its own work; MyWrapperClass knows nothing about the details of Main.mxml's implementation, nor does Main.mxml know anything about MyWrapperClass other than that it dispatches a webserviceComplete event, and exposes a public doWork() method. Loose coupling and information hiding in action.
Good luck!
If your class is an UIComponent added to the component tree, then you can use its parentApplication attribute. Otherwise, use the static Application.application attribute, but only after the application initialization has completed. Earlier than that, the field is null. Private fields and methods obviously cannot be accessed. Elements declared in the MXML part with explicit ids are public.
Adding such a call creates a rigid binding, though. You might want to consider dispatching an event instead, and handling this event in the main application.
In case anyone has the same problem:
mx.core.FlexGlobals.topLevelApplication.YOUR_FUNCTION
is the syntax to access public functions within the main.mxml.