out of curiosity I decided to experiment with the following in a Flex 4 project:
public class MyGroup extends Group
{
public function MyGroup()
{
super();
var myLabel:Label = new Label();
myLabel.id = "myLabel";
myLabel.text = "My label!";
this.addElement(myLabel);
} etc.
This custom component does what I'd expect; it looks like a label control with text="My label!".
Question: is there any way to reference the myLabel label control (e.g. to change the text) elsewhere in the project?
At the moment the only way I can get to the inner label control is by calling something like myGroup.getElementAt(0).
I realize that it would make more sense to have the label be a class variable -- I'm just wondering how this code works.
You can make a public setter to change you text label:
public class MyGroup extends Group
{
private var _label:Label=new Label();
public function set label(value:String):void{
_label.text=value;
}
public function MyGroup()
{
super();
_label.id = "myLabel";
label = "My label!";
addElement(_label);
}
.....
}
var myGroup:MyGroup=..
myGroup.label="Hello";
In your case since you are declaring your var myLabel inside a function, it 's scope will only apply inside this function
In ActionScript, variables are named handles that you can pull on to get objects and data. Variables have something called scope, which specifies in what parts of the code the handle is valid.
When you create a variable inside a function, its scope is that function. That is, that particular named handle is only usable inside that function.
In your code, you create a handle called myLabel, and put a Label in it--let's call it Label123. Next you put Label123 into the element list of MyGroup, which gives MyGroup a handle attached to Label123. Then the function ends, and the handle called myLabel is no longer usable. Label123 still exists, because MyGroup has a handle to it.
If you create myLabel as a private or protected class variable, that handle will be usable from any function inside MyGroup. If you create it as a public class variable, it will be usable anywhere inside MyGroup, and also anywhere in the code you have an instance of MyGroup. (And if you create it as an internal class variable, it will be usable anywhere inside the package MyGroup is in.)
Related
I know an ItemRenderer is a ClassFactory and that you can use the newInstance method of ClassFactory to get an instance of the ItemRenderer. My question, however, is is it possible to use methods of the ItemRenderer without using ClassFactory.newInstance()?
In my case I can't use this newInstance method because it doesn't keep the state.
Is there any way I can do this? Thanks!
An ItemRenderer is a component, like any other. The itemRenderer property of a list based class has a value of a ClassFactory. If you have a reference to an instance of the itemRenderer component, you can call methods on it.
You cannot call a method on any component if an instance if that component instance has not been created yet. So to call a method on an itemRenderer without using ClassFactory.newInstance() you must manually create your own instance using the new keyword.
You might want to implement the ItemRenderer as smart as it is needed to recreate the state depending in the data being set. On the other hand, make sure that the data contains everything needed. You barely want to interact with the renderers in a different scope then the renderer itself.
If it should necessary, a DataGroup dispatches a RendererExistence event when a renderer is added.
private function newList():List {
const list:List = new List();
list.addEventListener(FlexEvent.INITIALIZE, list_initializeHandler);
return list;
}
private function list_initializeHandler(event:FlexEvent):void {
const listBase:ListBase = ListBase(event.target),
dataGroup:DataGroup = listBase.dataGroup;
dataGroup.addEventListener(RendererExistenceEvent.RENDERER_ADD, dataGroup_rendererAddHandler);
dataGroup.addEventListener(RendererExistenceEvent.RENDERER_REMOVE, dataGroup_rendererRemoveHandler);
}
private function dataGroup_rendererAddHandler(event:RendererExistenceEvent):void {
// renderer added
}
private function dataGroup_rendererRemoveHandler(event:RendererExistenceEvent):void {
// renderer removed
}
This is the way to go if you need to reference single item renderer instances.
Do you mean static functions and variables?
If you define a function (or variable, or const) as static, it is accessible via the class name, so you could define
class MyClass {
public static const className:String="MyClass.className (const)";
public static function getClassName():String {
return "MyClass.getClassName (function)";
}
}
trace(MyClass.className); //prints "MyClass.className (const)"
trace(MyClass.getClassName()); //prints MyClass.getClassName (function)
I have never used Flex/Actionscript before so excuse me if I'm asking anything obvious but I have been working on this for 3 days (includes searching google and stackoverflow) before writing this post.
I have been asked to modify a game written in Flex 4 made with a mini-IoC-based MVC framework. Two mods I have been asked to make is a game over screen, which displays the final score and a difficulty selection on the introduction screen. I have made the game over screen, and successfully managed to get the controller to bring it up when the game timer runs out.
So for now, the game structure goes:
Intro View -> Game View -> Game Over View
^ |
|__________ (retry?) ________|
The first problem is getting the score to be passed from the mainGame ActionScript file to the game over screen.
Things I have tried:
Importing mainGame in the gameOverViewBase and calling the mainGame.score variable in the gameOverView mxml file.
-EDIT!!! = the above method works if I change the score variable in mainGame to a constant, but if it remains a variable, the controller won't load the gameOverView and the game sits at an empty mainGame view.
Making a function that adds to a new score variable in the gameOverViewBase whenever the player scores during the game.
Passing the score as a parameter to the GameOverView when the MainGame ends
controller.loadGameOver(score);
This seemed like the most logical way to go about it. So I followed the function through the other components of the game setting the loadGameOver to take an integer as a parameter until I got to the main game actionscript file:
public function loadGameOver(score:int) : void
{
loadView(GameOverView);
}
The loadView function (shown below) is where I get stuck because I can't see where to pass the 'score' parameter. It looks like this:
private function loadView(viewClass:Class, modelClass:Class = null) : void
{
var view:View = new viewClass();
if(!view) throw new Error("Could not load view");
viewContainer.removeAllElements();
viewContainer.addElement(view);
view.controller = this;
}
The second problem is the difficulty selection on the introduction screen. I have done this with 3 buttons (easy, normal, hard) in the mxml file and for every button in the ActionScript:
protected function onEasyButtonClick() : void
{
set = "easy"
controller.loadMainGame(set);
}
Once again, I end up at the above loadView function.
To sum up: I need to know how to pass the data between the views and models. If that's not the ideal method, I am open to any other methods that you think are better.
Thank You!
Ben
P.S. I can send my source code to anyone who would like to help :)
You don't specify which MVC framework you're using which would be helpful. However, score should definitely be a property of a model and the model data should be accessible to the view either directly, perhaps via binding (thanks weltraumpirat), or via some intermediary class.
I would suggest you have a look at some of the existing view classes and try to figure out how they are fed the model data. You can use this approach to get the data you need for your view.
[EDIT]:
The mainGame property is not being set on your GameOverView instance so you're unable to access its score property either through binding or through trace. The loadView method of your controller class accepts a Model class reference which it uses to construct a new Model instance to be used by the new View. Unfortunately this is no use to you as your GameOverView needs the instance of MainGame which was created for the MainGameView (and which contains the current score).
I don't know if the following fits into the philosophy of the framework you're using. However, I would change the loadView method to accept an instance of a Model rather than a class reference, and create and cache a reference to an instance of MainGame when your controller is instantiated. That way you can pass the same Model reference to both the MainGameView and GameOverView when these are created.
public class WhackAMoleBase extends Application implements IGameController
{
public var viewContainer:Group;
private var mainGame:MainGame
public function WhackAMoleBase() : void
{
super();
// Create and cache an instance of the main game Model
mainGame = new MainGame();
addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
}
public function loadIntroduction() : void
{
loadView(IntroductionView);
}
public function loadMainGame() : void
{
loadView(MainGameView, mainGame);
}
public function loadGameOver() : void
{
// Use the same instance of MainGame which the MainGameView
// has been using as the Model for the GameOverView
loadView(GameOverView, mainGame);
}
// Accept a Model instance rather than a class Reference
private function loadView(viewClass:Class, model:Model = null) : void
{
//Create a new instance of the supplied view
var view:View = new viewClass();
if(!view) throw new Error("Could not load view");
//Clear any previous views in the container and add
viewContainer.removeAllElements();
viewContainer.addElement(view);
//Property based dependency injection
view.controller = this;
//There may or may not be a model required for the
//requested view; check and instantiate appropriately
if(model)
{
//Give model reference to the controller
//and link the view up to the model
model.controller = this;
view.model = model;
}
}
private function onCreationComplete(event:FlexEvent) : void
{
//When the application is first created, we want to show the introductory view
loadView(IntroductionView);
}
}
I am new to Flex (got assigned to maintain an old project at work) and am having some trouble getting data binding to work correctly. I have a popup form class, AddOffer.mxml which uses a model AddOfferModel.as. On my popup form, I have the following component:
<mx:FormItem label="{getResource('addOffer.form.OFFER_DATE')}:"
labelWidth="90">
<views:OfferWindowDatesFragment
id="offerWindowField"
start="{model.offerStartDate}"
stop="{model.offerStopDate}" />
</mx:FormItem>
My AddForm.mxml file also has some embedded actionscript where I define my 'model' variable:
[Bindable]
public var model:AddOfferModel;
The model variables I am trying to bind to are standard getters/setters and look like this inside AddOfferModel.as:
[Bindable]
public function set offerStartDate(val:EditableInstant):void
{
_offerStartDate = val;
}
public function get offerStartDate():EditableInstant
{
return _offerStartDate;
}
private var _offerStartDate:EditableInstant;
[Bindable]
public function set offerStopDate(val:EditableInstant):void
{
_offerStopDate = val;
}
public function get offerStopDate():EditableInstant
{
return _offerStopDate;
}
private var _offerStopDate:EditableInstant;
Inside the OfferWindowDatesFragment component class, the start and stop variables look like this:
[Bindable]
public function set start(val:EditableInstant):void
{
_start = val;
}
public function get start():EditableInstant
{
return _start;
}
private var _start:EditableInstant;
[Bindable]
public function set stop(val:EditableInstant):void
{
_stop = val;
}
public function get stop():EditableInstant
{
return _stop;
}
private var _stop:EditableInstant;
Basically, I just want to bind the start and stop variables in my OfferWindowDatesFragment class to the offerStartDate and offerStopDate variables in the AddOfferModel.as file. Whenever I access the start/stop variables in functions inside the OfferWindowDatesFragment class, they are null.
I have an event listener function that gets triggered in OfferWindowDatesFragment anytime a user selects a new date, it looks like this:
private function changeOfferDate():void
{
start.currentValue = offerDateEditor.start;
stop.currentValue = offerDateEditor.stop;
}
Every time I reach this function, it throws up an error because 'start' and 'stop' are both null ... but should have been initialized and bound already. If I look at the variables in the debugger, I can confirm that values on the right side of the assignment expression are valid, and not what is causing the error.
I am not real familiar with how initialization works in Flex, and I assumed as long as I instantiated the component as seen in the first code snippet at the top of my post, it would initialize all the class variables, and setup the bindings. Am I missing something? Perhaps I am not properly initializing the model or class data for AddForm.mxml or AddFormModel.as, thereby binding null references to the start/stop fields in my OfferWindowDatesFragment class?
Any help would be greatly appreciated. Thanks!
EDIT:
I looked into this further and tried using Mate to inject the 'model' variable inside AddOffer.mxml with a valid AddOfferModel object:
<Injectors target="{AddOffer}" debug="{debug}">
<ObjectBuilder generator="{AddOfferModel}" constructorArguments="{scope.dispatcher}" cache="local"/>
<PropertyInjector targetKey="model" source="{lastReturn}" />
</Injectors>
I load the AddOffer.mxml dialog as the result of a button click event on another form. The function that pops it up looks like this:
public function addOffer():void
{
var addOfferDialog:AddOffer = new AddOffer();
addOfferDialog.addEventListener("addOffer", addOfferFromDialog);
modalUtil.popup(addOfferDialog);
}
It doesn't seem to be assigning anything to the 'model' variable in AddOffer.mxml. Does loading a view/dialog this way not trigger an injection from Mate by chance? (I realize this last part might belong in the Mate forums, but I'm hoping somebody here might have some insight on all of this).
In AddOffer.mxml, you have this code:
[Bindable]
public var model:AddOfferModel;
Is there something outside AddOffer.mxml that is setting this to a valid AddOfferModel? There should be. The nature of how the Flex component life cycle means that you can expect that things may be null at times as a View builds. So you should build your components to be able to "right themselves" after receiving bad data, if the data eventually comes good.
Data binding is one way to do this, but it may not paper over everything depending on what else is going on.
Have you verified that the model value you're getting is not null at the point where the user selects the date and that its offerStartDate and offerEndDate properties have been populated with valid EditableInstants? If both of those are correct, I'd start looking for pieces of the Views that expect to have stuff at a given instant and then don't recover if it is provided later.
I am just learning actionscript, so come across the problem
In my application I often call to different web services, and because I don't want to hardcode urls to them in my code, I am passing urls to the services as flashvars.
Currently I am doing it this way:
public var siteUrl:String;
public var gameId:String;
public function main():void
{
siteUrl = Application.application.parameters.siteurl;
gameId = Application.application.parameters.gameid;
Where main is a function, which is called on application's creation complete event.
This way I can call both variables from main file of the application but I want to access them from other files. (other as classes)
So is there a way to create class with constants and init values there with flashvars so I can use them everywhere (after importing of course)
The parameters are just stored in that Application.application.parameters object, and that's static. There's no reason you couldn't access that from other classes in your code.
If you want to write a class that wraps the parameters (maybe validates them or something) you could do that fairly easily. You can use a for each loop to loop over all the parameters. Something like:
var params:Object = Application.application.parameters
for(var name:String in params) {
var value:String = params[name] as String;
/* do something with the param */
}
If you want your class to actually verify things then it could just check for each parameter it expects and store it in a local variable.
It really just depends on your own preferences. Some people are fine with accessing the parameters object when they need it. Some people like having the extra code-completion by having a config class that actually defines all the expected config variables.
Update in response to comment:
Instead of having one module declare the variable and have other modules have to depend on that one to access the property it would be cleaner to have a single config module that everything that needs it would all use.
You could use a static class or singleton or some IoC stuff. Just for simplicity I'll show you a way you can do it with a static class.
class MyConfig {
private static var _infoService:String;
private static var _someOtherParam:int;
public static function get infoService():String { return _infoService; }
public static function get someOtherParam():int { return _someOtherParam; }
public static function initParams():Void {
var params:Object = Application.application.parameters;
_infoService = params.infoservice;
// just assuming you have a method to convert here. don't remember the
// code off the top of my head
_someOtherParam = convertToInt(params.someOtherParam);
}
}
Make sure when your app initializes it calls MyConfig.initParams(). You can have that method actually validate that it gets everything it expects and throw exceptions (or return an error) if there's a failure if you want.
Then wherever you need to use that config within your code you just import your config class and access the param. So getting infoService would just be:
var infoService:String = MyConfig.infoService;
Personally I wouldn't use a static class, but it was the easiest to show.
I have a class with many embedded assets.
Within the class, I would like to get the class definition of an asset by name. I have tried using getDefinitionByName(), and also ApplicationDomain.currentDomain.getDefinition() but neither work.
Example:
public class MyClass
{
[Embed(source="images/image1.png")] private static var Image1Class:Class;
[Embed(source="images/image2.png")] private static var Image2Class:Class;
[Embed(source="images/image3.png")] private static var Image3Class:Class;
private var _image:Bitmap;
public function MyClass(name:String)
{
var ClassDef:Class = getDefinitionByName(name) as Class; //<<-- Fails
_image = new ClassDef() as Bitmap;
}
}
var cls:MyClass = new MyClass("Image1Class");
This doesn't answer your question, but it may solve your problem. I believe doing something like this should work:
public class MyClass
{
[Embed(source="images/image1.png")] private static var Image1Class:Class;
[Embed(source="images/image2.png")] private static var Image2Class:Class;
[Embed(source="images/image3.png")] private static var Image3Class:Class;
private var _image:Bitmap;
public function MyClass(name:String)
{
_image = new this[name]() as Bitmap;
}
}
var cls:MyClass = new MyClass("Image1Class");
I'm having a tough time remembering if bracket notation works on sealed classes. If it doesn't, a simple solution is to mark the class as dynamic.
The reason your method does not work is because "Image1Class" is a variable name, not the actual Class name.
You can get the class name like this
import flash.utils.getQualifiedClassName;
trace(getQualifiedClassName(Image1Class));
Which as you may see, means your class name (the one that should be passed into the function) is something like MyClass_Image1Class.
You don't need to use any fancy getDefinitionByName() methods, simply refer to it dynamically. In your case, replace the 'Fails' line with:
var classDef:Class = MyClass[name] as Class;
And that should do it.
Thank you so much! I just spent almost 5 hours trying to get the POS getDefinitionByName to work with the getQualifiedClassName that I was ready to throw stuff!! My final working code looks like this and even gets the string name from an array.
CreatureParam is a 2 dimentional array of strings;
Type is an integer that is sent to flash by HTML tag which is turn comes from a MYSQL database via PHP.
Mark1_cb is a combobox that is on the stage and has an instance name. It's output is also an integer.
So this code directly below imports the class "BirdBodyColor_mc" from an external swf "ArtLibrary.swf". BirdBodyColor_mc is a movieclip created from a png image. Note you must double click on the movieclip in the ArtLibrary.fla and insert a second key frame. Movieclips apparently need two frames or flash tries to import it as a sprite and causes a type mismatch.
[Embed(source="ArtLibrary.swf", symbol="BirdBodyColor_mc")]
var BirdBodyColor_mc:Class;
Normally I would put an instance of this movieclip class on the stage by using this code.
myMC:MovieClip = new BirdBodyColor_mc();
addChild(myMC);
var Definition:Class = this["BirdBodyColor_mc"] as Class;
var Mark1:MovieClip = new Definition();
But I need to do this using a string value looked up in my array. So here is the code for that.
var Definition:Class = this[CreatureParam[Type][Mark1_cb + 2]] as Class;
var Mark1:MovieClip = new Definition();