1

the code below sets up a List object in the main controller class that uses a custom cell renderer (CustomListCell class). the CustomListCell class creates a Button object for the cell that will be used to delete itself from the List's DataProvider.

how can i properly access the parent List object from its custom cell renderer?

//Controller Class
private function createList():void
 {
 provider = new DataProvider(data);

 list = new List();
 list.width = 200;
 list.height = 400;
 list.rowHeight = 50;
 list.dataProvider = provider;
 list.setStyle("cellRenderer", CustomListCell);
 }

-----

//CustomListCell Class
import fl.controls.Button;

public class CustomListCell extends Sprite implements ICellRenderer
 {     
 public function CustomListCell()
  {
  var button:Button = new Button();
  button.label = "Delete Cell";
  button.addEventListener(MouseEvent_MOUSE_DOWN, deleteCellHandler);
        addChild(button);
  }

 private function deleteCellHandler(evt:MouseEvent):void
  {
  //Access List/DataProvider Here
  }

 //required implemented ICellRenderer functions follow
 }

UPDATE

the following is my working custom renderer that implements ICellRenderer with Flash v3 List component. the List's dataProvider consists of 2 elements for each cell: randomColor and randomNumber.

package
{
//Imports
import fl.controls.Button;
import fl.controls.List;
import fl.controls.listClasses.ICellRenderer; 
import fl.controls.listClasses.ListData;
import flash.display.Sprite;
import flash.events.MouseEvent;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.geom.ColorTransform;

//Class
public class TouchListRenderer extends Sprite implements ICellRenderer
    {
    //Properties
    private var cellWidthProperty:Number;
    private var cellHeightProperty:Number;
    private var dataProperty:Object;
    private var listDataProperty:ListData;  
    private var selectedProperty:Boolean;

    //Cell Display Objects
    private var backgroundCanvas:MySprite = new MySprite();
    private var numberTextField:TextField = new TextField();
    private var button:Button = new Button();

    //Constructor
    public function TouchListRenderer()
        {
        }

    //Size Setter (Getter Functions Intentionally Omitted)
    public function setSize(width:Number, height:Number):void
        {
        cellWidthProperty = width;
        cellHeightProperty = height;
        }

    //Data Setter
    public function set data(value:Object):void
        {
        dataProperty = value;
        }

    //Data Getter
    public function get data():Object
        { 
        return dataProperty; 
        }

    //List Data Setter
    public function set listData(value:ListData):void
        { 
        listDataProperty = value;
        }

    //List Data Getter
    public function get listData():ListData
        { 
        return listDataProperty; 
        }

    //Selected Setter
    public function set selected(value:Boolean):void
        { 
        selectedProperty = value;

        layout();
        }

    //Selected Getter
    public function get selected():Boolean
        { 
        return selectedProperty;
        }

    //Size And Layout
    private function layout():void
        {
        var newColor:ColorTransform = new ColorTransform();
        newColor.color = dataProperty.randomColor;

        backgroundCanvas.transform.colorTransform = newColor;
        backgroundCanvas.scaleX = cellWidthProperty / backgroundCanvas.width;
        backgroundCanvas.scaleY = cellHeightProperty / backgroundCanvas.height;

        numberTextField.text = dataProperty.randomNumber;
        numberTextField.autoSize = TextFieldAutoSize.LEFT;
        numberTextField.textColor = 0xFFFFFF;
        numberTextField.x = 50;
        numberTextField.y = cellHeightProperty / 2 - numberTextField.height / 2;
        numberTextField.border = true;
        numberTextField.selectable = false;

        button.label = "Delete";
        button.x = cellWidthProperty - button.width - 50;
        button.y = cellHeightProperty / 2 - button.height / 2;
        button.drawNow();
        button.addEventListener(MouseEvent.MOUSE_DOWN, buttonClickEventHandler);

        addChild(backgroundCanvas);
        addChild(numberTextField);
        addChild(button);
        }

    //Button Click Event Handler
    private function buttonClickEventHandler(evt:MouseEvent):void
        {
        List(listDataProperty.owner).removeItemAt(listDataProperty.index);
        }

    //Style Setter
    public function setStyle(style:String, value:Object):void
        {
        }

    //Mouse State Setter
    public function setMouseState(state:String):void
        {
        }
    } 
} 

