Flex DataGrid does not sort correctly if a column contains custom UIComponent - apache-flex

I have a DataGrid with the first column being strings ("Red", "Blue", etc) and the second column being a circle (custom UIComponent) of the corresponding color. If I click on the first column to sort, all the names of the colors are sorted fine but those circles in the second column are in totally wrong order. Anyone knows what went wrong with the code below?
This is the application mxml:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:components="components.*"
minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var myArray:ArrayCollection = new ArrayCollection([
{ name:"Red", color:0xff0000 },
{ name:"Orange", color:0xff8000 },
{ name:"Yellow", color:0xffff00 },
{ name:"Green", color:0x00ff00 },
{ name:"Blue", color:0x0000ff },
{ name:"Purple", color:0xff00ff }
]);
]]>
</fx:Script>
<s:DataGrid dataProvider="{myArray}">
<s:columns>
<s:ArrayCollection>
<s:GridColumn dataField="name"/>
<s:GridColumn dataField="color">
<s:itemRenderer>
<fx:Component>
<s:GridItemRenderer>
<s:VGroup>
<components:Circle color="{data.color}" />
</s:VGroup>
</s:GridItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:GridColumn>
</s:ArrayCollection>
</s:columns>
</s:DataGrid>
</s:Application>
And this is the custom UIComponent:
package components
{
import mx.core.UIComponent;
public class Circle extends UIComponent
{
public var color:uint;
public function Circle()
{
super();
height = 20;
width = 20;
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledWidth);
graphics.clear();
graphics.beginFill(color, 1);
graphics.drawCircle(10, 10, 8);
graphics.endFill();
}
}
}

Your custom component is probably simply not redrawn. The easiest solution to this is to not use a custom component at all and use FXG graphics to draw your circle instead.
Replace your itemrenderer with this one and it should work fine:
<s:GridItemRenderer>
<s:Ellipse width="20" height="20">
<s:fill>
<s:SolidColor color="{data.color}" />
</s:fill>
</s:Ellipse>
</s:GridItemRenderer>
That said, I would put the label and the color sample in one column (instead of two) since they represent the same data.

Related

Getting flex datagrid cell value using column and row indices

i'm using flex for an application but i'm having some trouble to recover data from a datagrid that has a checkbox in one of the columns, the problem is that i can't use the function selectedIndex or selectedItem, because they are used to save the checked items.
Is there a way to recovery the datagrid informations using the column and row indices? Something like:
MyDataGrid[row][column] or MyDataProvider[row][column] or any combination of functions to allow me recovery an information using only the row and column.
Thanks
If you just want to be able to get a cell value of the data grid using row and col indices, you can extend the DataGrid component and add one simple function.
Here is a working example.
It looks something like this:
//CustomDataGrid
<?xml version="1.0" encoding="utf-8"?>
<s:DataGrid xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="400" height="300">
<fx:Script>
<![CDATA[
import spark.components.gridClasses.GridColumn;
public function getElementAt(row:int, col:int):Object
{
if (this.dataProvider.length < row + 1 || this.columns.length < col + 1)
return null;
else
return this.dataProvider.getItemAt(row)[(this.columns.getItemAt(col) as GridColumn).dataField];
}
]]>
</fx:Script>
</s:DataGrid>
Then you call it in your application like this:
//App
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
minWidth="955" minHeight="600"
xmlns:dgrc="com.dgrc.*">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.Alert;
[Bindable]private var collection:ArrayCollection = new ArrayCollection([
{fld01:1, fld02:"fld02", fld03:"fld13", fld04:"fld14"},
{fld01:2, fld02:"fld02", fld03:"fld23", fld04:"fld24"},
{fld01:3, fld02:"fld02", fld03:"fld33", fld04:"fld34"},
{fld01:4, fld02:"fld02", fld03:"fld43", fld04:"fld44"},
{fld01:5, fld02:"fld02", fld03:"fld53", fld04:"fld54"}
]);
protected function getElement():void
{
var obj:Object = myDG.getElementAt(nsRow.value, nsCol.value);
Alert.show(String(obj));
}
]]>
</fx:Script>
<s:VGroup x="20" y="20">
<s:HGroup>
<s:NumericStepper id="nsRow" minimum="0" value="0"/>
<s:NumericStepper id="nsCol" minimum="0" value="0"/>
<s:Button label="Get It!" click="getElement()"/>
</s:HGroup>
<dgrc:CustomDataGrid id="myDG" width="300" height="160" dataProvider="{collection}">
<dgrc:columns>
<s:ArrayList>
<s:GridColumn dataField="fld01" headerText="Field 1" width="100"/>
<s:GridColumn dataField="fld03" headerText="Field 3" width="100"/>
<s:GridColumn dataField="fld04" headerText="Field 4"/>
</s:ArrayList>
</dgrc:columns>
</dgrc:CustomDataGrid>
</s:VGroup>
</s:Application>
Be aware that the row and col indices in this example refer a cell of the data grid and not a value of the data grid provider.
If you call
getElementAt(0, 1)
you will get "fld13"
If you are using the original DataGrid control, it is better to use the itemToLabel method of DataGridColumn, because item[dataField] will cause an exception when the property value contains dot notation, such as "customer.name":
public function getElementAt(row:int, col:int):Object {
var item:Object = dataProvider.getItemAt(row)
var column:DataGridColumn = dataGrid.columns[column] as DataGridColumn;
var value:String = column.itemToLabel(item);
return value;
}
This same method is used by the framework to provide the text displayed in the grid cell. If your checkbox in the grid is driven by a boolean property, the returned value will be true or false.

