Monday, January 17, 2011

AS3 DataGrid component: Part 12

In this tutorial we will learn how to add a ComboBox component into a DataGrid using a custom CellRenderer class.

Here is how it is going to look like:


First of all, here's the code in the .fla file:

import fl.data.DataProvider;
import fl.controls.dataGridClasses.DataGridColumn;

myGrid.move(10,10);
myGrid.setSize(300, 200);

var col_img:DataGridColumn = new DataGridColumn("Role");
myGrid.addColumn(col_img);
col_img.cellRenderer = loaderCellRenderer;
loaderCellRenderer._stage = this;

var col_desc:DataGridColumn = new DataGridColumn("Name");
myGrid.addColumn(col_desc);

var textFormat1:TextFormat = new TextFormat();
textFormat1.size = 16;
textFormat1.color = 0x333333;
textFormat1.bold = true;
textFormat1.font = "Arial";

myGrid.setStyle("headerTextFormat", textFormat1);

var textFormat2:TextFormat = new TextFormat();
textFormat2.size = 10;
textFormat2.font = "Verdana";

myGrid.setRendererStyle("textFormat", textFormat2);

var myData:Array = [{Name: "John", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}])},
{Name: "Ralph", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}])},
{Name: "Ted", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}])},
{Name: "Bob", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}])}
];

myGrid.dataProvider = new DataProvider(myData);
myGrid.rowHeight = 26;
myGrid.rowCount = myGrid.length;

The important thing: the parameter Role contains an array that will become the DataProvider object for the ComboBox.

Now, the loaderCellRenderer.as code:

package 
{

import fl.controls.ComboBox;
import flash.events.Event;
import fl.data.DataProvider;
import fl.controls.listClasses.ListData;
import fl.controls.listClasses.ICellRenderer;

public class loaderCellRenderer extends ComboBox implements ICellRenderer
{
protected var _data:Object;
protected var _listData:ListData;
protected var _selected:Boolean;
private var _selecteditem:Object;
public static var _stage;

public function loaderCellRenderer()
{
super();
addEventListener(Event.CHANGE, onSelItem);
}

public function onSelItem(evt:Event):void
{
_selecteditem = evt.target.selectedItem;
}

public function get data():Object
{
return _data;
}

public function set data(value:Object):void
{
_data = value;
dataProvider = new DataProvider(_data.Role);
selectedItem = _selecteditem;
}

public function get listData():ListData
{
return _listData;
}

public function set listData(value:ListData):void
{
_listData = value;
}

public function get selected():Boolean
{
return _selected;
}

public function set selected(value:Boolean):void
{
_selected = value;
}

public function setMouseState(state:String):void
{
}

}
}

You can see that the class extends ComboBox. We have a new variable called _selecteditem which stores the current selected item. Why is this needed? Because every time we select an item in the ComboBox, the set data function is called, which loads the data provider again, reseting the selected item. We use this _selecteditem variable to pass it to the ComboBox every time we select something.

And that's how you do it!

Thanks for reading!

Related:

AS3 DataGrid component: Part 1
AS3 DataGrid component: Part 2
AS3 DataGrid component: Part 3
AS3 DataGrid component: Part 4
AS3 DataGrid component: Part 5
AS3 DataGrid component: Part 6
AS3 DataGrid component: Part 7
AS3 DataGrid component: Part 8
AS3 DataGrid component: Part 9
AS3 DataGrid component: Part 10
AS3 DataGrid component: Part 11
AS3 DataGrid component: Part 13
AS3 DataGrid component: Part 14
AS3 DataGrid component: Part 15
AS3 DataGrid component: Part 16

16 comments:

agil said...

thx...
but i got this error:

TypeError: Error #2007: Parameter child must be non-null.
at flash.display::DisplayObjectContainer/addChildAt()
at fl.controls::BaseButton/drawBackground()
at fl.controls::BaseButton/draw()
at fl.core::UIComponent/drawNow()
at fl.controls::ComboBox/drawLayout()
at fl.controls::ComboBox/draw()
at fl.core::UIComponent/drawNow()
at fl.controls::DataGrid/drawList()
at fl.controls::DataGrid/draw()
at fl.core::UIComponent/callLaterDispatcher()

agil said...

sorry.. I forgot adding combobox component to my library.. now it's work...

Glyn Barrows said...

Finally, a way to do these things. I am thoroughly enjoying this walkthrough.
I do have a question on part 12.

This works great when the ComboBox is in the first column.

I attempted to move the ComboBox column to the second column by moving these two lines in the fla:

var col_desc:DataGridColumn = new DataGridColumn("Name");
myGrid.addColumn(col_desc);

to just above the "col_img" declaration.

All showed fine, however, when I selected a ComboBox dropdown, initially it displays an empty selection field.

If I close it and reopen it, it seems to function correctly.
This behavior seems to happen when selecting a ComboBox for
the first time or when selecting a ComboBox again after selecting a different one.

If I move the above code back to after , It works perfectly.

It there a way to have the ComboBox in other than the first column?


Regards,

Glyn Barrows
gmb7777@sbcglobal.net

Kirill Poletaev said...

Yes.

