Help me understand data Binding
When I create a variable in a class:
[Bindable] private var _name:String;
and then generate getters and setters, I get:
private var _name:String;
[Bindable]
public function get name():String
{
return _name;
}
public function set name(value:String):void
{
_name = value;
}
Why does it generate the tag '[Bindable]' only on the get function?
To me, it would seem that it should be on the set function, as I would want to know when the
value changes, not when the value is just read.
What might help to understand what is going on here is the code that the MXML compiler will generate for you when you make something [Bindable]. The MXML compiler wraps your [Bindable] property in it's own getter/setter. It does this so that the wrapper setter method can dispatch a "propertyChange" event when a new value is set. This event notifies the parties binding to the property that the value has changed.
Getters/setters in Actionscript are considered to be properties of the object (they are not methods of the object). So it doesn't matter whether your annotate the getter or the setter as [Bindable], the generated code does the right thing.
It's worth noting that you can avoid the generated code and optimize the situation by dispatching your own event when your property changes. To do this, your [Bindable] metadata tag needs to include the event name that will be dispatched when the property changes:
private var _name:String;
[Bindable("nameChanged")]
public function get name():String
{
return _name;
}
public function set name(value:String)
{
if (_name == value)
return;
_name = value;
dispatchEvent(new Event("nameChanged"));
}
Because the bindable metadata contains an event string, no extra code is generated. Note, the compiler won't warn you if you forget to dispatch the event from the setter. In fact, you can dispatch your custom binding event from anywhere in your class (this can be useful with functions that are bindable).
Related
In Flex (Flash Builder 4), I need a way to know if something in an array collection has changed.
I have a custom object:
[Bindable]
public var _myobject:MyObject;
It's basically just a class containing a bunch of different String and Number properties.
I need a reliable way to know if any of the properties have been changed. For example, I am binding the properties to a user interface (fields), but it's also possible for some of the properties to change through code.
Is there a way to do this? I found ChangeWatcher, but that looks like it only looks at a single simple property, such as a String or Number. I need to watch or detect changes in all the properties in my object, hopefully without having to add ChangeWatcher events to every property. Is this possible?
You're probably better off just dispatching binding events on the specific properties you want bindable. Better yet, dispatch a custom binding event, so that all of the things that are bound don't have to filter for "is this the property I care about?" It's really easy with Flash Builder 4.5 to do this, just select your variable name and press Ctrl-1, select "Create getter and setter," select getter and setter and check "Bindable" and "create custom event."
This will create code for you that looks something like this:
private var _yourProperty:String;
[Bindable (event='yourPropertyChange')]
public function get yourProperty():String {
return _yourProperty;
}
public function set yourProperty(value:String):void {
if (value !=== _yourProperty) {
_yourProperty = value;
dispatchEvent(new Event('yourPropertyChange'));
}
}
This will be much less verbose and more performant than the code that Flash Builder generates for you behind the scenes when you just use the Bindable tag on its own.
If you use defined classes as VO/DAO and apply the [Bindable] tag to the class, this will do binding on all properties for you (so long as they are read/write).
Anonymous object detection is difficult at best, let alone adding additional headaches of loosing compiler type checking.
Super basic example: - the key is to tie it to the dispatcher, so internally it can send out the PropertyChangeEvent.
[Bindable]
public class Actor extends EventDispatcher
{
public var fName:String;
public var lName:String;
public var age:uint;
public function get displayName():String
{
return lName +', '+ fName;
}
public function Actor()
{
super();
}
}
public class BindableDictionary extends EventDispatcher {
public function BindableDictionary() {
super();
}
public var dictionary:Dictionary = new Dictionary();
[Bindable("change")]
public function get(key:Object):Object {
return dictionary[key];
}
public function put(key:Object, value:Object):void {
dictionary[key] = value;
dispatchEvent(new Event(Event.CHANGE));
}
}
maybe this class will give you some new idea
I'd like to add a derived property to flex:
private var a:int;
private var b:int;
[Bindable]
public function get derived():int
{
return a+b;
}
However, derived won't update when I change a or b. Is there a better (faster) way to make derived update than giving a and b setter methods which invalidate it?
Edit: added keyword "get". This makes more sense now, I hope.
Your code does not create a property, it creates a method. Methods cannot be Bindable, only properties. This approach should work for you:
private var _a:int;
public function get a():int{
return _a
}
public function set a(value:int):void{
_a = a;
dispatchEvent(new Event('derivedChanged'));
}
private var _b:int;
public function get b():int{
return _b
}
public function set b(value:int):void{
_b = b;
dispatchEvent(new Event('derivedChanged'));
}
[Bindable(event="derivedChanged")]
public function get derived():int
{
return a+b;
}
I wrote code n the browser; so there may be minor syntax errors.
You can use [Bindable(event="propertyChanged")] on your derived function.
You should also make your derived function a getter.
I should work because flex uses PropertyChangeEvent.PROPERTY_CHANGE to bind variables, by automatically creating getters and setters and dispatching a PropertyChangeEvent. The modification of either a or b will automatically invalidate the result of derived.
I felt like this was a common problem, so I wrote some code to help out with it. You can add what your getter is dependent on in the Bindable metadata. So:
[Bindable(event="derivedChanged",dependentProperty="a")]
[Bindable(event="derivedChanged",dependentProperty="b")]
public function get derived():int
{
return a+b;
}
It's custom code, written to use Parsley's metadata processing, but you could use it without Parsley--it just would be a normal method call and wouldn't look as nice.
Check it out: http://frishy.blogspot.com/2011/06/binding-dependencies.html
-Ryan
I have a class called which is called ChartInfo,and it has a getter and setter methods as:
[Bindable]
public function set isShowingPower(b:Boolean):void
{
_isShowingPower = b;
hasChanged();
}
public function get isShowingPower():Boolean
{
return _isShowingPower;
}
The _isShowingPower is the property.
However,if I want to set the _isShowingPower from another class:
_chartInfo.isShowingPower(false)
It will always give error like:
1195: Attempted access of inaccessible method isShowingPower through a reference with static type components.charting:ChartInfo.
Could anyone give an idea?Thanks a lot.
to access a setter and/or getter you have to do it like a var.
in your case it should be
_chartInfo.isShowingPower = false;
Setters are used like properties, so _chartInfo.isShowingPower = false;
In an AIR application, I have a private variable and a setter:
private var _saveResult
public function set saveResult( result:String ):void
{
_saveResult = result;
dispatchEvent( new resultUpdatedEvent( _saveResult ));
}
The first time that I set "saveResult" the event fires. But it will never fire again unless I restart the application.
If I change the setter to:
public function set saveResult( result:String ):void
{
_saveResult = result;
if ( result != null)
{
dispatchEvent( new resultUpdatedEvent( _saveResult ));
}
}
The problem goes away, I can set the variable many times and the event fires every time.
My question:
Am I doing something wrong here? If not, can anyone explain to me whats happening? If so, what SHOULD I be doing?
Thanks!
It looks like you're constructing your event incorrectly. The first parameter of an Event object should always be a string. So in this case you'd want to always use the same string so you could listen for the event. What does your resultUpdatedEvent class look like? You'll want it to look something like this:
package myEvents
{
import flash.events.Event;
public class PropertyChangeEvent extends Event
{
public static const PROPERTY_CHANGE:String = "propertyChange";
public var result:String = "";
// Public constructor.
public function PropertyChangeEvent (type:String,
result:String="") {
// Call the constructor of the superclass.
super(type);
// Set the new property.
this.result= result;
}
// Override the inherited clone() method.
override public function clone():Event {
return new PropertyChangeEvent(type, result);
}
}
}
That way, when you go to dispatch your event, you can construct the event as follows:
new PropertyChangeEvent(PropertyChangeEvent.PROPERTY_CHANGE, result);
That way, you're listening for the event "PropertyChangeEvent.PROPERTY_CHANGE", which never changes. The problem is now your event listener is probably listening for an event represented by the string saved in result, and obviously, this changes after the first time it's set, so there's no way to assign a listener to it.
For more information about how events work in AS3: http://livedocs.adobe.com/flex/3/html/help.html?content=events_02.html
Per the comments...
There was no event dispatcher problem.
I misdiagnosed the problem, the REAL problem was that if you have a [Bindable] property and you use a setter, and you set it for the current value, flex will ignore it. SO, you have several choices:
1) give the getter and setter different names. Seems like a "bad idea" but it does fix the problem.
2) remove [Bindable] from either the class (my problem) or the property. If the class does not implement IEventDispatcher, you will need to do so. You can simply "extends Sprite" to see it work, but that seems like a "bad idea" as a solution, so I implemented IEventDispatcher per the example at the end of this page: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/events/IEventDispatcher.html
3) I am sure that there is a way to get around this bug, but I don't actually NEED the class to be [Bindable] so I did not find a work around.
Is it possible to create an instance of the main MXML and use it inside the ActionScript class.
public var obj:classname= new classname();
When i try to call a components id through obj.textfieldID... it does not...
though obj is an instance of the classname.mxml.
I'm not sure but it's possible that control instances are generated as protected. Try adding a public property/method that wraps access to your textfield. You should then be able to access that public member from outside the MXML file.
FYI, though, it's better practice to use binding to populate MXML components. You can add a binding via code using BindingUtils.bindProperty. Even then, though, you would bind a property on the MXML file (either in an <mx:Script> or in 'code-behind' via inheritance) and then have your textField bind to the property:
private var _displayText : String;
[Bindable] // only required on get
public function set displayText(value : String) : void
{
return _displayText;
}
public function set displayText(value : String) : void
{
_displayText = value;
}
Then your field would be declared:
<mx:Label id="displayNameLabel" text="{displayName}" />
Now displayNameLabel.text will automatically change everytime you change your (public) displayName property.