What event is fired when all SkinSparts are available in a SkinnableComponent? - apache-flex

I'm (slowly) learning Flex 4 and working on skinning a custom component that extends SkinnableComponent. The component is all in ActionScript and essentially looks like this:
package components
{
import spark.components.supportClasses.SkinnableComponent
[SkinState("normal")]
[SkinState("over")]
[SkinState("selected")]
public class AccountSummary extends SkinnableComponent
{
[Bindable]
public var itemIndex:int = 0;
[Bindable]
public var accountName:String = "";
[Bindable]
public var accountNumber:String = "";
[Bindable]
public var currentBalance:String = "";
[SkinPart(required="true")]
public var lblAccountName:Label;
[SkinPart(required="true")]
public var lblCurrentBalance:Label;
[SkinPart(required="true")]
public var lblAccountNumber:Label;
[SkinPart(required="true")]
public var lblLastUpdated:Label;
public function AccountSummary()
{
super();
lblAccountName.text = accountName;
lblCurrentBalance.text = currentBalance;
lblAccountNumber.text = "Acc: " + accountNumber;
lblLastUpdated.text = "Last Updated: ";
}
override protected function getCurrentSkinState():String
{
return "normal";
}
}
}
The issue I'm having is on NULL REFERENCES in the Constructor for the various SkinParts, because they're not created yet. I'm trying to find out when is the best time to access them to assign their .text values. I know I can override partAdded() and add the .text value as each part is added, but I'd rather just listen for some magic event that is dispatched when they're all available.
I'm not sure whether or not it matters, but the accountName, accountNumber variables are assigned by using a repeater with a dataProvider on the host component. I'm not sure if that too has something to do with when the data is available to the AccountSummary component - which may also needed to be waited on. I was able to successfully use FlexEvent.CREATION_COMPLETE to assign all my .text values to the SkinParts, but is that the right event/best practice? Any help would be greatly appreciated!