Flex : How to reuse Item renderer?

I have a data grid table, the gridColumn in the dataGrid table calls a "DropDownListItemRenderer". Using the same item renderer I want to change the contents in the drop down list according to the dataField name. So for example if the dataField name is colour then the drop down will contain red,blue,green ect. If the dataField name is furits, then the drop down list will contain different fruits options.
If u look at the codes in my item Renderer. I tried to do the above by creating a bindable array collection called dropDownListData. In the override set data function I get the dataField name, Using the data field name I add items in array collection accordingly.
Although the dropdown list is filled with the correct data when u run the program. The data in the drop down list is repeated and increased every time when the dropdown list is selected.
I think I am not using the right method to do this. So can someone show me how I do this ? Pls can someone help me with this ? Pls let me know if my question is not clear I will try to rephrase it.
Thanks :)
This is my MXML file :
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<fx:Script>
<![CDATA[
import FrontEndObjects.ColourItems;
import mx.collections.ArrayCollection;
import spark.events.IndexChangeEvent;
[Bindable]
private var order:ArrayCollection = new ArrayCollection();
private function addOrder():void{
var orderItems:ColourItems = new ColourItems();
order.addItem(orderItems);
}
]]>
</fx:Script>
<s:BorderContainer x="175" y="101" width="606" height="289">
<s:DataGrid id="myDG" x="53" y="27" width="516" height="201" dataProvider="{order}"
editable="true" variableRowHeight="true">
<s:columns>
<s:ArrayList>
<s:GridColumn dataField="label1" headerText="Order #" editable="true"/>
<s:GridColumn dataField="quant" headerText="Qty" editable="true"/>
<s:GridColumn dataField="color" headerText="Color" editable="true" rendererIsEditable="true" itemRenderer="myRenderers.DropDownListItemRenderer"/>
<s:GridColumn dataField="furits" headerText="Furits" editable="true" rendererIsEditable="true" itemRenderer="myRenderers.DropDownListItemRenderer"/>
</s:ArrayList>
</s:columns >
</s:DataGrid>
<s:Button x="499" y="236" label="add" click="addOrder()" />
</s:BorderContainer>
This is my DropDownList item Renderer :
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" clipAndEnableScrolling="true">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import spark.components.gridClasses.GridColumn;
import spark.events.IndexChangeEvent;
public var cName:String;
[Bindable]
private var dropDownListData:ArrayCollection = new ArrayCollection();
protected function onCbChange(event:IndexChangeEvent):void
{
var value:String = (event.currentTarget as DropDownList).selectedItem;
data[column.dataField] = value;
}
override public function set data(value:Object):void
{
super.data = value;
cName = column.dataField;
if(cName == "color"){
dropDownListData.addItem("red");
dropDownListData.addItem("blue");
dropDownListData.addItem("green");
}
else if(cName == "furits"){
dropDownListData.addItem("banana");
dropDownListData.addItem("apple");
dropDownListData.addItem("grapes");
}
}
]]>
</fx:Script>
<s:DropDownList id="cb" width="100%" change="onCbChange(event)" requireSelection="true" dataProvider="{dropDownListData}"/>
This is my Object class :
public class ColourItems
{
public var label1:String;
public var quant:String;
public var color:String;
public var furits:String;
}
In the setData method of the itemRenderer, you should check what data already exists in the list before doing the addItem
You set target columns as editable: editable="true" rendererIsEditable="true". When you click for cell, grid calls set data(value:Object) method in itemrenderer where you add items in dropdown list more more and more. You can easy fix it as disabled editable for cell:
<s:GridColumn dataField="color" headerText="Color" editable="false" itemRenderer="myRenderers.DropDownListItemRenderer"/>
<s:GridColumn dataField="furits" headerText="Furits" editable="false" itemRenderer="myRenderers.DropDownListItemRenderer"/>

