Fire an event when a TextField text property is set - actionscript-3

I have a TextField instance on stage. The copy within the field is updated from outside my project by a 3rd party system that I have no access to. I don't know exactly how this is achieved but I need to know when it has happened.
I want a way of telling when the text is updated within the field so I can then format and position the field based on its new content. I've tried
myTextField.addEventListener(Event.CHANGE, updateMethod);
but it seems that this only fires when the text is changed by direct user interaction and not the kind of injection from outside that I am getting.
Is there any way that I can listen for an assignment to myTextField.text rather that only for changes made by the user via mouse/keyboard?
Appreciate any advice. Thanks in advance :)

Instead of directly accessing the text field, you can wrap it with your own class, and dispatch events when the setter is fired: (untested code)
public class MyTextField extends EventDispatcher {
private var textField:TextField;
public MyTextField(t:TextField) {
textField = t;
}
public function set text(s:String) {
textField.text = s;
var e:Event = new Event(Event.CHANGE);
dispatchEvent(e);
}
public function get text():String {
return textField.text;
}
}
And else where in your code:
var mytf:MyTextField = new MyTextField(stage.myTextField); //stage.myTextField is the reference to the text field on the stage
mytf.addEventListener(Event.CHANGE, function(e:Event) { /* get event */ });
// don't use stage.myTextField.text to set text, instead, use:
mytf.text = "setting text!"; //this will trigger the event callback above

Related

How can I automatically update a TextFieldFormat in AS3?

Everytime I change the text value of a dynamic textfield I have to immediately also assign a value to the TextFormat, or else the text doesn't display anything at all. It's fine, but annoying and easy to forget. So my first question is simply if there is something I'm doing wrong with my TextField that the field requires an immediate call to its TextFormat setter (even if it's already been previously set). If I'm doing everything correctly then my second question is regarding my simple custom class I've made to bypass the repetitive functions. My method was to override the text setter function so that every time I assign a new text value, it also assigns the textFormat value again. I've only ever overridden maybe 1 or 2 other functions before, so I think that is probably where I'm going wrong.
public class DynamicDisplayText extends TextField
{
private var textFilter:GradientGlowFilter = new GradientGlowFilter(4,90,[0xFE23C1, 0xFC9625, 0xFCFFBF], [0, .5, 1], [0, 125, 250],15,15,2,2,"outer",true);
private var _format:TextFormat;
public function DynamicDisplayText(format:TextFormat, filtersArray:Array, txt:String):void
{
super();
text = txt;
_format = format;
selectable = false;
mouseEnabled = false;
embedFonts = true;
setTextFormat(_format);
autoSize = TextFieldAutoSize.LEFT;
filters = filtersArray
}
override public function get text():String { return super.text }
override public function set text(text:String):void {
super.text = text
super.setTextFormat(_format); // This seems to be the root of the problem
}
}
If I remove the super.setTextFormat(_format); and call the setTextFormat function from the Main doc. class, then all works fine. I get error #2007 parameter value must be non null.
Thanks for any tips!

How to get the width of a MovieClip for a different frame instantly?