package
{
import flash.display.Sprite;

public class MySprite extends Sprite
    {
    public function MySprite()
        {
        graphics.beginFill(0xFF0000);
        graphics.drawRect(0, 0, 10, 10);
        graphics.endFill();
        }
    }
}
Chunky Chunk
  • 16,553
  • 15
  • 84
  • 162
  • i'm not sure about it, but i can't see that you create an instance of `CustomListCell`. If you do - imho the best practice is passing a reference to a `List` or `DataProvider` instance to `CustomListCell` constructor. Another option is creating a static getter, but such methods are rather unpopular :) – www0z0k Dec 14 '10 at 19:34
  • you mean, i should write something like: list.setStyle("cellRenderer", CustomListCell(list)); and use the list reference in my cell. i assume that would work, but it kinda feels like overkill. might be the only way? its unfortunate that i can't seem to reference the parent List object from the cellRenderer. – Chunky Chunk Dec 14 '10 at 22:30
  • ok. i seem to have found a way, but it's kinda ugly: trace(List(parent.parent).dataProvider.getItemAt(0).label); the first parent is Sprite and the second parent (or grandparent) is the List reference. – Chunky Chunk Dec 14 '10 at 23:23
  • i'd rather used a static getter (or if you need many Lists - a static object or array containing all of them) :) – www0z0k Dec 14 '10 at 23:47

2 Answers2

2

ugh! the answer was in front of me the whole time! next time remind me to check the docs:

List(listData.owner)

fl.controls.listClasses.ListData.owner

Chunky Chunk
  • 16,553
  • 15
  • 84
  • 162
1

There are multiple ways to do this.

Here is a very hacky solution: Use an icon, and have that icon dispatch a close event.

The idea is you'll place a custom MovieClip in each list cell as icon. That icon will dispatch an event with the index of the cell clicked so you can remove it.

1st step: Make a basic custom event to pass cell index through:

package{

    import flash.events.Event;

    public class CloseEvent extends Event{

        public static const CLOSE:String = 'close';
        public var index:int;

        public function CloseEvent(type:String,bubbles:Boolean = true,cancelable:Boolean=true){
            super(type,bubbles,cancelable);
        }

    }

}

2nd step:: Draw a close icon or something, convert it to MovieClip and Export for Actionscript

3rd step: Add the event listener to dispatch the custom event when the close icon is clicked.

Inside the close icon Movie Clip I've placed the following actions:

import fl.controls.listClasses.CellRenderer;

//setup click
buttonMode = true;
if(parent) parent.mouseChildren = true;
addEventListener(MouseEvent.MOUSE_DOWN,dispatchClose);
//setup event
var closeEvent:CloseEvent = new CloseEvent(CloseEvent.CLOSE,true);
if(parent) closeEvent.index = CellRenderer(parent).listData.index;
//listen to click and pass on
function dispatchClose(event:MouseEvent):void {
    dispatchEvent(closeEvent);
}

Very basic stuff, listen for mouse down, create an event and set the index and dispatch that event on click. The icon is added to a cell renderer, therefor the cell render is it's parent which it has a listData property among others, which holds the index of the cell.

So here's how the test snippet looks:

import fl.data.DataProvider;

var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 30 ; i++) dp.addItem({label:'item'+(i+1),icon:Close});
ls.dataProvider = dp;

addEventListener(CloseEvent.CLOSE,deleteItem);
function deleteItem(event:CloseEvent):void {
    ls.removeItemAt(event.index);
}

Since the CloseEvent bubbles, we can catch it from outside the cell renderer's icon and tell the list to remove the item at that index. It's possible to do that within the icon, but it will be necessary to 'climb' up the hierarchy all the way to the list, and it's pretty hacky already.

I did this because, I was probably as lazy as @TheDarkIn1978 :P to implement the ICellRenderer functions. Then I looked at question code again and didn't understand why the custom cell extends a Sprite, when CellRenderer already implements the ICellRenderer functions already.

So here is my attempt to do it in a less hacky manner:

package{

    import fl.controls.*;
    import fl.controls.listClasses.*;
    import fl.data.*;
    import flash.events.*;

    public class SCListCell extends CellRenderer implements ICellRenderer{

        protected var closeButton:Button;
        protected var closeEvent:CloseEvent;

        override protected function configUI():void {
            super.configUI();
            closeButton = new Button();
            closeButton.label = 'x';
            closeButton.buttonMode = true;
            closeButton.setSize(30,20);
            closeButton.drawNow();
            closeButton.addEventListener(MouseEvent.CLICK,close);
            addChild(closeButton);
            closeEvent = new CloseEvent(CloseEvent.CLOSE);
        }

        private function close(event:MouseEvent):void{
            closeEvent.index = listData.index;
            dispatchEvent(closeEvent);
        }

        override protected function drawLayout():void{
            mouseChildren = true;
            closeButton.x = width-closeButton.width;
        }

    }

}

Used the same CloseEvent to pass the index, and the custom cell has direct access to the listData object to fetch the index, so the sample snippet looks like this:

import fl.data.DataProvider;

var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 30 ; i++) dp.addItem({label:'item'+(i+1)});
ls.dataProvider = dp;

