ActionScript - Access List/DataProvider From Custo

2019-08-28 21:57发布

问题:

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

回答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



回答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