After some tinkering with the code and playing with the combo box, I've figured out that the box only responds properly if the whole row is selected.

For example, to work the second combobox properly, you need to select the whole row first (by clicking on the other non-combobox cell in the same row) and then on the combobox.

Changing the set data function in the cellrenderer like this seems to fix it:

public function set data(value:Object):void
{
_data = value;
if (! _selected)
{
dataProvider = new DataProvider(_data.Role);
}
selectedItem = _selecteditem;
}

Glyn Barrows said...

That solution works perfectly. I have been working with this all weekend.
I have created some really neat DataGrids thanks to this tutorial.

I do have a couple of followup questions on the ComboBox Renderer.

Scenario -
Assume data from a database shows John is a Knight and Ralph is an Archer.

Two questions -
How do I prepopulate the datagrid ComboBox display with the desired selection for each?
Then, if I change John to an Archer and Ralph to a Knight, how do I grab the new selected Role for each in the main fla so I can subsequently update the database?

Kirill Poletaev said...

To manually set the selected item of a ComboBox in a DataGrid, you can use the myData array to store the array index that you want to set your value to:

var myData:Array = [{Name: "John", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}]), Selected: 0},
{Name: "Ralph", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}]), Selected: 1},
{Name: "Ted", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}]), Selected: 2},
{Name: "Bob", Role: ([{label:"Footman", data:1},{label:"Archer", data:2},{label:"Knight", data:3}]), Selected: 0}
];

In the CellRenderer class, we can set our value to the one specified in the array once the combo box gets its values. For this, make a variable in the class to make sure we only set the value once:

private var firstTime:Boolean = true;

And change your set data function like this:

public function set data(value:Object):void
{
_data = value;
if (! _selected)
{
dataProvider = new DataProvider(_data.Role);
}
if (firstTime)
{
_selecteditem = _data.Role[_data.Selected];
firstTime = false;
}
selectedItem = _selecteditem;
}


Now you can manually set values for each of the combo box in the data grid.

To send a value back to the main array, change your onSelItem function to this:

public function onSelItem(evt:Event):void
{
_selecteditem = evt.target.selectedItem;
_stage.myData[_listData.index].Selected = evt.target.selectedIndex;
}

Anonymous said...

thanks for all

Anonymous said...

I experienced crashes if we put the column with the combobox at the end of your datagrid (as the right hand latest column ) even after applying the fix from Kirill Poletaev.

Aldo Marsilio www.saintgeorgehotel.net

Anonymous said...

Sick and tired of all customized datagrids I started to build my own.
I developed a class to deal with custom cells without using the datagrid component from Flash.
Here the results go:
http://www.flashbackstage.com/private/datagrid.swf

Aldo Marsilio www.saintgeorgehotel.net

That Guy! said...

I'm implementing this using the additional code to select an item in the combo box, but I get this error:

TypeError: Error #1010: A term is undefined and has no properties.
at loaderCellRenderer/set data()[/Users/.../loaderCellRenderer.as:38]
at fl.controls::DataGrid/drawList()
at fl.controls::DataGrid/draw()
at fl.core::UIComponent/callLaterDispatcher()

Any idea what might be going on here?

The line it's referencing is this one:

if (firstTime) {
*_selecteditem = _data.Role[_data.Selected];
firstTime = false;
}

That Guy! said...

Ah, i see what happened. The _data.Role uses the column name hard-coded in. I changed it and it now appears correctly, but I am getting other errors.

After selecting the dropdown, it closes immediately.Clicking in ita second time gives me an editable combo box (which I don't want) loaded with [object Object],[object Object],[object Object],[object Object],[object Object],[object Object] which the the array of labels and data for the box, I am assuming. How can I turn off the editablility of the column?

Clicking the ouse and holding it down lets me select an item, but produces thjis error:

ReferenceError: Error #1069: Property dgData not found on Tangrammar and there is no default value.
at loaderCellRenderer/onSelItem()[/Users/.../loaderCellRenderer.as:24]

I changed the myData to dgData, but's it's not globally availalable. I am assuming that is causing the problem.

Kirill Poletaev said...

This is weird... The example on this page is using the exact same code as in the tutorial.

Can you try using the code I provided in this tutorial and nothing else (I assume you are adding this to an existing application, right?) and see if that works? Because it might be the fact that you are editing an existing document that is causing the trouble.

That Guy! said...

Ah--I was right about the dgData. Making it globally available fixed the ReferenceError: Error #1069 AND setting the comboBox column.editable=false fixed the problem where the [object Object] kept popping up.

Thanks again. I knew it wasn't your code, but discussing it helped!

That Guy! said...

How would sorting be enabled on columns using this dropdown method?

Anonymous said...

Hi all, first of all thanks for the tutorial really appreaciated it

I doing a custom data grid for playlist where user can sort individual item using an up and down button.

How i do was i remove the item and add to the proper index using (removeItemAt & addItemAt).

problem occur when after i swap, the combo box doesn't seem like swap with the whole row

Quick reply will be much appreciated.
Thanks

Anonymous said...

The column sort doest seem working properly with the custom component, is there a way to fix it?

Post a Comment