Is there a way to get the width of a MovieClip (that does have a name) on a different frame? I have tried to using .width and .getBounds(null).width, however, both of them will give me only the width of the current frame. I have tried to do gotoAndStop(frameiwant), but the information doesn't seem to be correct until at least the next frame
I would like to get the width of the frame instantly so I don't have to wait until the next frame for the width.
The only way I could think of doing this was to have an initial phase in your project which will:
Run through all of the frames in your timeline. Create an object which will hold information about the children in that frame. It can be called Frame.
Iterate over all the children that are added to the stage in that frame and add a definition object that describes that child. The description can be as basic or vast as you need. We can call this class an ObjectDefintion.
The downside of this process is that you need to wait for the FRAME_CONSTRUCTED event like #Larusso pointed out in his answer. This means that the frame actually has to finish rendering before you are able to get information about its children, which of course means you have to go through and render every single frame in your timeline during this phase. All you can really do to mitigate this problem is set the frameRate to something high and then set it back when you're done assessing all the frames.
I have set this up and it works well - I'll paste each class and try explain what they do.
So for your document class (or whichever MovieClip holds the frames you want to look at), I have this:
public class Main extends MovieClip
{
private var _userFrameRate:int;
private var _frames:Vector.<Frame> = new <Frame>[];
public function Main()
{
_userFrameRate = stage.frameRate;
stage.frameRate = 120;
addEventListener(Event.FRAME_CONSTRUCTED, _assess);
}
public function getFrame(index:int):Frame
{
return _frames[index - 1];
}
private function _assess(e:Event):void
{
var frame:Frame = new Frame(this);
_frames.push(frame);
if(currentFrame === totalFrames)
{
removeEventListener(Event.FRAME_CONSTRUCTED, _assess);
gotoAndStop(1);
stage.frameRate = _userFrameRate;
ready();
}
else play();
}
public function ready():void
{
// Start here.
// There is a MovieClip on frame 10 with the instance name 'test'.
// We can get the width of it like this.
trace( getFrame(10).define("test").property("width") );
}
}
This basically initializes the phase in which we will run over each frame in the MovieClip and assess its children. The ready() method is used as the entry point for your code post-assessment.
Next we have the Frame class, which serves to hold information about children related to a frame:
public class Frame
{
private var _main:Main;
private var _content:Object = {};
public function Frame(main:Main)
{
_main = main;
update();
}
public function update():void
{
_content = {};
for(var i:int = 0; i < _main.numChildren; i++)
{
var target:DisplayObject = _main.getChildAt(i);
// This will be explained below.
var definition:ObjectDefinition = new ObjectDefinition(target, "x", "y", "width", "height");
_content[target.name] = definition;
}
}
public function define(name:String):ObjectDefinition
{
return _content[name];
}
}
It's pretty straightforward - you give it a reference to Main so that it can check children that are existent within it each frame.
The ObjectDefinition class is also pretty straightforward, acting purely as a repository for data that you want to keep track of on each child of the frame:
public class ObjectDefinition
{
private var _definition:Object = {};
public function ObjectDefinition(target:DisplayObject, ...properties)
{
for each(var i:String in properties)
{
_definition[i] = target[i];
}
}
public function property(property:String):*
{
return _definition[property];
}
}
You'll notice that the constructor accepts the target DisplayObject that will be defined, as well as any amount of properties you want to keep track of as strings (see above within Frame for implementation).
Once complete, you can chain the methods Main.getFrame(), Frame.define() and ObjectDefinition.property() to get properties of children that will exist throughout the timeline. For example, if you have a MovieClip with the instance name square on frame 15 and you want to get its width and height, you can do this within .ready() like so:
var square:ObjectDefinition = getFrame(15).define("square");
trace(square.property("width"), square.property("height"));
Of course this process is not ideal - but unfortunately it is the only way I can see that what you want to achieve is possible.
You have to listen to a specific event before you can ask for the information.
clip.addEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
clip.gotoAndStop(frame);
function frameReadyHandler(event:Event):void
{
clip.removeEventListener(Event.FRAME_CONSTRUCTED, frameReadyHandler);
var width = clip.width;
}
The Frame constructed event is the first of several events that gets dispatched. It gets dispatches right before the frame script gets executed. You could also wait for the on enter frame event.
You could add an event listener for 1 millisecond and test if the previousWidth you had stored is different. If it is, there you go. If not, its probably listening to the same frame.
A 1 millisecond timer is not such a big deal, stop it if you don't need it, resume it if you do, else, keep it running constantly. When it changes, dispatch an event or whatever needs to happen.
If you know the maximum size of the MovieClip, you may try this:
// Create movie clip
var movie :MovieClip = new MovieClipWith3Frames();
// Move to second frame
movie.gotoAndStop(2);
// Create bitmap witch magenta background
var bd :BitmapData = new BitmapData(200, 200, false, 0xFF00FF);
// Draw second frame
bd.draw(movie);
// Found the bounds of shape
var movieBounds:Rectangle = bd.getColorBoundsRect(0xFFFFFF, 0xFF00FF, false);
trace(movieBounds); // (x=42, y=15, w=32, h=33)

Action Script 3.0 Mouse Event in a class package