You need to read up on the Flex 4 Component LifeCycle, specifically the piece about component instantiation. Nothing will be created at the time the constructor executes. The values you are referencing (accountName, lblCurrentBalance. etc...) will not have moved beyond the default states either.
You want to set your default values in the partAdded() method. Rewrite your constructor code to something like this:
public function AccountSummary()
{
super();
}
override protected function partAdded(partName : String, instance: Object):void{
super.partAdded(partName, instance);
if(instance == lblAccountName){
lblAccountName.text = accountName;
else if(instance == lblCurrentBalance){
} else if (instance == lblCurrentBalance){
lblCurrentBalance.text = currentBalance;
} else if (instance == lblAccountNumber){
lblAccountNumber.text = "Acc: " + accountNumber;
} else if (instance == lblLastUpdated){
lblLastUpdated.text = "Last Updated: ";
}
}
If you're a new programmer, you may want to devote some time to learning some basic programming concepts. This ActionScript 3 Guide may help you get started. If you have experience in other languages, but are new to Flex, I strongly suggest spending a couple of days reading through the full flex documentation to help get your head around it.

Related

Prism6 shared service and dependency property

I use Prism6 + Unity container for desktop application developing.
This is a long-read, sorry. So I ask at top: Prism SetProperty() function is not rising property changed event if input value is Unity singleton. And I understand why: because input value and save value have same reference to singleton instance. RaisePropertyChanged() don't help in this situation.
Long-read is statring...
So, I have a dependency property in my UserControl component:
public static readonly DependencyProperty WorksheetDataProperty =
DependencyProperty.Register("WorksheetData", typeof(WorksheetDataModel), typeof(SheetUserControl),
new PropertyMetadata(new WorksheetDataModel(), WorksheetDataPropertyChanged));
public WorksheetDataModel WorksheetData {
get { return (WorksheetDataModel)GetValue(WorksheetDataProperty); }
set { SetValue(WorksheetDataProperty, value); }
}
private void WorksheetDataPropertyChanged(WorksheetDataModel worksheetData) {
if (worksheetData == null)
return;
SheetGrid.Model.ActiveGridView.BeginInit();
this.ClearWorksheetModel();
this.ResizeWorksheetModel();
SheetGrid.Model.ActiveGridView.EndInit();
}
private static void WorksheetDataPropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e) {
((SheetUserControl)d).WorksheetDataPropertyChanged((WorksheetDataModel)e.NewValue);
}
It's important for me to invoke actions from WorksheetDataPropertyChanged() function.
And scheme without shared service (singleton) is working well: this function is called.
But now I want to share data between several modules. How I see it: I have some "parent" module, which load\save data from storage and shared this data with several other modules, which can modificate shared data, but can't save it.
And EventAggregator is not convenient for me: I don't want to create copies of data and then collect it again after modifications.
So I register my "shared service" as singleton:
_container.RegisterInstance(new WorksheetDataModel());
Now I can load data from database in "parent" viewmodel to singleton object created in previous step:
var data = _container.Resolve<WorksheetDataModel>();
data.Header = args.Header;
data.User = args.User;
data.RowHeader = new WorksheetRowHeader(_model.ReadRowHeader(data.Header.WshCode));
data.ColHeader = new WorksheetColHeader(_model.ReadColHeader(data.Header.WshCode));
data.Cells = _model.ReadCells(data.Header.WshCode);
Further, I notify child viewmodels about new data in singleton:
data.OnDataChanged?.Invoke();
And now most important code from child viewmodel.
In delegate handler I "apply" new value:
WorksheetData = _container.Resolve<WorksheetDataModel>();
WorksheetData is:
private WorksheetDataModel _worksheetData;
public WorksheetDataModel WorksheetData {
get { return _worksheetData; }
set { SetProperty(ref _worksheetData, value); }
}
And problem in this line:
set { SetProperty(ref _worksheetData, value); }
It works only once at first call, because _worksheetData is null. But then refernce of _worksheetData (pointer) setted to singleton and in all next call value and _worksheetData are identical for SetProperty() and, as result, it just quit.
I tried next code:
set {
SetProperty(ref _worksheetData, value);
RaisePropertyChanged("WorksheetData")
}
But no effect. WorksheetDataPropertyChanged() callback in UserControl component is not calling.
So, I don't know now how to better share some data between several modules.
Thanks for any advice.
WorksheetData does not change, the contents of the WorksheetDataModel instance change.
So to update your bindings,
either WorksheetDataModel implements INotifyPropertyChanged and/or uses INotifyCollectionChanged-implementing collections
or you let the view model listen to WorksheetDataModel.OnDataChanged and raise its own PropertyChanged to update all bindings to WorksheetData.
Example:
private WorksheetDataModel _worksheetData;
public WorksheetDataModel WorksheetData
{
get { return _worksheetData; }
set
{
if (_worksheetData != null)
_worksheetData.OnDataChanged -= DataChangedHandler;
SetProperty(ref _worksheetData, value);
if (_worksheetData != null)
_worksheetData.OnDataChanged += DataChangedHandler;
}
}
private void DataChangedHandler( object sender, DataChangedEventArgs args )
{
RaisePropertyChanged( nameof( WorksheetData ) );
}

flex inheritance class share

