Problem solved, see below
Question
I'm working in Flex Builder 3 and I have two ActionScript 3 classes (ABC and XYZ) and a Flex MXML project (main.mxml). I have an instance of XYZ as a property of ABC, and I want XYZ's properties to be visible ([Bindable]) in the Flex project in text controls.
Unfortunately, only prop3 and prop4 update when they are changed. I've entered the debugger to make sure that prop1 and prop2 change, but they are not being updated in the text controls.
Here's the code:
ABC.as
[Bindable]
public class ABC extends UIComponent {
/* Other properties */
public var xyz:XYZ = new XYZ();
/* Methods that update xyz */
}
XYZ.as
[Bindable]
public class XYZ extends Object {
private var _prop1:uint = 0;
private var _prop2:uint = 0;
private var _prop3:uint = 0;
private var _prop4:uint = 1;
public function get prop1():uint {
return _prop1;
}
public function set prop1(value:uint):void {
_prop1 = value;
}
/* getters and setters for prop2, prop3, and prop4 */
}
main.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:com="com.*" />
<com:ABC id="abc" />
<mx:Text text="{abc.xyz.prop1}" />
<mx:Text text="{abc.xyz.prop2}" />
<mx:Text text="{abc.xyz.prop3}" />
<mx:Text text="{abc.xyz.prop4}" />
</mx:Application>
Answer
It's not apparent from the small code snippets that I posted, but from within XYZ I was updating _prop3 and _prop4 using their setters. In constrast, I updated _prop1 and _prop2 through their private variables, not their setters. Thus, properties 1 and 2 were not dispatching update events.
It looks like your getters are returning voids. They should be returning uints, according to your field types.
Otherwise your code should be working fine. I've assembled and tested a working version with a Timer that sets all four values, so you can see all four updating:
Main.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" xmlns:local="*" creationComplete="onCreationComplete()">
<mx:Script>
<![CDATA[
private function onCreationComplete():void
{
var t:Timer = new Timer(1000);
t.addEventListener(TimerEvent.TIMER, t_tick);
t.start();
}
private function t_tick(event:TimerEvent):void
{
var i:uint = Timer(event.currentTarget).currentCount;
abc.xyz.prop1 = i;
abc.xyz.prop2 = i;
abc.xyz.prop3 = i;
abc.xyz.prop4 = i;
}
]]>
</mx:Script>
<local:ABC id="abc" />
<mx:VBox>
<mx:Text text="{abc.xyz.prop1}" />
<mx:Text text="{abc.xyz.prop2}" />
<mx:Text text="{abc.xyz.prop3}" />
<mx:Text text="{abc.xyz.prop4}" />
</mx:VBox>
</mx:Application>
ABC.as
package
{
import mx.core.UIComponent;
[Bindable]
public class ABC extends UIComponent
{
public var xyz:XYZ = new XYZ();
public function ABC()
{
super();
}
}
}
XYZ.as
package
{
[Bindable]
public class XYZ extends Object
{
private var _prop1:uint = 0;
private var _prop2:uint = 0;
private var _prop3:uint = 0;
private var _prop4:uint = 1;
public function XYZ()
{
super();
}
public function get prop1():uint {
return _prop1;
}
public function set prop1(value:uint):void {
_prop1 = value;
}
public function get prop2():uint {
return _prop2;
}
public function set prop2(value:uint):void {
_prop2 = value;
}
public function get prop3():uint {
return _prop3;
}
public function set prop3(value:uint):void {
_prop3 = value;
}
public function get prop4():uint {
return _prop4;
}
public function set prop4(value:uint):void {
_prop4 = value;
}
}
}
Have a look at those, plug things in and you should see it all come together. Post back if you have any questions. Cheers.
When you define your bindable sources through the use of getters and setters, the bindings don't seem to work. The solution is to declare a bindable event for your getter and dispatch the event in your setter:
[Bindable]
public class XYZ extends Object {
private var _prop1:uint = 0;
private var _prop2:uint = 0;
private var _prop3:uint = 0;
private var _prop4:uint = 1;
[Bindable(event="prop1Changed")]
public function get prop1():uint {
return _prop1;
}
public function set prop1(value:uint):void {
_prop1 = value;
dispatchEvent (new Event ("prop1Changed"));
}
/* getters and setters for prop2, prop3, and prop4 */
}
So whenever your private member changes, an event is dispatched that notifies any component linked to the getter that the property has changed.
Related
My Skin:
<s:DataGroup id="view1" width="100%" height="100%" itemRenderer="views.itemRenderers.BrickItemRenderer" dataProvider="{hostComponent.createArray()}">
<s:layout>
<s:TileLayout />
</s:layout>
</s:DataGroup>
My View for create object 'bricks'
[Bindable]
public function createArray():ArrayCollection
{
var dataBrick:ArrayCollection = new ArrayCollection();
var data:DataAboutBrick;
for (var x:int = 0; x < 5; x++)
for (var y:int = 0; y < 7; y++)
{
data = new DataAboutBrick();
data.x = 0;
data.y = 0;
data.color = colorBrick;
dataBrick.addItem(data);
}
return dataBrick;
}
Class DataAboutBrick for saved data about object:
public class DataAboutBrick extends Object
{
public function DataAboutBrick()
{
super();
}
public var x:int;
public var y:int;
public var color:uint = 0xFF0000;
public var id:String;
}
Mediator:
private function btnBricknew_clickHandler(event:BaseBrickEvent):void
{
view.colorBrick = model.currentColor;
}
Mediator change color all object when clicked on object. And need only change the color of the object to be pressed.
Assuming you have Brick and BrickSkin defined, you can define the functionality to change the color in BrickItemRenderer as below:
<?xml version="1.0"?>
<spark:DefaultComplexItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:spark="spark.skins.spark.*">
<fx:Script><![CDATA[
[Bindable("dataChanged")]
override public function set data(value:Object):void
{
super.data = value;
var brick:Brick = new Brick();
brick.colorBrick = value.color;
brick.addEventListener(MouseEvent.CLICK, brickClicked,false,0,true);
this.addElement(brick);
}
private function brickClicked(event:Event)
{
event.stopImmediatePropagation();
event.preventDefault();
data.color = 0x00FF00;// Please make sure your color variable defined in DataAboutBrick.as is Bindable.
}
]]></fx:Script>
</spark:DefaultComplexItemRenderer>
I want to create a custom reusable component by extending a spark Button class so that it has a checkbox and a label which says Show Image. When checkbox is selected an image will be displayed instead of the label. The Image path should be exposed as an API. How can we extend spark.components.Button to have it check box with labe or image (image path should be dynamic).
I tried to extend Button class as below but not sure how to create check box in it and how to pass image path as parameter to that.
package myClasses
{
import spark.components.Button;
public class ImageCheckBox extends Button
{
public function ImageButton()
{
super();
this.buttonMode = true;
}
}
}
I want to use the custom components something like below in application.
<myClasses:ImageCheckBox skinClass="mySkins.HelpButtonSkin" path="...."" label="Show Image" />
Something like this
package myClasses {
import flash.events.Event;
import spark.components.CheckBox;
import spark.components.Image;
import spark.components.Label;
import spark.components.supportClasses.SkinnableComponent;
public class ImageCheckBox extends SkinnableComponent {
[SkinPart(required=true)]
public var checkBox:CheckBox;
[SkinPart(required=true)]
public var labelComp:Label;
[SkinPart(required=true)]
public var image:Image;
private var pathChanged:Boolean = false;
private var _path:String;
public function get path():String {
return _path;
}
public function set path(value:String):void {
if (_path != value) {
_path = value;
pathChanged = true;
invalidateProperties();
}
}
private var labelChanged:Boolean = false;
private var _label:String;
public function get label():String {
return _label;
}
public function set label(value:String):void {
if (_label != value) {
_label = value;
labelChanged = true;
invalidateProperties();
}
}
public function ImageCheckBox() {
super();
setStyle("skinClass", ImageCheckBoxSkin);
}
override protected function partAdded(partName:String, instance:Object):void {
super.partAdded(partName, instance);
if (instance == checkBox) {
checkBox.addEventListener(Event.CHANGE, checkBoxChangeHandler)
}
else if (instance == labelComp) {
labelComp.text = label;
}
else if (instance == image) {
image.source = path;
}
}
override protected function partRemoved(partName:String, instance:Object):void {
super.partRemoved(partName, instance);
if (instance == checkBox) {
checkBox.removeEventListener(Event.CHANGE, checkBoxChangeHandler)
}
}
override protected function getCurrentSkinState():String {
return checkBox.selected ? "selected" : super.getCurrentSkinState();
}
override protected function commitProperties():void {
if (labelChanged) {
labelChanged = false;
labelComp.text = label;
}
if (pathChanged) {
pathChanged = false;
image.source = path;
}
super.commitProperties();
}
private function checkBoxChangeHandler(event:Event):void {
invalidateSkinState();
}
}}
And skin
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009"
xmlns:mx="library://ns.adobe.com/flex/mx">
<!-- host component -->
<fx:Metadata>
<![CDATA[
/**
* #copy spark.skins.spark.ApplicationSkin#hostComponent
*/
[HostComponent("myClasses.ImageCheckBox")]
]]>
</fx:Metadata>
<s:states>
<s:State name="normal"/>
<s:State name="selected"/>
</s:states>
<s:HGroup width="100%" height="100%" gap="3">
<s:CheckBox id="checkBox"
verticalCenter="0"
height="100%"/>
<s:Label id="labelComp"
verticalCenter="0" verticalAlign="middle"
width="100%" height="100%"
visible.normal="true" includeInLayout.normal="true"
visible.selected="false" includeInLayout.selected="false"/>
<s:Image id="image"
verticalCenter="0"
width="100%" height="100%"
fillMode="scale" scaleMode="letterbox"
visible.normal="false" includeInLayout.normal="false"
visible.selected="true" includeInLayout.selected="true"/>
</s:HGroup>
I need to be able to call a method from a component located under the main application in Flex 4. Can anyone tell me please how to do this without using FlexGlobals please?
Sample code is attached. Thanks in advance.
// TestApp.mxml (application)
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="initApp()">
<fx:Script>
<![CDATA[
import com.TestComp;
import mx.managers.PopUpManager;
public function myMethod():void
{
// do something
}
protected function initApp():void
{
var popUp:TestComp = new TestComp();
PopUpManager.addPopUp(popUp, this, true);
}
]]>
</fx:Script>
</s:WindowedApplication>
// TestComp.mxml (component)
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="400" height="300">
<fx:Script>
<![CDATA[
private function doSomething(event:MouseEvent):void
{
// call to myMethod() in TestApp.mxml
}
]]>
</fx:Script>
<s:Button click="doSomething(event)" label="Click Me"/>
</s:Group>
This is bad design. You should provide a callback function or an event listener.
// TestComp.mxml
<mx:Metadata>
[Event(name="doSomethingEvent", type="flash.events.Event")]
</mx:Metadata>
<mx:Script><![CDATA[
private function doSomething(event:MouseEvent):void
{
this.dispatchEvent(new Event("doSomethingEvent"));
}
]]></mx:Script>
// TestApp.mxml
protected function initApp():void
{
var popUp:TestComp = new TestComp();
popUp.addEventListener("doSomethingEvent", myMethod);
PopUpManager.addPopUp(popUp, this, true);
}
private function myMethod(event: Event): void
{
// do something
}
And this is a callback example:
// TestComp.mxml
public var doSomethingCallback: Function;
private function doSomething(event:MouseEvent):void
{
doSomethingCallback.call();
}
// TestApp.mxml
protected function initApp():void
{
var popUp:TestComp = new TestComp();
popUp.doSomethingCallback = myMethod;
PopUpManager.addPopUp(popUp, this, true);
}
private function myMethod(): void
{
// do something
}
Easiest option?
Take out the click handler from the button in TestComp.
In your main app, add a listener to TestComp (if it's a direct child of the main application) or itself (if TestComp is further down the display list) for MouseEvent.CLICK. In the handler, test to see if the event's target is the TestComp either through == if you've got a direct reference, or through "is" if not.
That's the least amount of effort from what you have just now, still relies on (bubbling) events, and is more "correct"
I do agree with splash that it's bad design, but the following should work
//in TestApp
protected function initApp():void
{
var popUp:TestComp = new TestComp(this);
PopUpManager.addPopUp(popUp, this, true);
}
//in TestComp
private var app:TestApp;
public function TestComp(app:TestApp)
{
this.app = app;
}
private function doSomething(event:MouseEvent):void
{
// call to myMethod() in TestApp.mxml
app.myMethod();
}
or you could do it this way
//in TestApp
protected function initApp():void
{
var popUp:TestComp = new TestComp(this);
popUp.addEventListener( 'test' , eventListener );
PopUpManager.addPopUp(popUp, this, true);
}
private function eventListener(event:Event):void
{
//in which case myMethod doesn't need to be public
myMethod();
}
//in TestComp
private function doSomething(event:MouseEvent):void
{
dispatchEvent( new Event('test') );
}
Here is the error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at view::ScoreBoard/setChipCount()[C:\Flex Builder 3\StackOverflowQuestion\src\view\ScoreBoard.as:32]
at model::MainDeckScoreBoard()[C:\Flex Builder 3\StackOverflowQuestion\src\model\MainDeckScoreBoard.as:21]
at model::MainDeckScoreBoard$cinit()
at global$init()[C:\Flex Builder 3\StackOverflowQuestion\src\model\MainDeckScoreBoard.as:5]
at main()[C:\Flex Builder 3\StackOverflowQuestion\src\main.mxml:13]
at _main_mx_managers_SystemManager/create()
at mx.managers::SystemManager/initializeTopLevelWindow()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3188]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::docFrameHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3064]
at mx.managers::SystemManager/docFrameListener()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:2916]
Here is main.mxml:
<?xml version="1.0"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="initApp()"
>
<mx:Script>
<![CDATA[
import model.MainDeckScoreBoard;
public var _mainDeckScoreBoard:MainDeckScoreBoard = MainDeckScoreBoard.instance;
private function initApp():void {
this.addChild(_mainDeckScoreBoard);
}
]]>
</mx:Script>
</mx:Application>
Here is MainDeckScoreBoard.as:
package model {
import view.ScoreBoard;
[Bindable]
public dynamic class MainDeckScoreBoard extends ScoreBoard {
/** Storage for the singleton instance. */
private static const _instance:MainDeckScoreBoard = new MainDeckScoreBoard( SingletonLock );
/** Provides singleton access to the instance. */
public static function get instance():MainDeckScoreBoard {
return _instance;
}
public function MainDeckScoreBoard( lock:Class ) {
super();
// Verify that the lock is the correct class reference.
if ( lock != SingletonLock ) {
throw new Error( "Invalid Singleton access. Use MainDeckScoreBoard.instance." );
}
this.setChipCount("0");
}
} // end class
} // end package
class SingletonLock {
} // end class
Here is ScoreBoard.as:
package view {
import mx.containers.HBox;
import view.ScoreBoardLabel;
import view.ChipCountContainer;
import view.CardRankList;
public dynamic class ScoreBoard extends HBox {
/** The chip count. */
public var _chipCount:ChipCountContainer;
public function ScoreBoard() {
super();
this.width = 489;
this.height = 40;
this.setStyle("horizontalScrollPolicy", "off");
}
override protected function createChildren():void {
super.createChildren();
if(!_chipCount) {
_chipCount = new ChipCountContainer();
this.addChild(_chipCount);
}
}
public function setChipCount(labelText:String):void {
_chipCount._chipCountLabel.text = labelText;
invalidateDisplayList();
}
}
}
Here is ChipCountContainer.as:
package view {
import mx.containers.Canvas;
public dynamic class ChipCountContainer extends Canvas {
/** The label. */
public var _chipCountLabel:ChipCountLabel;
public function ChipCountContainer() {
super();
this.width = 20;
this.height = 20;
}
override protected function createChildren():void {
super.createChildren();
if(!_chipCountLabel) {
_chipCountLabel = new ChipCountLabel();
this.addChild(_chipCountLabel);
}
}
}
}
I've methodically moved things around and waved the invalidate Display List incense while performing a create Children dance but I've only succeeded in completely confusing myself. I've searched the Flex libraries for similar constructions, and it looks OK to me, but I guess I'm just not getting the concept.
I think you're confusing the order of instantiation. Namely, if you want to use setChipCount after the children of the component have been initialized, you should wait for the initialize event to fire, i.e.:
public dynamic class MainDeckScoreBoard extends ScoreBoard {
...
public function MainDeckScoreBoard( lock:Class ) {
super();
// Verify that the lock is the correct class reference.
if ( lock != SingletonLock ) {
throw new Error( "Invalid Singleton access. Use MainDeckScoreBoard.instance." );
}
// wait for the children to be created
addEventListener(FlexEvent.INITIALIZE, onInitialize);
}
// executes when the children of this component have been created
private function onInitialize(event:FlexEvent):void {
this.setChipCount("0");
}
} // end class
For a more detailed explanation of the component instantiation lifecycle, this adobe doc may be helpful:
http://livedocs.adobe.com/flex/3/html/help.html?content=components_06.html
Here is the error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at mx.styles::StyleProtoChain$/initProtoChainForUIComponentStyleName()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\styles\StyleProtoChain.as:72]
at mx.core::UIComponent/http://www.adobe.com/2006/flex/mx/internal::initProtoChain()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:7469]
at mx.core::UIComponent/regenerateStyleCache()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:7690]
at mx.core::UIComponent/http://www.adobe.com/2006/flex/mx/internal::addingChild()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:5239]
at mx.core::UIComponent/addChild()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:4955]
at mx.controls.listClasses::ListBase/createChildren()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\controls\listClasses\ListBase.as:3103]
at mx.core::UIComponent/initialize()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:5370]
at mx.core::UIComponent/http://www.adobe.com/2006/flex/mx/internal::childAdded()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\UIComponent.as:5267]
at mx.core::Container/http://www.adobe.com/2006/flex/mx/internal::childAdded()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:3305]
at mx.core::Container/addChildAt()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:2217]
at mx.core::Container/addChild()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\core\Container.as:2140]
at model::MessageBoard()[C:\Documents and Settings\dbabbitt\My Documents\Flex Builder 3\ListExample\src\model\MessageBoard.as:56]
at model::MessageBoard$cinit()
at global$init()[C:\Documents and Settings\dbabbitt\My Documents\Flex Builder 3\ListExample\src\model\MessageBoard.as:7]
at main()[C:\Documents and Settings\dbabbitt\My Documents\Flex Builder 3\ListExample\src\main.mxml:56]
at _main_mx_managers_SystemManager/create()
at mx.managers::SystemManager/initializeTopLevelWindow()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3188]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::docFrameHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3064]
at mx.managers::SystemManager/docFrameListener()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:2916]
Here is main.mxml:
<?xml version="1.0"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
>
<mx:Script>
<![CDATA[
import model.MessageBoard;
public var _messageBoard:MessageBoard = MessageBoard.instance;
]]>
</mx:Script>
</mx:Application>
Here is MessageBoard.as:
package model {
import mx.containers.VBox;
import mx.controls.Label;
[Bindable]
public class MessageBoard extends VBox {
/** The message list title. */
private var _messageTitle:Label;
/** The maximum message count. */
private var _maxMessageCount:int = 4;
/** Storage for the singleton instance. */
private static const _instance:MessageBoard = new MessageBoard( SingletonLock );
/** Provides singleton access to the instance. */
public static function get instance():MessageBoard {
return _instance;
}
/**
* Constructor
*
* #param lock The Singleton lock class to pevent outside instantiation.
*/
public function MessageBoard( lock:Class ) {
super();
// Verify that the lock is the correct class reference.
if ( lock != SingletonLock ) {
throw new Error( "Invalid Singleton access. Use MessageBoard.instance." );
}
_messageTitle = new Label();
_messageTitle.text = "Message Board";
this.addChild(_messageTitle);
}
} // end class
} // end package
class SingletonLock {
} // end class
Maybe you could school me in how to keep null object references out of complex Classes?
Thanx
Dave
It looks like the error may be coming from the call to the addChild() method in the constructor of your MessageBoard class. You may very well be invoking this method too early in the UIComponent lifecycle. I would recommend overriding the createChildren() method and adding your child element(s) there.
override protected function createChildren():void
{
super.createChildren();
_messageTitle = new Label();
_messageTitle.text = "Message Board";
this.addChildAt(_messageTitle, 0);
}