Tuesday, July 31, 2007

Bug/Feature - De-Refrenced Variables in Flex (Double Instantiation)

I forgot to post about this earlier, but I discovered some interesting behavior in Flex when I accidentally instantiated a timer object twice. I had a component with an init command that was called on creationComplete but I also accidentally called component.init() from the parent application. Inside my init function I had this:
 timer=new Timer(1000,(parentApplication.TIMELIMIT*60));
 timer.addEventListener(TimerEvent.TIMER,onTick);
 timer.addEventListener(TimerEvent.TIMER_COMPLETE,onTimerComplete);
 timer.start();
You would think that one init would happen, then the next would happen and the timer object would be re-instantiated since there is the "new" keyword. Not so in Flex (this was using 2.01). The result is that when I stopped the timer using timer.stop(), only one instance stopped and onTimerComplete would still fire. The solution, obviously, is not to accidentally call the same function twice. But this just goes to show that it is possible to de-reference a variable in flex and have it floating around in memory (and it never gets garbage collected since it's still firing events).

Monday, July 30, 2007

Animation with MXML App

So I had a program where I needed to move an image around based on keyboard hits or button clicks. I figured "that'll be easy!", I'll just use the mousedown event and have it call a function that says targetObject.x-- to move it left. Unfortunatly, life is not so easy. It will only fire one event when the mouse goes down and another when it comes back up (Mouse.MOUSE_DOWN and Mouse.MOUSE_UP respectively). So how do you get some clean moving action? In this example whe'll have an image called MyImage and I want it to move left or right based on keyboard or button clicks. Assume we have two buttons: btnLeft and btnRight. The very fist step is to improve the animation abilities of an MXML Flex app is to add frameRate="60" into the main application tag. This will increase the framerate of your flex application which is important because we're going to use the ENTER_FRAME event to trigger. So let's first right the MXML.

<mx:Application frameRate="60" layout="Absolute">
   <mx:Image source="assets/test.jpg" id="MyImage" x="25" top="25"/>
   <mx:Button label="Move Right" id="btnRight" x="25" top="100"/>
   <mx:Button label="Move Left" id="btnLeft" x="75" top="100"/>
</mx:Application>
The next thing we'll need to do after that is set up some action functions and linking them with an initialize function triggered by the applications "onCreationComplete" event. Perhaps I should stop here and tell you some theory about how I'm going to make the image move smoothly. Basically we're going to have two "states" for our image. They will be booleans.
  public var movingLeft:Boolean=false;
  public var movingRight:Boolean=false;
Now we're going to set these variables to true when you're moving right or left. The ENTER_FRAME function is going to see if one of those is true and move the image accordingly. Let's go ahead and write the ENTER_FRAME function.

 private function framesHappen(event:Event):void{
  if(movingLeft){
   MyImage.x--;
  }else if(movingRight){
   MyImage.x++;
  }
 }
Good we got that, now we needto write some events to handle button ups and downs.
 private function leftBtnDown(event:MouseEvent):void{
  movingLeft=true;
 }
 private function leftBtnUp(event:MouseEvent):void{
  movingLeft=false;
 }
 private function rightBtnDown(event:MouseEvent):void{
  movingRight=true;
 }
 private function rightBtnUp(event:MouseEvent):void{
  movingRight=false;
 }
And lastly, nothing will happen if we don't have the event listeners. So let's create that init function (And don't forget to call it using the application's onCreationComplete event!!)

private function init():void{   
   btnLeft.addEventListener(MouseEvent.MOUSE_DOWN,leftBtnDown);
   btnLeft.addEventListener(MouseEvent.MOUSE_UP,leftBtnUp);
   btnRight.addEventListener(MouseEvent.MOUSE_DOWN,rightBtnDown);
   btnRight.addEventListener(MouseEvent.MOUSE_UP,rightBtnUp);
   this.addEventListener(Event.ENTER_FRAME,framesHappen);
}
To modify this for keyboard is very similar. Use KeyDown and KeyUp listeners on the whole application using "this.AddEventListener" and then within the listening function differentiate which key is pressed and set your left or right variable.

Thursday, July 12, 2007

Data to Objects

Here's my presentation from last night: download presentation.zip. Sorry it was a rushed affair.

Friday, July 06, 2007

Flex 2 Object Translation problem

So perhaps someone can shed some light on this problem. I've got an XML file Test.xml

<test>
    <myString>72157600686915164</myString>
</test>
When I use an HTTPService call to grab that XML data, the object that result event returns has the value test.myString equal 72157600686915168 no matter what I do. Everything is the same except the last number is 8. Did I find a bug or is the object translation assuming my string is a number and doing some weird operation on it. To make problems even worse... if I have an AS3 object called "someObject" and it's got a property of type String called "someProperty" and then I do:

someObject.someProperty=event.result.test.myString;
Then it becomes 72157600686915170. What the heck is going on!? I tried casting it several different ways... my temporary fix was to add a character to the XML data. In this example an underscore:

<test>
    <myString>72157600686915164_</myString>
</test>
And then I used substr() when I set it to the object property. That worked just fine. For some reason adding a character makes it assume that it's a string, not a number.

Monday, July 02, 2007

Pagination with Flex 2 (tilelist example)

So I you want to paginate a tilelist? Piece of cake. The best way to accomplish this is with an ArrayCollection as a dataprovider. But we won't actually use it as a dataprovider, instead we'll use a dummy arrayCollection as the dataprovider which will only be populated with the current page. So let's set up some variables.
[Bindable]
private var myDataProvider:ArrayCollection;
private var pagedDataProvider:ArrayCollection;
[Bindable]
private var curPage:int=1;
[Bindable]
private var pageCount:int=0;

private static PERPAGE:int=8;
Now let's assume your myDataProvider variable is populated by about 30 objects. You only want to see 8 at a time. So let's set a static, PERPAGE as the number of items per page. The other vars are obvious here except pagedDataProvider which is going to be used to store the current dataset. Let's setup an init function to get our first page of results and set everything up.
public function init():void{
    pagedDataProvider=new ArrayCollection();
    pageCount=(myDataProvider.length/PERPAGE)+1;
    curPage=1;
    if(pageCount > 1){  
        btnNext.enabled=true;
    }
    if(myDataProvider.length >= PERPAGE){
        for(var i:int=0;i<PERPAGE;i++){
            pagedDataProvider.addItem(myDataProvider.getItemAt(i));
        }
    }else{
        pagedDataProvider=myDataProvider;
    }
}
now assume that btnNext and btnPrevious are buttons to control the paging. This function will set up pagedDataProvider nicely. Let's go ahead and tie it to an component, in this case a tilelist.
<mx:TileList rowHeight="108" columnWidth="108" left="3" columnCount="4" id="tilelistPagedExample" rowCount="2" dataProvider="{pagedDataProvider}">
        <mx:itemRenderer>
            <mx:Component>
                <mx:Image width="100" height="100" source="{data.thumbnail}" toolTip="{data.caption}" />
            </mx:Component>
        </mx:itemRenderer>
    </mx:TileList>
All we have left is the forward and back functions. These functions will repopulate the pagedDataProvider with the next pages worth of stuff.
private function getNextPage():void{
    var start:int=PERPAGE*curPage;//for example, click from page 1, start would then be 8*1=8. start at index 8.
    var end:int=0;
    //do we have enough items in this set?
    if((myDataProvider.length-start)>PERPAGE){
        end=start+PERPAGE;//count it
    }else{
        end=myDataProvider.length;
    }
    pagedDataProvider=new ArrayCollection();
    for(var i:int=start;i<end;i++){
        pagedDataProvider.addItem(myDataProvider.getItemAt(i));
    }
    curPage++;//incriment the page!
    btnPrevious.enabled=true;
    if(curPage==pageCount){
        btnNext.enabled=false;
    }
}
Pretty self explanatory. Calculate the start and end, loop through adding those items. Now the previous button function.
private function getPreviousPage():void{
    curPage--;//decriment the page!
    btnNext.enabled=true;
    if(curPage==1){
        btnPrevious.enabled=false;
    }
    var start:int=PERPAGE*(curPage-1);//for example, click from page 1, start would then be 8*1=8. start at index 8.
    var end:int=start+PERPAGE;//count it
    pagedDataProvider=new ArrayCollection();
    for(var i:int=start;i<end;i++){
        pagedDataProvider.addItem(myDataProvider.getItemAt(i));
    }    
}
That last function is pretty simple since we can assume a lot of things based on the fact that we're going back. We don't need to worry about checking if end is greater than the overall length because if we're going backwards, we know it already got that far! Pretty simple eh? Don't forget to add the btnNext and btnPrevious controls and to run init after you've populated your dataprovider. I'll publish an example of this code in action later.

Feed (RSS/Atom)

Contact Me

Don't put anything here: