How to handle event for nonvisual objects in Flex - actionscript-3

I am trying to perform two way binding e.g I have a button (out of many controls), on its selection, I am showing the values of its diff properties(like height, width etc) in some textinput. This one way process works fine.
But the reverse process doesn't work. i.e When I select some button, and try to change its dimension by entering some value in height, width textinputs, the dimension are not changed.
How to know which button was selected by me? How events needs to be handled here ?
private void Form1_Load(object sender, System.EventArgs e)
{
//Create some data and bind it to the grid
dt1 = GetData(1000, 3);
this.UltraGrid1.DataSource = dt1;
//Set the grid's CreationFilter to a new instance of the NumbersInRowSelectors class.
this.UltraGrid1.CreationFilter = new NumbersInRowSelectors();
}
private void UltraGrid1_InitializeLayout(object sender, Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs e)
{
//Hide the default images that are drawn in the RowSelectors, like the pencil and asterisk, etc.
e.Layout.Override.RowSelectorAppearance.ImageAlpha = Infragistics.Win.Alpha.Transparent;
//Center the text in the RowSelectors.
e.Layout.Override.RowSelectorAppearance.TextHAlign = Infragistics.Win.HAlign.Center;
e.Layout.Override.RowSelectorAppearance.TextVAlign = Infragistics.Win.VAlign.Middle;
//There is no wy to change the width of the RowSelectors.
//Use a smaller font, so that 3-digit numbers will fit.
e.Layout.Override.RowSelectorAppearance.FontData.Name = "Small Fonts";
e.Layout.Override.RowSelectorAppearance.FontData.SizeInPoints = 6;
}
//The NumbersInRowSelectors class. This class Implements a CreationFilter and
//adds a TextUIElement to each RowSelector which displays the row number of
//the row.
public class NumbersInRowSelectors:Infragistics.Win.IUIElementCreationFilter
{
#region Implementation of IUIElementCreationFilter
public void AfterCreateChildElements(Infragistics.Win.UIElement parent)
{
//Don't need to do anything here
}
public bool BeforeCreateChildElements(Infragistics.Win.UIElement parent)
{
//Declare some variables
Infragistics.Win.TextUIElement objTextUIElement;
Infragistics.Win.UltraWinGrid.RowSelectorUIElement objRowSelectorUIElement;
Infragistics.Win.UltraWinGrid.UltraGridRow objRow;
int RowNumber;
//Check to see if the parent is a RowSelectorUIElement. If not,
//we don't need to do anything
if (parent is Infragistics.Win.UltraWinGrid.RowSelectorUIElement)
{
//Get the Row from the RowSelectorsUIElement
objRowSelectorUIElement = (Infragistics.Win.UltraWinGrid.RowSelectorUIElement)parent;
objRow = (Infragistics.Win.UltraWinGrid.UltraGridRow)objRowSelectorUIElement.GetContext(typeof(Infragistics.Win.UltraWinGrid.UltraGridRow));
//Get the Index of the Row, so we can use it as a row number.
RowNumber = objRow.Index;
//Check to see if the TextUIElement is already created. Since
//The RowSelectorsUIElement never has children by default, we
//can just check the count.
if (parent.ChildElements.Count == 0)
{
//Create a new TextUIElement and parent it to the RowSelectorUIElement
objTextUIElement = new Infragistics.Win.TextUIElement(parent, RowNumber.ToString());
parent.ChildElements.Add(objTextUIElement);
}
else
{
//There's already a TextUIElement here, so just set the Text
objTextUIElement = (Infragistics.Win.TextUIElement)parent.ChildElements[0];
objTextUIElement.Text = RowNumber.ToString();
}
//Position the TextUIElement into the RowSelectorUIElement
objTextUIElement.Rect = parent.RectInsideBorders;
//Return True let the grid know we handled this event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return true;
}
//Return false to let the grid know we did not handle the event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return false;
}
#endregion
}
}