//Base.as
public class Base
{
private var _foo:String;
[Bindable]
public function set foo(value:String):void
{
_foo = value;
}
public function get foo():String
{
return _foo;
}
/*
Many many setter/getter, methods, events
*/
}
//Control.as
public class MyControl extends Group
{
public function MyControl()
{
}
}
//Window.as
public class MyWindow extends spark.components.Window
{
public function MyWindow()
{
}
}
//Module
public class MyModule extends spark.modules.Module
{
public function MyModule()
{
}
}
I want to expose (friendly) Base properties, methods and events on the other classes. Something like this:
var window:MyWindow = new MyWindow();
window.foo = 'Hello World!';
var module:MyModule = new MyModule();
module.foo = 'bar';
<namespace:MyControl foo="Hello World!"/>
I don't want define all the properties in each class because they are many and the same for all of them.
Ideally would define something like:
public class MyControl extends Group, Base
{
public function MyControl()
{
}
}
(I know it can't be done.)
Thanks!
UPDATE:
Thanks again!
Maybe this clarify more my need... On business layer I have a variable called processID (and businessID, operationID, localityID, etc.) what be passed to Window from Menu, and Window passes it to Module. On Module Container, I have a CustomComponent what query database using this variable as parameter. This applied for all (almost) Components on Module. These variables are defined as level business layer, then I define a Class to store and manage these variables (and some related methods operating with these variables using business logic), so I can make a standalone class (or library) for every environment to reusing my common components. The idea is... insert a new CustomComponent and set these variables via mxml, like this:
<custom:MyCustomComponent id="zzz" processID="{processID}" businessID="{businessID}"/>
Module has the business logic for set (o not) any of the variables.
Otherwise, I would have to implement different logic for the CustomComponent (and Module) for read parent's variables and define these variables only in MyWindow (using composite pattern).
You can get your answer from following link -
http://flexinonroids.wordpress.com/2009/05/27/flex-3-dynamically-loading-components-at-runtime/
http://thecomcor.blogspot.in/2007/11/adobe-flex-dynamically-loading-classes.html
Or you can follow below approach -
1) Create an Interface as base
2) Extend your class with interface
3) Load class at runtime with SWFLoader.loaderContext.applicationDomain.getDefinition method
Thanks,
Varun
You can place your classes that require friendly access in the same package as your Base class, and define private fields without any access modifier( it is equivalent to internal modifier).
Otherwise, you can define your namespace like that:
namespace my_internal;
and then define class members like that:
my_internal var _foo:String;
after that, those members will be hidden for all code, except for code that contains
use namespace my_internal;
You can read more here:
http://help.adobe.com/en_US/ActionScript/3.0_ProgrammingAS3/WS5b3ccc516d4fbf351e63e3d118a9b90204-7f9e.html#WS5b3ccc516d4fbf351e63e3d118a9b90204-7f91
However, using 'friend access' can be an evidence of bad design, so if I were you I'd think twice before defining namespaces.
Update:
pseudo-superclass 1:
package proxy
{
public class Simple1
{
public var x:int;
public var y:int;
}
}
pseudo-superclass 2:
package proxy
{
import mx.controls.Alert;
public class Simple2
{
public var name:String = 'noname';
public function doAlert():void{
Alert.show(name);
}
//not normal method to replace 'this' with proxy
Simple2.prototype.doCrossClass = function doCrossClass():void{
Alert.show(''+(Number(this['x'])+Number(this['y'])));
}
}
}
Code for testing the result (looks as what you are expecting?):
var mega:Mega = new Mega();
mega.x = 100;
mega.y = 200;
mega.name = 'Multiple inheritance';
mega.doAlert();
mega.doCrossClass(); //300
And now pseudo-subclass with multiple inheritance:
package proxy
{
import flash.utils.Proxy;
import flash.utils.flash_proxy;
public dynamic class Mega extends Proxy
{
public function Mega()
{
super();
}
public var superArray:Array = [new Simple1(), new Simple2()];
flash_proxy override function getProperty(name:*):*{
for each(var superClass:Object in superArray){
if( name in superClass){
return superClass[name];
}
}
throw new Error('no such property');
}
flash_proxy override function setProperty(name:*, value:*):void{
for each(var superClass:Object in superArray){
if( name in superClass){
superClass[name] = value;
return;
}
}
throw new Error('no such property');
}
flash_proxy override function callProperty(name:*, ...args):*{
for each(var superClass:Object in superArray){
if( name in superClass){
var f:Function = superClass[name] as Function;
return f.apply(this, args);
}
}
throw new Error('no such function');
}
}
}
You can also want to use javascript-like class construction(i.e. just using simple Object and assigning properties and functions to it in any combinations you want).

How to trigger a function only once in case of a mouseEvent