Flex Spark datagrid - disable selection of a row

I want to disable (and look disabled) some rows in a spark datagrid. I found this answer to stop selection which is great
In flex, is there a way to capture and optionally cancel a row selection event in a DataGrid?
But I want in addition to show that the particular row is not selectable. Ideally I want to have some sort of overlay but I am not sure if that is possible. My alternative solution is to change the text color to grey for the unselectable row. Looking at the datagrid renders they all seem to be column based. I looked at skinning potentially (overriding the alternating color property) but this just sets the background property and not the text color. Is this possible?
Thanks
The most basic solution is using the custom renderer and selection preventing logic you've mentioned:
DataGridRowDisabling.mxml
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" xmlns:local="*">
<fx:Script>
<![CDATA[
import spark.events.GridSelectionEvent;
private function dataGrid_selectionChangingHandler(event:GridSelectionEvent):void
{
var index:int = event.selectionChange.rowIndex;
var product:Product = dataGrid.dataProvider.getItemAt(index) as Product;
if (product && !product.enabled)
event.preventDefault();
}
]]>
</fx:Script>
<s:DataGrid id="dataGrid" itemRenderer="GridItemRenderer2" selectionChanging="dataGrid_selectionChangingHandler(event)">
<s:dataProvider>
<s:ArrayCollection>
<local:Product name="iPod" price="199.99"/>
<local:Product name="iPad 3" price="499.99"/>
<local:Product name="iPad 4" price="599.99" enabled="false"/>
<local:Product name="iPad 5" price="699.99" enabled="false"/>
</s:ArrayCollection>
</s:dataProvider>
</s:DataGrid>
</s:Application>
GridItemRenderer2.mxml
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" clipAndEnableScrolling="true">
<fx:Script>
<![CDATA[
override public function prepare(hasBeenRecycled:Boolean):void
{
if (data is Product)
enabled = Product(data).enabled;
lblData.text = data[column.dataField];
}
]]>
</fx:Script>
<s:Label id="lblData" top="9" left="7"/>
</s:GridItemRenderer>
Product.as
package
{
public class Product
{
public var name:String;
public var price:Number;
public var enabled:Boolean = true;
}
}

Adobe Flex 4.5 Spark: Binding ItemRenderer Component to Parent