am having problem with using mouse click event inside a class, i am an absolute beginner to Action Script.
what i want is that if i click the btn_MClick button it should run the script, but everytime i click it i get error message that btn_MClick is undefined.
btn_MClick is on stage and with the instance name if btn_MClick
public class gunShip1 extends MovieClip
{
var moveCount = 0;
public function gunShip1()
{
stage.addEventListener(KeyboardEvent.KEY_DOWN, moveGunShip1);
stage.addEventListener(KeyboardEvent.KEY_DOWN, ShootGunShip1)
btn_MClick.addEventListener(MouseEvent.MOUSE_DOWN.KEY_DOWN, ShootGunShip1);;
}
function ShootGunShip1(evt: MouseEvent)
{
var s_Bullet:survBullet = new survBullet();
var stagePos:Point = this.localToGlobal (new Point(this.width / 2-10, this.height));;
s_Bullet.x = stagePos.x;
s_Bullet.y = stagePos.y;
parent.addChild(s_Bullet);
//play sound
var gun_sound:ricochetshot = new ricochetshot();
gun_sound.play();
}
}
Please, i have absolutely no idea what to do, and somehow it feels like the whole process is wrong.
Your class gunShip1 does not have the property btn_MClick, the root, or document class does.
Basically what's happening is that you've placed your button on the stage, which makes it an instance that belongs to the root container. At the moment, you're trying to refer to the button as a property of gunShip1.
What you should really do here is have the button click managed separately to gunShip1, and have that separate code invoke methods of gunShip1. For example, you could have this in your document class:
public class Game extends MovieClip
{
private var _ship:gunShip1;
public function Game()
{
_ship = new gunShip1();
// The Document Class will have reference to objects on the stage.
btn_MClick.addEventListener(MouseEvent.CLICK, _click);
}
private function _click(e:MouseEvent):void
{
_ship.shoot();
}
}
And then your updated shoot method in gunShip1:
public function shoot():void
{
var s_Bullet:survBullet = new survBullet();
var stagePos:Point = this.localToGlobal (new Point(this.width / 2 - 10, this.height));
s_Bullet.x = stagePos.x;
s_Bullet.y = stagePos.y;
parent.addChild(s_Bullet);
var gun_sound:ricochetshot = new ricochetshot();
gun_sound.play();
}
The idea is that the gunShip1 should not be responsible for dealing with user input (mouse, keyboard, etc). Instead, that should be a separate class which informs gunShip1 that it should do something.

my dynamically added movieclips have a name of "instance XX"

There are a couple things going on here that I dont fully understand. I have created a custom class that extends MovieClip to give some custom properties and create a geometric shape inside of the created MovieClip
package com.hyatt
{
import flash.display.*;
import flash.geom.*;
public class mapPin extends MovieClip
{
public var spirit:String;
public var callName:String;
public var hotelName:String;
public var city:String;
public var s:String;
public var zip:String;
public var country:String;
public var brand:String;
public var featured:Boolean;
public var horizon:Boolean;
private var _mc1:MovieClip = new MovieClip();
public function mapPin(_brand:String)
{
brand = _brand;
switch (_brand)
{
case "Andaz":
pinCircle(0xff0000);
break;
case "Grand Hyatt":
pinCircle(0x0000ff);
break;
case "Hyatt":
pinCircle(0x4600f0);
break;
}
}
private function pinCircle(color:uint):void
{
_mc1.graphics.beginFill(color);
_mc1.graphics.drawCircle(0,0,20);
this.addChild(_mc1);
_mc1.graphics.endFill();
}
}
}
Then I'm adding an couple instances of the mapPin class to a container movieclip on my stage and adding an event listener to that container clip.
var myTest1:mapPin = new mapPin("Andaz");
myTest1.brand = "Andaz";
container_mc.addChild(myTest1);
myTest1.name = "myTest1" //this is added purely for testing the "instance xx", same result
myTest.x = 100;
myTest.y = 100;
var myTest2:mapPin = new mapPin("Hyatt");
container_mc.addChild(myTest2);
myTest2.brand = "Hyatt";
myTest2.x = 400;
myTest2.y = 400;
container_mc.addEventListener(MouseEvent.CLICK, pinClicked);
finally I'm trying to be able to access the properties (the only one set thusfar is "brand") of the mapPin that is clicked.
function pinClicked(e:MouseEvent):void
{
trace(e.target.name); // traces "instance xx" instead of "myTest1"
trace(e.target.brand); // traces "undefined"
}
I can add the mapPin instances, and adjust their x and y though i cannot reference the custom class properties like "brand" and their name becomes a generic instance name. What am I missing? There are going to be upwards of 500 of these items added and I want to be able to pull information from them based upon a users click.
I'd have to see your mapPin class to be sure, but I think that the DisplayObject that is dispatching the event, is a child of mapPin.
To fix this, inside your mapPin class constructor add this line :
mouseChildren = false;
That will specify that children shouldn't receive clicks/dispatch mouse events.
currentTarget is the most recent object to dispatch an event and target is the object that originally dispatched it
It's not. AS3 doc says :
currentTarget :
The object that is actively processing the Event object with an event listener. For example, if a user clicks an OK button, the current target could be the node containing that button or one of its ancestors that has registered an event listener for that event.
target : The event target. This property contains the target node. For example, if a user clicks an OK button, the target node is the display list node containing that button.