Create a "currently selected item" member in the class where the button and the text edit are declared.
In the button selection event listener assign the event target to this member. Then use it in the text edit event listener.
For example:
// It's a declaration of the member variable
private var m_current_btn:Button = null;
// It's an event listener for your button
private function on_selection_change(event:Event):void
{
m_current_btn = event.target as Button;
// button_x and button_y are two text edits
button_x.text = m_current_button.x.toString();
button_y.text = m_current_button.y.toString();
}
// Event listener to track changes in the coordinate text inputs
private function on_coordinate_textedit_change(event:Event):void
{
if (m_current_btn != null)
{
m_current_btn.x = parseInt(button_x.text);
m_current_btn.y = parseInt(button_y.text);
}
}

Related

Access to undefined property itemRenderer (Keyboard Event)

I am trying to get the following code to run when I hit enter on a row within a data grid, it works when I click on the row (ListEvent) but how would I enable this to work when the enter key is hit (KeyboardEvent). I have the enter key working although it throws this error every time
private function onButtonClick(evt:KeyboardEvent):void
{
var item:Object = evt.itemRenderer.data;
openWorkflowItem(item.date.date, item.workFlowID);
$multiEdit = false;
if (target === currentWorkflowItems)
{
$histFilter['sym'] = item.sym;
histSym.text = item.sym;
applyHistFilters();
}
}
<mx:AdvancedDataGrid
id="historicalWorkflowItems"
dataProvider="{$historicalWFItems}"
width="100%" height="100%"
itemClick="{onWFItemClick(event)}"
keyDown="if (event.keyCode==Keyboard.ENTER){ onButtonClick(event)}"
borderStyle="none"
sortExpertMode="true"
useHandCursor="true"
headerShift="{saveColumnSettings('historical', historicalWorkflowItems)}"
columnStretch="{saveColumnSettings('historical', historicalWorkflowItems)}"
horizontalScrollPolicy="auto"
verticalScrollPolicy="auto"
allowMultipleSelection="true"
>
The problem is that you are assigning a key listener to the grid, so there's no specific item in this context. The keyDown event fires whenever a key is pressed with focus anywhere within the grid. You want something like itemKeyDown, but the grid does not seem to provide you with such an event.
This is why I asked you to check Event/target to see what value it holds. If an item renderer has focus and the user presses a key, your handler will fire and you can check if the event target is an item renderer, ex if(event.target is AdvancedDataGridItemRenderer). (The event currentTarget will always be the grid.) Now, I'm not super familiar with the implementation of AdvancedDataGrid item rendering, but it's likely that the event target will not itself be an item renderer, only a child of an item renderer (say a text field inside the item renderer). To check if the target is inside an item renderer is possible by walking the display tree and checking if a parent is an item renderer class:
function findItemRenderer(object:DisplayObject):AdvancedDataGridItemRenderer {
var current:DisplayObject = object;
while(current && !(current is AdvancedDataGrid)){
if(current is AdvancedDataGridItemRenderer)
return current as AdvancedDataGridItemRenderer;
current = current.parent;
}
return null;
}
Now you can handle your event like this:
function gridKeyDown(e:KeyboardEvent):void {
var item:AdvancedDataGridItemRenderer = findItemRenderer(e.target as DisplayObject);
if(item){
// do stuff with your item
}
}
But this is getting kind of silly to search the display hierarchy like this. What you should do is just dispatch an event from inside your item renderer, so that the event target is always the item renderer itself. For example:
class MyItemRenderer extends AdvancedDataGridItemRenderer {
public static const ITEM_ENTER:String = "itemEnter";
public MyItemRenderer(){
addEventListener(KeyboardEvent.KEY_DOWN, keyDown);
}
private function keyDown(e:KeyboardEvent):void {
if(e.keyCode == Keyboard.ENTER)
dispatchEvent(new Event(ITEM_ENTER, true)); // bubbles=true
}
}
Now you can handle this event:
myGrid.addEventListener(MyItemRenderer.ITEM_ENTER, itemEnter);
function itemEnter(e:Event):void {
var item:MyItemRenderer = e.target as MyItemRenderer;
// do stuff with your item
}