I am trying to make a simple mp3 player using flash. The songs are loaded using an XML file which contains the song list. I have "play" button with the instance name "PlayBtn". I have an actionscript file named "playctrl", the content of which are listed below:
package classes
{
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.events.*;
import flash.events.MouseEvent;
import flash.net.URLRequest;
public class playctrl
{
private var MusicLoading:URLRequest;
private var music:Sound;
private var sc:SoundChannel;
private var currentSound:Sound;
private static var CurrentPos:Number;
private var xml:XML;
private var songlist:XMLList;
private static var currentIndex:Number;
public function playctrl()
{
music = new Sound();
currentSound= music;
CurrentPos = 0;
currentIndex = 0;
}
public function success(e:Event):void
{
xml = new XML(e.target.data);
songlist = xml.song;
MusicLoading = new URLRequest(songlist[0].file);
music.load(MusicLoading);
}
public function playSong(e:Event):void
{
if(sc != null)
sc.stop();
sc = currentSound.play(CurrentPos);
trace("HELLO !!!");
}
}
}
I have a second file named "play.as", the content of which is listed below:
import classes.playctrl;
var obj:playctrl = new playctrl();
var XMLLoader:URLLoader = new URLLoader(); //XML Loader
XMLLoader.addEventListener(Event.COMPLETE, obj.success);
XMLLoader.load(new URLRequest("playlist.xml"));
PlayBtn.addEventListener(MouseEvent.CLICK, obj.playSong);
However on clicking the play button, I notice that the function playSong() is called 7-8 times(check by printing an error msg. inside the function) resulting in overlapped audio output and the player crashing as a result. The function should be called only once when the MouseEvent.CLICK is triggered. Please help ...
interestingly, sound object doesn't have a built-in "isPlaying" boolean property (strange), so you could just create your own.
var isPlaying:Boolean
function playSong():void
{
if(!isPlaying)
sound.play();
}
function stopSong():void
{
if(isPlaying)
{
channel.stop();
isPlaying = false;
}
just a note: by convention, class names are capitalized camel case while instance names are uncapitalized camel case. so your playctrl.as class file should (or could) be PlayCtrl.as, and your PlayBtn instance should (or could) be playBtn.
Edit:
The title of your question is a bit misleading, the answer I gave you is a solution to the question expressed in the title.
Looking at your code, I would look at separating the concerns, on one hand you want to load the song data, on the other hand you want to control the sounds. I would implement separate classes for each concern. If you create a separate class for your player control, you'll be able to dispatch event within that class without the event bubbling all over your app and calling your functions several times.
//Previous answer
You could do this by implementing a Boolean that would be set when the sound is stopped or played.
In any case here's another way to filter unwanted clicks
private function playSong(event:MouseEvent ):void
{
// set up a conditional to identify your button ,
// here's an example...
if( event.currentTarget.name is "PlayBtn" )
{
//do whatever
//then...
event.stopImmediatePropagation();
}
}
This being said, in your case , it sounds like a bit of a quick fix since a MouseEvent shouldn't trigger the play function several times...
It would make sense to debug your code in order to understand why several events are dispatched after a Mouse click
private var _isPlaying:Boolean;
public function playSong(e:Event):void
{
if(sc != null)
{
sc.stop();
_isPlaying = false;
}
if( !_isPlaying )
{
sc = currentSound.play(CurrentPos);
_isPlaying = true;
trace("HELLO !!!");
}
}

Order of calls to set functions when invoking a flex component

I have a component called a TableDataViewer that contains the following pieces of data and their associated set functions:
[Bindable]
private var _dataSetLoader:DataSetLoader;
public function get dataSetLoader():DataSetLoader {return _dataSetLoader;}
public function set dataSetLoader(dataSetLoader:DataSetLoader):void {
trace("setting dSL");
_dataSetLoader = dataSetLoader;
}
[Bindable]
private var _table:Table = null;
public function set table(table:Table):void {
trace("setting table");
_table = table;
_dataSetLoader.load(_table.definition.id, "viewData", _table.definition.id);
}
This component is nested in another component as follows:
<ve:TableDataViewer width="100%" height="100%" paddingTop="10" dataSetLoader="{_openTable.dataSetLoader}"
table="{_openTable.table}"/>
Looking at the trace in the logs, the call to set table is coming before the call to set dataSetLoader. Which is a real shame because set table() needs dataSetLoader to already be set in order to call its load() function.
So my question is, is there a way to enforce an order on the calls to the set functions when declaring a component?
The Flex documentation mentions (somewhere, I can't find it al the moment) that the order of initialisation of properties set in MXML is undefined. That is, sometimes dataSetLoader might have its value set first and then table, or sometimes the other way round.
To get around this you can use the Flex invalidation methods like invalidateProperties() and invalidateDisplayList() to wait until all of your properties are set and then perform your processing all at once.
As an example, here is how you might handle your issue.
Note that we move the call to the _dataSetLoader.load(...) method to the commitProperties() method, when we know we have both the table and dataSetLoader values:
[Bindable]
private var _dataSetLoader:DataSetLoader;
private var dataSetLoaderChanged:Boolean = false;
public function get dataSetLoader():DataSetLoader {return _dataSetLoader;}
public function set dataSetLoader(dataSetLoader:DataSetLoader):void{
trace("setting dSL");
_dataSetLoader = dataSetLoader;
dataSetLoaderChanged = true;
invalidateProperties();
}
[Bindable]
private var _table:Table = null;
private var tableChanged:Boolean = false;
public function set table(table:Table):void {
trace("setting table");
_table = table;
tableChanged = true;
invalidateProperties();
}
override protected function commitProperties():void
{
super.commitProperties();
if (tableChanged || dataSetLoaderChanged)
{
if (_dataSetLoader != null)
{
_dataSetLoader.load(_table.definition.id, "viewData", _table.definition.id);
}
tableChanged = false;
dataSetLoaderChanged = false;
}
}

How do I create a Hierarchical Cursor from the dataProvider of an AdvancedDataGrid?

In a previous application that I had written, I had a Class that extended AdvancedDataGrid (ADG). It contained the following code:
package
{
public class CustomADG extends AdvancedDataGrid
{
....
// This function serves as the result handler for a webservice call that retrieves XML data.
private function webServiceResultHandler(event:ResultEvent):void
{
var resultXML:XML = new XML(event.result);
dataProvider = new HierarchicalData(resultXML.children);
}
....
public function setOpenNodes(maxDepth:int = 0):void
{
var dataCursor:IHierarchicalCollectionViewCursor = dataProvider.createCursor();
while (dataCursor.current != null)
{
if (dataCursor.currentDepth < maxDepth)
dataProvider.openNode(dataCursor.current);
dataCursor.moveNext();
}
dataProvider.refresh();
}
}
}
In this implementation, the function setOpenNodes() worked fine - it did exactly what I intended it to do - pass it a number, and open all nodes in the dataProvider at or below that level.
Now, I am creating a new ADG Class and want to reproduce this functionality:
package view
{
import mx.collections.IHierarchicalCollectionViewCursor;
public class ReportADG extends AdvancedDataGrid
{
public function ReportADG()
{
super();
}
public function setOpenNodes(maxDepth:int = 0):void
{
var dataCursor:IHierarchicalCollectionViewCursor =
dataProvider.createCursor();
while (dataCursor.current != null)
{
if (dataCursor.currentDepth < maxDepth)
dataProvider.openNode(dataCursor.current);
dataCursor.moveNext();
}
dataProvider.refresh();
}
}
}
The dataProvider is set in the parent component:
<view:ReportADG id="reportADG" dataProvider="{reportData}" />
reportData is set in another file:
reportData = new HierarchicalData(resultXML.children);
However, I am getting runtime errors:
TypeError: Error #1034: Type Coercion failed: cannot convert ListCollectionViewCursor#6f14031 to mx.collections.IHierarchicalCollectionViewCursor.
I've tried casting dataProvider as ICollectionView. I've tried then casting the ICollectionView as IHierarchicalCollectionView. I've tried all sorts of casting, but nothing seems to work. Why won't this work in this new implementation as it did in the past implementation? What do I need to do?
*** Update:
I started debugging this. I added an override setter to my ADG Class to see when dataProvider was being set:
override public function set dataProvider(value:Object):void
{
super.dataProvider = value;
}
I added a breakpoint to this setter and to my setOpenNodes() function. Sure enough, the dataProvider is being set BEFORE setOpenNodes() is called, and it is HierarchicalData. But, when the setOpenNodes() the debugger says that the dataProvider is a null ArrayCollection. It seems like this is the root issue.
I needed to call commitProperties before attempting to access the dataProvider property.
public function setOpenNodes(maxDepth:int = 0):void
{
super.commitProperties();
var dataCursor:IHierarchicalCollectionViewCursor =
dataProvider.createCursor();
while (dataCursor.current != null)
{
if (dataCursor.currentDepth < maxDepth)
dataProvider.openNode(dataCursor.current);
dataCursor.moveNext();
}
dataProvider.refresh();
}

Resources