addEventListener(CloseEvent.CLOSE,deleteItem);
function deleteItem(event:CloseEvent):void {
    ls.removeItemAt(event.index);
}

ls.setStyle('cellRenderer',SCListCell);

So to answer your question:

how can i properly access the parent List object from its custom cell renderer?

You can use the listData property of the cell renderer. You can if you want to, but it means going up a few levels:

package{

    import fl.controls.*;
    import fl.controls.listClasses.*;
    import fl.data.*;
    import flash.events.*;

    public class SCListCell extends CellRenderer implements ICellRenderer{

        protected var closeButton:Button;

        override protected function configUI():void {
            super.configUI();
            closeButton = new Button();
            closeButton.label = 'x';
            closeButton.buttonMode = true;
            closeButton.setSize(30,20);
            closeButton.drawNow();
            closeButton.addEventListener(MouseEvent.CLICK,close);
            addChild(closeButton);
        }

        private function close(event:MouseEvent):void{
            List(this.parent.parent.parent).removeItemAt(listData.index);
        }

        override protected function drawLayout():void{
            mouseChildren = true;
        closeButton.x = width-closeButton.width;
        }

    }

}

Which leaves the list creation part as simple as:

import fl.data.DataProvider;

var dp:DataProvider = new DataProvider();
for(var i:int = 0 ; i < 30 ; i++) dp.addItem({label:'item'+(i+1)});
ls.dataProvider = dp;

ls.setStyle('cellRenderer',SCListCell);

CloseEvent isn't needed in this case.

HTH

George Profenza
  • 50,687
  • 19
  • 144
  • 218
  • thanks for the lengthy response. the reason why i'm extending Sprite and implementing ICellRenderer instead of simply extending CellRenderer is because, as far as i know, it's the only way to really have full control over a custom cell: basically a sprite with a full, unlimited display list parenting any display object and the ability to position them anywhere. for example, if i simply extend CellRenderer i don't believe i could have 3 textFields, a few checkBoxes or buttons and be able to place them wherever i want them using plain x and y. – Chunky Chunk Dec 15 '10 at 02:20
  • this method is written about here: "Use A Symbol To Define A CellRenderer": http://help.adobe.com/en_US/ActionScript/3.0_UsingComponentsAS3/WS5b3ccc516d4fbf351e63e3d118a9c65b32-7fd4.html – Chunky Chunk Dec 15 '10 at 02:22
  • @TheDarkIn1978 I see, that makes sense. If it helps, try extending UIComponent as it already implements setStyle, setSize and a few others, might be handy. – George Profenza Dec 15 '10 at 09:38
  • after spending days trying to implement my own ICellRenderer i've realized that there are some things going on in the background that i seriously do not understand. in both extending the CellRenderer or using a symbol to define a cell renderer, adding a shape or sprite with graphics to the display list of the cell really kills performance with touch (automatic) scrolling. it seems i can temporarily remedy the lag by calling list.invalidateList() often, but that seems like such a dirty hack. any ideas what's happening? – Chunky Chunk Dec 23 '10 at 10:37
  • @TheDarkIn1978 How complex are the graphics ? If they're vector heavy, it could impact performance, as the cells get redrawn on rollover/rollout/click/etc. You could either try to enable cacheAsBitmap for the background or export an image and use an image instead of complex vectors, if that's the case – George Profenza Dec 23 '10 at 11:10
  • it works fine if i create the vector graphics in Flash Professional and convert to a symbol. i'm thinking that way, or extending the custom cell from a custom class that extends sprite (essentially programatically creating a symbol - i think?) lightens the load. i guess in the same way that a textField or button component do not slow it down as they are basically symbols. does that make sense? – Chunky Chunk Dec 23 '10 at 11:38
  • @TheDarkIn1978 Not sure. Are you saying you have loads of vectors grouped, as opposed to using symbols ? In this case, yes, having a symbol would make a difference. – George Profenza Dec 23 '10 at 12:04
  • what i was doing that was causing a heavy performance hit was actually creating, drawing and adding to the display list a new sprite within my custom cell renderer class. the reasons you've cited for the cause of the lag makes perfect sense and was certainly the problem. i've since altered my code to incorporate an instance of a custom sprite class (programatic symbol) to the display list of the cell renderer. doing so solved the problem. – Chunky Chunk Dec 23 '10 at 12:30
  • @TheDarkIn1978 nice one! Thanks for sharing your findings. – George Profenza Dec 23 '10 at 14:04
  • interestingly, all new instances that are display objects need to be instantiated before the constructor as a variable (even TextField and other components), otherwise there are some serious slowdowns. i've updated my question with my working custom renderer that implements ICellRenderer. – Chunky Chunk Dec 24 '10 at 09:03