Dynamically Handling Events with Function Expression

I have a class which exposes literally dozens of events(before you get of on a tangent about whether that's good/bad design, just know that I didn't make that class). The event object of each event(eventParam in the below code) always has a toDebugString function, that basically creates a string containing all of the event object's property values:
propertyName1: propertyValue1
propertyName2: propertyValue2
propertyName3: propertyValue3
It works so far as creating all of the panels, with the title of each panel being the name of the event. However, the big problem is that all of events end up in the TextArea of the last panel. So there is something I don't understand about the anonymous method. It's as if each iteration of the loop uses the same function, and on the last iteration of the loop it decides that the debugPanel that was just created will be the one that all instances of that function will reference. In other words, a new unique debugSubPanel and TextArea is created in each iteration of the loop, but there is only one debugResponseListener event handler shared by all iterations of the loop. So my question is, how can I dynamically create the event handler function dynamically so that it stays associated with the debugSubPanel that I want it to?
public function debugPanelCreated(event:FlexEvent)
{
//iterate through all of the events exposed by mClient.ResponsesDispatcher
//where key is the name of the event
for (var key:String in mClient.ResponsesDispatcher.respMap)
{
//for each event, create a panel containing a text box
var debugSubPanel:Panel = new Panel();
debugSubPanel.title = debugSubPanel.label = key;
var debugSubPanelTextArea:TextArea = new TextArea();
debugSubPanel.addChild(debugSubPanelTextArea);
var debugResponseListener:Function =
function (eventParam :Object) : void
{
//use debugString function to write the properties
//of eventParam to the text box
debugSubPanelTextArea.text = eventParam .toDebugString();
};
//listen to this event:
mClient.ResponsesDispatcher.addEventListener(key,debugResponseListener);
//add the panel for this event
debugPanel.addChild(debugSubPanel);
}
}
Actionscript includes a feature called closures, which means that when you create an inner function and call it, the variables of its parent function are still available. (This is how debugResponseListener = function() ... works at all.) The issue is that a closure is only created when that function is called, and it uses the variable values from their last setting.
You can get around this by making a function that returns the listener function you want.
function makePanelListener(debugSubPanelTextArea:TextArea) : Function
{
return function(eventParam :Object) : void {
//use debugString function to write the properties
//of eventParam to the text box
debugSubPanelTextArea.text = eventParam .toDebugString();
}
}
and in your original code:
var debugResponseListener:Function = makePanelListener(debugSubPanelTextArea);
(There's a little explanation of what's going on in Explaining JavaScript scope and closures, look for the section called "The Infamous Loop Problem". More on closures at jibbering.)
This is the hack I came up with. I really don't like it, but it'll work for now. Open to suggestions still.
public class ResponseDispatcherToDebugStringHelper
{
public var textArea:TextArea;
public function responseToDebugStringHandler(eventParam:Object) : void
{
//use debugString function to write the properties
//of eventParam to the text box
textArea.text = eventParam.toDebugString();
}
}
public function debugPanelCreated(event:FlexEvent)
{
//iterate through all of the events exposed by mClient.ResponsesDispatcher
//where key is the name of the event
for (var key:String in mClient.ResponsesDispatcher.respMap)
{
//for each event, create a panel containing a text box
var debugSubPanel:Panel = new Panel();
debugSubPanel.title = debugSubPanel.label = key;
var debugSubPanelTextArea:TextArea = new TextArea();
debugSubPanel.addChild(debugSubPanelTextArea);
var helper:ResponseDispatcherToDebugStringHelper =
new ResponseDispatcherToDebugStringHelper();
helper.textArea = debugSubPanelTextArea;
//listen to this event:
mClient.ResponsesDispatcher.addEventListener(key,helper.responseToDebugStringHandler);
//add the panel for this event
debugPanel.addChild(debugSubPanel);
}
}