How to check if user clicked on smaller object before larger object?

Hey Everyone so I am currently working on a game and the objective is for the user to click on objects on the stage. But the user has to click on the largest objects first before the user clicks on the smaller objects. I wanted to make it that if the user clicks on a smaller object first and not the larger one then the game will be over. I thought I could go about setting this up with booleans for each object on the stage but my if statements arent cutting it here is how I have it set up:
Here are my objects and booleans I use:
//Add box references
public var box_1:MovieClip;
public var box_2:MovieClip;
public var box_3:MovieClip;
public var box_4:MovieClip;
//Booleans
private var b1:Boolean;
private var b2:Boolean;
private var b3:Boolean;
private var b4:Boolean;
now I set all the Booleans to false and if the user clicks on one of the objects I set the Boolean to true.
Here are my if statements tied to my main ENTER_FRAME Listener:
private function level_1():void
{
if (b1)
{
mainScreen.box_1.gotoAndPlay("B1GONE");
b1 = false;
}else
if (b2)
{
mainScreen.box_2.gotoAndPlay("B2GONE");
b2 = false;
}else
if (b3)
{
mainScreen.box_3.gotoAndPlay("B3GONE");
b3 = false;
}else
if (b2 && !b1)
{
endGameCondition();
}
}
On this statement:
if (b2 && !b1)
{
endGameCondition();
}
I was trying to state that if box_2 is true meaning that its clicked on and box_1 hasnt been clicked on yet which is the larger object then the game is now over due to the user not clicking on the largest object first. I have it setup to where box_1 is the largest object and the others are the next size down.
Can anyone see why this isnt working correctly or if there is a better method in doing this?
**** UPDATE HOW MY MAIN CLASS IS SETUP NOW **************
Where I add all my Movie clips and variables:
public class boxTapEngine extends MovieClip
{
//Screens
private var mainScreen:mcMainScreen;
//Add box references
public var box_1:MovieClip;
public var box_2:MovieClip;
public var box_3:MovieClip;
public var box_4:MovieClip;
private var aBoxesArray:Array;
in my constructor function:
aBoxesArray = [box_1, box_2, box_3, box_4];
//AddMainScreen
mainScreen = new mcMainScreen();
mainScreen.x = (stage.stageWidth / 2);
mainScreen.y = (stage.stageHeight / 2);
stage.addChild(mainScreen);
//Initiate numbers
nLevel = 1;
for each(var box:MovieClip in aBoxesArray)
{
box.addEventListener(MouseEvent.CLICK, boxClick);
}
finally on the boxClick function:
private function boxClick(e:MouseEvent):void
{
var box:MovieClip = e.currentTarget as MovieClip; //get a reference to one that was just clicked
box.mouseEnabled = false; //we'll use this as a flag to know if it's been clicked yet
box.gotoAndPlay("BGONE");
//check to see if previous boxes have been clicked.
//this will iterate through all the boxes in order
for (var i:int = 0; i < aBoxesArray.length; i++)
{
if(aBoxesArray[i] == box) return; //if we've reached the currently clicked box, all is good, no need to keep checking so let's exit this loop and function
if (!aBoxesArray[i].mouseEnabled)
{ //one of the previous boxes hasn't been clicked yet
endGameCondition();
}
}
}
Probably isn't working because your final else if statement won't ever be reached (because you're handling b2 == true earlier on which will then bypass all other else statements). Plus your setting b2 to false when you handle it earlier, so it will always be false by the time it gets your final statement.
You need to move that final else if before you check for the other things. See code comments:
//Do this first, and not as a else if
if (b2 && !b1){
endGameCondition();
return; //no need to check checking things if it's game over
}
//now you can do the rest
if (b1)
{
mainScreen.box_1.gotoAndPlay("B1GONE");
b1 = false;
}else
if (b2)
{
mainScreen.box_2.gotoAndPlay("B2GONE");
b2 = false;
}else
if (b3)
{
mainScreen.box_3.gotoAndPlay("B3GONE");
b3 = false;
}
As an aside, you don't need the enter frame handler, you can just check everything on click. Something like this would work:
var boxes:Array = [box_1,box_2,box_3,box_4]; //make sure this is in order of what needs to be clicked first
//if you wanted to actually sort them dynamically based off total size (regardless of what order you've stuffed them in the array), you could add in something like this, which will order them from biggest to smallest:
boxes.sort(function(a,b){
//compare which item has a greater total area (width * height)
if(a.width * a.height > b.width * b.height) return -1; //A is bigger, so put a before b in the array
if(a.width * a.height < b.width * b.height) return 1; //put b before a in the array
return 0; //return 0 if they are the same
});
for each(var box:MovieClip in boxes){
box.addEventListener(MouseEvent.CLICK, boxClick,false,0,true);
}
function boxClick(e:Event):void {
var box:MovieClip = e.currentTarget as MovieClip; //get a reference to one that was just clicked
box.mouseEnabled = false; //we'll use this as a flag to know if it's been clicked yet
box.gotoAndPlay("BGONE");
//or you could just do this to get rid of the box:
if(box.parent) box.parent.removeChild(box);
//check to see if previous boxes have been clicked.
//this will iterate through all the boxes in order
for(var i:int=0;i<boxes.length;i++){
if(boxes[i] == box) return; //if we've reached the currently clicked box, all is good, no need to keep checking so let's exit this loop and function
if(!boxes[i].mouseEnabled){ //one of the previous boxes hasn't been clicked yet
endGameCondition();
}
}
}
//Store your clips in an array
var myClips:Array = [mc1,mc2,mc3,mc4];
//get the clip sizes and store it to an array.
var sizes:Array = new Array();
for (var i:uint = 0; i < myClips.length; i++) {
sizes.push(myClips[i].width);
}
//apply Numeric array sort.
sizes.sort(Array.NUMERIC);
function onClickAction(e:MouseEvent):void {
//Check wheather the array is empty or not.
if (sizes.length != 0) {
//Check wheather the clicked object bigger or not.
if (e.target.width == sizes[sizes.length - 1]) {
trace("Bigger");
e.target.alpha = .5;
sizes.splice(sizes.length-1,1);
} else {
trace("Smaller");
}
}
}

How do i transition between game and menu without using frames in as3?

I have simplified this code for easier reading
I have one class called LevelSelect,
one class called Game,
and one class called NavButtons.
*/
The code below i have added levelSelect MovieClip to the stage and
added level numbers to level boxes
onclick it goes to my selected level.
public class LevelSelectPage extends Game
{
public var levelSelectScreen:level_selection;
public var list:Array;
// add movieClips to L1,L2 etc...
public static var Level:String = "No Level Selected";
public static var shared:SharedObject;
public function LevelSelectPage( )
{
setUpLevelSelect();
}
public function setUpLevelSelect():void
{
shared = SharedObject.getLocal("savegame");
shared.clear(); // remove when game completed
if (shared.data.level_passed == undefined)
{
shared.data.level_passed = 3;
}
if (shared.data.playing_level == undefined)
{
shared.data.playing_level = "L0";
}
else
{
shared.data.playing_level = "L0";
}
levelSelectScreen = new level_selection();
addChild(levelSelectScreen);
AddMovieClipsForLevels();
}
public function AddMovieClipsForLevels():void
{
// array of movieClip instances... level pictures.
list = [
levelSelectScreen.L1,
levelSelectScreen.L2,
levelSelectScreen.L3
];
list.forEach(setupSquare);
}
public function setupSquare(square:MovieClip, index:int, array:Array):void
{
// add numbers on top of level images
// get numbers of the levels... as index starts from 0, add 1
var LevelNumber:Number = index + 1;
// convert number to string
var imageLevelNumber:String = LevelNumber.toString();
// set textfield // get childs instance name
var insertlevelNumber:TextField
= square.getChildByName("levelNumberText") as TextField;
// output text
insertlevelNumber.text = imageLevelNumber;
}
public function onSquareClick(me:MouseEvent):void
{
switch(me.currentTarget.name)
{
case "L1": // trace("level 1 was selected");
// startGame();
break;
}
}
}
}
public class Game
{
// imagen a blank screen, it doesn't matter
}
The code bellow - i have added the nav buttons to the game stage, and on click of the back button it outputs a trace statment
but how do i run the LevelSelectPage again instead ?
public class NavButtons
{
private var m_NavDisplayIcon:ReferenceArray;
private var m_stage:Stage;
public function NavButtons( stage:Stage )
{
// add navigation to stage
m_stage = stage;
m_NavDisplayIcon = new ReferenceArray( navDisplayFla );
var icon:navDisplayFla = new navDisplayFla( );
icon.x = 5;
icon.y = 5;
m_NavDisplayIcon.Add( icon );
m_stage.addChild( icon );
// addEventlisteners for backButton, pause game and information
icon.backIcon.addEventListener(MouseEvent.CLICK, goBackToLevelSelectPage);
icon.pauseIcon.addEventListener(MouseEvent.CLICK, pauseGame);
icon.informationIcon.addEventListener(MouseEvent.CLICK, showInformation);
}
public function goBackToLevelSelectPage( event:MouseEvent ):void
{
//var levelSelectScreen:level_selection = new LevelSelectPage();
//m_stage.addChild(levelSelectScreen);
trace("you pressed the backButton");
}
Think of the stage as a blank piece of paper onto which you can stick post-it notes. So when you begin the game, your piece of paper is blank, and you add a post-it note onto this blank "stage" that contains your level selectscreen.
You do this by adding movieclips or sprites to the stage, or better yet add one container to the stage to which you will then add all additional elements (this will make it easier later because you can remove the container and everything in it will be removed as well in one go).
Now, when the player clicks on a button on your level select screen, what you need to do is:
Remove the current post-it note that's on the paper (i.e, remove the container that has your level select screen).
Place a new post-it note that contains the level the player clicked on (i.e, now you add another container to the stage, and add all the elements of the new level into it.)
When the user wants to go back to the level select screen, all you do is again remove the post-it note that is on the paper currently, and add the post-it note that contains the level select screen.

Dependent text fields

I want to have text fields which are dependent of each other. E.g there's two input text fields like angular frequency(ω) and ordinary frequency(f), ω=2πf. If I change one, the other changes. How can I do that? Second thing, how can I check if text field is empty?
if (field.text=="")... // doesn't work
There are 2 ways of doing this. You can either have some sort of update function that is called at every frame checking for the current value of each field with what they used to be and seeing if something changed then react to that.
private var tf1 : TextField = new TextField();
private var tf2 : TextField = new TextField();
private var tf1LastVal : String = "";
private var tf2LastVal : String = "";
public function update() : void
{
if(tf1.text != tf1LastVal)
{
tf2.text = //put your logic here
tf1LastVal = tf1.text;
}
else if(tf2.text != tf2LastVal)
{
tf1.text = //put your logic here
tf2LastVal = tf2.text;
}
}
The other, and better way (in my opinion) of doing this is by adding event listeners to your TextFields
//add this where you construct your TextFields
tf1.addEventListener(Event.CHANGE, onTf1Change);
tf2.addEventListener(Event.CHANGE, onTf2Change);
//end
private function onTf1Change(e : Event) : void
{
tf2.text = //your logic here;
}
private function onTf2Change(e : Event) : void
{
tf1.text = //your logic here;
}

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);
}
}