In Flex 3, it used to be possible to bind a component property within an itemRenderer via outerDocument. So for instance, if there was a image inside an itemRenderer that was only displayed on a given condition of the parent, something like this would work perfectly:
<mx:itemRenderer>
<mx:Component>
<mx:Label text="{data}"/>
<mx:Image id="img" visible="{outerDocument.ShowImage}" includeInLayout="{outerDocument.ShowImage}"/>
</mx:Component>
</mx:itemRenderer>
where the outer document (not the list, but the mxml the list is in) contained something like
[Bindable]
public function get ShowImage():void
{
return showImage;
}
public function set ShowImage(val:Boolean):void
{
showImage = val;
}
I've tried to do the same thing in Flex 4.5 using Spark item renderers using parentDocument, but it doesn't seem to be aware to the binding. When I do this in Flex 4.5, the itemRenderer doesn't seem to be aware when the parentDocument ShowImage changes.
Has anyone seen this issue and is able to offer a solution?
EDIT: Add Spark Source
As requested here is my spark source:
MyItemRenderer.mxml
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:Label id="myLabel" text="{data}/>
<s:Image src="something.png" visible="{parentDocument.ShowImage}" includeInLayout="{parentDocument.ShowImage}"/>
</s:ItemRenderer>
RendererContainer.mxml
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
private var showImage:Boolean = false;
[Bindable]
public function set ShowImage(val:Boolean):void
{
showImage = val;
}
public function get ShowImage():Boolean
{
return showImage;
}
]]>
</fx:Script>
<!-- Content Group -->
<s:List id="lstCell" width="100%" height="100%" itemRenderer="MyItemRenderer">
</s:List>
</s:Panel>
Ok so there is a checkbox in a wrapper outside of RendererContainer.mxml that dispatches a custom event that is handled by changing a Bindable Boolean. The change in that var then changes the ShowImage property on my RendererContainer component. I would expect that the binding would then be picked up by MyItemRenderer but it doesnt seem to be working.
So my outer wrapper would access ShowImage like this
<comp:RendererContainer id="myId" ShowImage="{myCheckbox.selected}"/>
I think this should do the trick for you, YourTypeHere would be the class of the containing
object, make sure the ShowImage property is public and bindable.
<mx:itemRenderer>
<mx:Component>
<mx:Script>
<![CDATA[
import YourTypeHere;
]]>
</mx:Script>
<mx:Label text="{data}"/>
<mx:Image id="img"
visible="{YourTypeHere(this.parent.ShowImage)}"
includeInLayout="{YourTypeHere(this.parent.ShowImage)}"/>
</mx:Component>
</mx:itemRenderer>
P.s. please don't name properties with a starting uppercase letter, including getters, consider naming it showImage and your private var to something like _showImage instead :D
Your getter seems to have return type as void. Change that to Boolean
[Bindable]
public function get ShowImage():Boolean
{
return showImage;
}
public function set ShowImage(val:Boolean):void
{
showImage = val;
}
This will help.
<s:Image src="something.png" visible="{RendererContainer(ListSkin(parentDocument).parentDocument).ShowImage}" includeInLayout="{RendererContainer(ListSkin(parentDocument).parentDocument).ShowImage}"/>
Following works perfectly fine:
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Panel xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
private var showImage:Boolean = false;
[Bindable]
public function set ShowImage(val:Boolean):void
{
showImage = val;
}
public function get ShowImage():Boolean
{
return showImage;
}
]]>
</fx:Script>
<s:CheckBox label="Select" change="{ShowImage = !ShowImage}"/>
<!-- Content Group -->
<s:List id="lstCell" width="100%" height="100%" dataProvider="{new ArrayCollection(['A','B'])}">
<s:itemRenderer>
<fx:Component>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true">
<s:layout>
<s:HorizontalLayout/>
</s:layout>
<s:Label id="myLabel" text="{data}"/>
<s:Button label="something.png" visible="{outerDocument.ShowImage}" includeInLayout="{outerDocument.ShowImage}"/>
</s:ItemRenderer>
</fx:Component>
</s:itemRenderer>
</s:List>
</s:Panel>
</s:WindowedApplication>

Styling both foreground and background selection color in a Flex list/datagrid

Flex exposes a "selectionColor" CSS property for styling the background color of a selected list/datagrid. However, I cannot figure out how to style the foreground or text color of selected list. It appears you can only change the foreground color for all rows.
So, for example, that I wanted a very dark selection background color and a very light deselected background color. You would similarly want a light text color for selection and a dark text color for deselected.
I know I could do this with a custom item renderer, but that seems rather silly. The point is to style all lists/datagrids in my app. I don't want to have to set a custom item renderer or extend Datagrid for each place I use it. Note that I am using Flex 4 and am willing to use skins though I don't know if that means anything considering DataGrid is not sparkified yet.
Flex 3 used textRollOverColor and textSelectedColor but Flex 4 components does not support them anymore.
The following example demonstrates all this + adding support for this colors for spark List:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Style>
#namespace s "library://ns.adobe.com/flex/spark";
#namespace mx "library://ns.adobe.com/flex/mx";
global
{
textRollOverColor: yellow;
textSelectedColor: green;
}
</fx:Style>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
private function getListDataProvider():ArrayCollection
{
return new ArrayCollection([ "Item 1", "Item 2", "Item 3"]);
}
private function getGridDataProvider():ArrayCollection
{
return new ArrayCollection([ { name: "Item 1" }, { name: "Item 2" }, { name: "Item 3" } ]);
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout horizontalAlign="center" verticalAlign="middle"/>
</s:layout>
<s:List dataProvider="{getListDataProvider()}"/>
<s:List dataProvider="{getListDataProvider()}" itemRenderer="ColoredItemRenderer"/>
<mx:List dataProvider="{getListDataProvider()}"/>
<mx:DataGrid dataProvider="{getGridDataProvider()}"/>
</s:Application>
ColoredItemRenderer:
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="true">
<fx:Script>
<![CDATA[
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var color:uint;
if (selected)
color = getStyle("textSelectedColor");
else if (hovered)
color = getStyle("textRollOverColor");
else
color = getStyle("color");
sparkLabel.setStyle("color", color);
}
]]>
</fx:Script>
<s:Label id="sparkLabel" text="{data}"/>
</s:ItemRenderer>
Here is an example on how to do it in Flex 4 using states and itemRenders
<s:List itemRenderer="com.renderer.GlossaryRenderer" change="handleGridClick(event)" width="293" height="206" styleName="glossaryList" dataProvider="{_glossary}">
<s:layout>
<s:VerticalLayout horizontalAlign="justify" paddingLeft="5" requestedRowCount="9" />
</s:layout>
</s:List>
You could set up your item renderer like this
<?xml version="1.0" encoding="utf-8"?>
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="false">
<s:states>
<s:State name="normal" />
<s:State name="hovered" />
<s:State name="selected" />
</s:states>
<s:Label id="sparkLabel" backgroundColor.selected="#ff0000" color.selected="#FFFFFF" color.hovered="#FFFFFF" text="{data.word}" left="2" right="2" top="4" bottom="4" />
</s:ItemRenderer>
For Spark DataGrid you need to create a new item renderer based on GridItemRenderer. Then assign that to the DataGrid itemRenderer property.
MyGridItemRender.mxml:
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
width="100%"
height="100%">
<fx:Script>
<![CDATA[
override public function prepare(hasBeenRecycled:Boolean):void {
super.prepare(hasBeenRecycled);
var styleClient:IStyleClient = owner as IStyleClient;
var color:uint;
if (selected && styleClient.getStyle("textSelectionColor")!==undefined) {
color = styleClient.getStyle("textSelectionColor");
}
else if (selected && styleClient.getStyle("textSelectedColor")!==undefined) {
color = styleClient.getStyle("textSelectedColor");
}
else if (hovered && styleClient.getStyle("textRollOverColor")!==undefined) {
color = styleClient.getStyle("textRollOverColor");
}
else {
color = styleClient.getStyle("color");
}
labelDisplay.setStyle("color", color);
}
]]>
</fx:Script>
<s:Label id="labelDisplay"
paddingLeft="3" paddingRight="3"
paddingTop="5" paddingBottom="5"
verticalCenter="2"
left="2"/>
</s:GridItemRenderer>
Code:
<s:DataGrid id="dataGrid" itemRenderer="MyGridItemRenderer"/>
You can also use this DataGrid, which has it on by default.

Resources