DataGrid ItemRenderer - actionscript-3

Here's the thing: I've this DataGrid that holds a column with the purpose of handle some actions. So, this DataGrid have several data columns and, at the end there is this special column.
At this special column named "operations" there are two icons; one of 'em shows an alarm icon (like a little bell). So, what I've to accomplish is that, initially the alarm icon shows invisible; when the user sets an alarm (through another interface) the alarm icon shows up (with default style color) and when it's time to fire the alarm the alarm icon is supposed to take another style color (like yellow).
So, I've this next definition:
<mx:DataGrid id="dgSomeValues"
dragEnabled="true"
draggableColumns="false"
width="100%" height="100%"
horizontalScrollPolicy="off"
resizableColumns="true"
rowHeight="19">
<components:columns>
<mx:DataGridColumn id="dgcItem" headerText="{resourceManager.getString('resources','columnItem')}" width="70" resizable="false"/>
<!--
Some other dataGridColumns
-->
<mx:DataGridColumn id="dgcOperation" headerText=" " width="50" resizable="false">
<mx:itemRenderer>
<fx:Component>
<components:OperationItemRenderer/>
</fx:Component>
</mx:itemRenderer>
</mx:DataGridColumn>
</components:columns>
And definition for OperationItemRenderer is like this:
import flash.events.MouseEvent;
import com.broker.control.BrokerEvent;
import mx.containers.HBox;
public class OperationItemRenderer extends HBox
{
//--------------------------------------------------------------------------
//
// Variables
//
//--------------------------------------------------------------------------
/**
* Alarm Button --> bell
**/
private var btnAlarm:Icon;
//--------------------------------------------------------------------------
//
// Constructor
//
//--------------------------------------------------------------------------
/**
* Constructor
**/
public function OperationItemRenderer()
{
super();
this.setStyle("horizontalGap",0);
}
//--------------------------------------------------------------------------
//
// Overridden methods
//
//--------------------------------------------------------------------------
/**
* #inheritDoc
**/
override public function set data(value:Object):void
{
super.data = value;
}
/**
* #inheritDoc
**/
protected override function createChildren():void
{
super.createChildren();
if (!btnAlarm){
btnAlarm = new Icon();
btnAlarm.styleName = ""; // Initially do not shows any icon.
btnAlarm.enabled = true;
btnAlarm.addEventListener(MouseEvent.CLICK, btnAlarmClickHandler,false,0,true);
addChild(btnAlarma);
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (btnAlarm){
btnAlarm.width = unscaledWidth/2;
btnAlarm.height= unscaledHeight;
}
}
//--------------------------------------------------------------------------
//
// Methods
//
//--------------------------------------------------------------------------
/**
* If this item has an alarm, then it will show an icon.
* States for alarm icon are: Default --> icnAlarmOff
* Fired --> icnAlarm
*/
public function upgradeIcon(toogle:Boolean):void
{
btnAlarm.styleName = toogle ? "icnAlarm" : "icnAlarmOff";
}
//--------------------------------------------------------------------------
//
// Event Handlers
//
//--------------------------------------------------------------------------
protected function btnAlarmaClickHandler(event:MouseEvent):void
{
if (btnAlarm.styleName == "favIcnAlarma") {
var evt:BrokerEvent;
evt = new BrokerEvent(BrokerEvent.SETUP_ALARM);
dispatchEvent(evt);
}
}
}
The function "upgradeIcon" it's supposed to be called from another section in the application where a rowFunction it's called everytime the DataGrid's datasource is refreshed. At this rowFunction I want to find a way to access DataGrid ItemRenderer so that way I'll be able to call this upgradeIcon function.
The question is, how can I access DataGrid's ItemRenderer programatically? What I already tried is something like this:
var c:ClassFactory = view.dgcOperation.itemRenderer as ClassFactory;
if (c != null && c.generator != null) { // c never is null at this point
var f:OperationItemRenderer = c.generator as OperationItemRenderer;
f.upgradeIcon(change); // f it's always null
//(c.generator as OperationItemRenderer).upgradeIcon(change);
}
But this approach does not work. Any help would be greatfully appreciated.
Thanks in advance!

try to dispatch an event when user set alarm...and add a listener of this event in your renderer, so when a new alarm is set, an event will be dispatched from your alarm interface, and be captured in your renderer which will update your icon style.

Your datagrid must be having a data provider of value objects (say Report is name of your class) . If you have marked your Report class bindable then any change in any of its property will dispatch PropertyChangeEvent and then you can override set Data method in the item-renderer and based on the value of that property you should have taken necessary action.
ItemRenderers are recycled and fetching them in this was is a bad practice. You should not alter an ItemRenderer directly rather alter the underlying object.

Related

Actionscript 3.0 - Custom Events with Parameters

Is there any simple -- or suggested -- way in order to send a parameter along with a custom event? Or even just a way to pass variables between two classes?
My program is a sort of simple Civ/Age of Empires kind of game, where you can place buildings on tiles. This would work as follows:
Player clicks on the icon on the HUD, which creates an dispatches an event which is received by the PLAYER class.
The PLAYER class changes a value depending on which building is held (clicked on).
Player clicks on the tile in the grid to place it, which dispatches an event which is received by the PLAYER class.
The PLAYER class creates a building object and adds it to an array within the PLAYER class.
An example of how I'd want the code to work:
icon.as
private function onMouseClick(e:MouseEvent = null):void {
var iconClickedEvent:Event = new Event("BUILDING_HELD", buildingType); // passes "buildingType" through the event somehow
stage.dispatchEvent(iconClickedEvent);
}
tile.as
private function onMouseClick(e:MouseEvent = null):void {
var buildingPlacedEvent:Event = new Event("BUILDING_PLACED", xRef, yRef);// passes "xRef" & "yRef", the tile's co-ordinate
stage.dispatchEvent(buildingPlacedEvent);
}
player.as
private function init(e:Event):void {
stage.addEventListener("BUILDING_HELD", buildingHeld(buildingType));
stage.addEventListener("BUILDING_PLACED", placeBuilding(xRef, yRef));
}
private function buildingHeld(building:int):void {
buildingType = building;
}
private function placeBuilding(xRef:int, yRef:int):void {
switch(buildingType){
case 1: // main base
MainBaseArray.push();
MainBaseArray[length-1] = new MainBase(xPos, yPos); // create new object with the references passed
break;
}
}
The best way to manage this is to create custom event classes for each of your events (or event types).
If you create a class that inherit Event, it will be usable in the same ways that a standard Event, but can contain custom values or methods.
Here's an example of such class :
public class BuildingEvent extends Event {
// contains an event name. Usefull to ensure at compile-time that there is no mistape in the event name.
public static const BUILDING_HELD:String = "BUILDING_HELD";
private var _buildingType:int;
// the constructor, note the new parameter "buildingType". "bubbles" and "cancelable" are standard parameters for Event, so I kept them.
public function BuildingEvent(type:String, buildingType:int, bubbles:Boolean = false, cancelable:Boolean = false) {
super(type, bubbles, cancelable);
_buildingType = buildingType;
}
// using a getter ensure that a listening method cannot edit the value of buildingType.
public function get buildingType() {
return _buildingType;
}
}
We can then use this class like this :
// to dispatch the event
private function onMouseClick(e:MouseEvent = null):void {
var iconClickedEvent:BuildingEvent = new BuildingEvent(BuildingEvent.BUILDING_HELD, buildingType);
stage.dispatchEvent(iconClickedEvent);
}
// to listen to the event
private function init(e:Event):void {
stage.addEventListener(BuildingEvent.BUILDING_HELD, buildingHeld);
}
private function buildingHeld(event:BuildingEvent):void {
buildingType = event.buildingType;
}

Update spark List control on dataProvider change

I am using spark List control which is bind to an instance of such class:
[Event(name="collectionChange", type="mx.events.CollectionEvent")]
public class HierarchicalCollectionListAdapter extends EventDispatcher implements IList
{
...
}
I want to make List fully re-draw each time when this collection send "reset" collectionChange event. Now to achieve this, each time after collection update I have to call this code:
var _itemRenderer:IFactory = _list.itemRenderer;
_list.itemRenderer = null;
_list.itemRenderer = _itemRenderer;
Is there any way to do it in more elegant way?
It's a bit hard for me to see whats going on here but it looks like your list dataProvider is not extending ListCollectionView so you can't use refresh()
However, you may be able to force a redraw using invalidateDisplayList()
I would try something like this:
_list.dataGroup.invalidateDisplayList();
I'll leave my solution here in case if someone will have same problem.
Best thing that I could do - create extended list control, which will refresh itself each time on collection update. So, create new control, based on spark list and add this code there:
<fx:Script>
<![CDATA[
import mx.collections.IList;
import mx.events.CollectionEvent;
import mx.events.CollectionEventKind;
/**
* Subscribe to collection change event.
*/
override public function set dataProvider(value:IList):void
{
if (dataProvider)
dataProvider.removeEventListener(CollectionEvent.COLLECTION_CHANGE,
dataProvider_collectionChangeHandler);
if (value)
value.addEventListener(CollectionEvent.COLLECTION_CHANGE, _collectionChangeHandler, false, 0, true);
var _itemRenderer:IFactory = this.itemRenderer;
this.itemRenderer = null;
this.itemRenderer = _itemRenderer;
super.dataProvider = value;
}
/**
* If collection has changed - redraw list by resetting itemRenderer.
*/
private function _collectionChangeHandler(event:Event):void
{
if (event is CollectionEvent)
{
var ce:CollectionEvent = CollectionEvent(event);
// We don't need to refresh if any collection element will change.
if(ce.kind != CollectionEventKind.UPDATE)
{
var _itemRenderer:IFactory = this.itemRenderer;
this.itemRenderer = null;
this.itemRenderer = _itemRenderer;
}
}
}
]]>
</fx:Script>

Create dropdownlist with delete button in this itemrenderer

I works with Flex 4.5 and I like to create a custom dropdownlist. Indeed, I'd like to show in each line on my dropdownlist a label and a delete button.
The goal is to delete the line on click to delete button.
This look like simple, but I don't found how to do that.
Thanks for helping
You have to jump through a few hoops for this one because DropDownList prevents any MouseEvent.CLICK from an object inside an ItemRenderer from being fired.
First things first: you will need a custom event for this to work. One that carries your item or at least its index. e.g.:
public class ItemEvent extends Event {
public static const REMOVE:String = "itemRemove";
public var item:MyClass;
public function ItemEvent(type:String, item:MyClass,
bubbles:Boolean=false,
cancelable:Boolean=false) {
super(type, bubbles, cancelable);
this.item = item;
}
override public function clone():Event {
return new ItemEvent(type, item, bubbles, cancelable);
}
}
Then you create a custom ItemRenderer with a 'delete' Button that will dispatch this event.
<s:ItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
private function remove():void {
owner.dispatchEvent(
new ItemEvent(ItemEvent.REMOVE, data as MyClass)
);
}
]]>
</fx:Script>
<s:Label id="labelDisplay" verticalCenter="0" left="10" />
<s:Button verticalCenter="0" right="10" width="16" height="16"
mouseDown="remove()" />
</s:ItemRenderer>
Important here is that you catch the MOUSE_DOWN event of the Button, since its CLICK event doesn't fire (as mentioned before). The owner property of the ItemRenderer refers to the List it is a child of.
Now the last piece of the puzzle. Here's your DropDownList with custom ItemRenderer:
<s:DropDownList id="myDropDownList" dataProvider="{dp}"
itemRenderer="MyItemRenderer" />
And here's how you listen for that custom event and remove the selected item:
myDropDownList.addEventListener(ItemEvent.REMOVE, removeSelectedItem);
private function removeSelectedItem(event:ItemEvent):void {
var items:IList = myDropDownList.dataProvider;
var index:int = items.getItemIndex(event.item);
items.removeItemAt(index);
}
Because we caught the MOUSE_DOWN instead of CLICK the myDropDownList.selectedIndex property will still be at the previously selected item (or -1 if none was selected). This is why we needed the custom event, because there was no other way of knowing which is the item you want to remove.

How to make simple actionscript itemrenderer for Flex Mobile?

I've been making mxml itemRenderers, but from what I hear at adobe, for the Flex Mobile projects they keep saying "make your item renderers with only actionscript3, no mxml"
So...I have this list where im trying to remake the itemRenderer in actionscript the best way I can guess to do so. can some one let me know if im doing something wrong? Maybe I should be doing it in a whole different file..i dont know this is my first time making an all actionscript3 IR.
The text appears, but now my scollToBottom() function no longer works now. I used it with my mxml itemrenderer and it worked fine. so i thought maybe I was doing something wrong here...So this is my primary problem, im assuming something is wrong with how im doing the itemrenderer and thats why the scroll to bottom function wont work anymore.
//my scroll to bottom function that i run after i put something in the list. since its a chat, this way it auto scrolls down for the user to read the latest message.
protected function scrollToBottom():void {
// update the verticalScrollPosition to the end of the List
// virtual layout may require us to validate a few times
var delta:Number = 0;
var count:int = 0;
while (count++ < 10){
chat_list.validateNow();
delta = chat_list.layout.getVerticalScrollPositionDelta(NavigationUnit.END);
chat_list.layout.verticalScrollPosition += delta;
if (delta == 0)
break;
}
}
<?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" width="100%" height="100%" autoDrawBackground="false" contentBackgroundAlpha=".3" creationComplete="itemrenderer1_creationCompleteHandler(event)">
<fx:Style>
#namespace s "library://ns.adobe.com/flex/spark";
#font-face {
src: url("assets/fonts/mpb.ttf");
fontFamily: "myFont";
embedAsCFF: true;
advancedAntiAliasing: true;
}
</fx:Style>
<fx:Script>
<![CDATA[
import mx.core.FlexGlobals;
import mx.core.UIComponent;
import mx.events.FlexEvent;
import spark.components.Label;
import spark.components.VGroup;
private var msgTxt:Label = new Label();
private var nameLabel:Label = new Label();
private var mainContainer:VGroup = new VGroup();
protected function itemrenderer1_creationCompleteHandler(event:FlexEvent):void
{
maxWidth=this.width;
mainContainer.paddingBottom=10;
mainContainer.paddingTop=10;
mainContainer.verticalAlign="bottom";
mainContainer.explicitWidth=this.width;
this.addElement(mainContainer);
msgTxt.setStyle("fontFamily","myFont");
msgTxt.setStyle("color","#000000");
msgTxt.setStyle("fontSize","35");
msgTxt.setStyle("paddingRight","15");
msgTxt.setStyle("paddingTop","10");
msgTxt.setStyle("paddingLeft","15");
msgTxt.explicitWidth=this.width;
mainContainer.addElement(msgTxt);
nameLabel.setStyle("fontFamily","myFont");
nameLabel.setStyle("color","#666666");
nameLabel.setStyle("paddingLeft","5");
nameLabel.setStyle("fontSize","24");
nameLabel.explicitWidth=this.width;
mainContainer.addElement(nameLabel);
}
override public function set data(value:Object):void {
super.data = value;
if (data == null)
return;
if(data.systemMsg)
{
}
if(data.name)
{
nameLabel.text=data.name;
if(data.name == "You: ")
{
nameLabel.setStyle("textAlign","right");
msgTxt.setStyle("textAlign","right");
nameLabel.setStyle("paddingRight","5");
}
else if(data.name == "Them: ")
{
nameLabel.setStyle("textAlign","left");
msgTxt.setStyle("textAlign","left");
}
else
{
nameLabel.setStyle("textAlign","left");
msgTxt.setStyle("textAlign","left");
}
}
if(data.icon)
{
}
if(data.msg)
{
msgTxt.text=data.msg;
}
}
]]>
</fx:Script>
</s:ItemRenderer>
what you are missing are a few function calls that need to be overwritten so that the size and position of your items are correctly measured at the right point in the work flow. Here is a copy/paste of the code from the default Flex Template.
Also, from the look of things is looks like you are trying to put as3 code into a Flex ItemRenderer, but that isn't going to help you performance wise. You are going to need a pure AS3 class that extends a Class such as LabelItemRenderer
/**
* #private
*
* Override this setter to respond to data changes
*/
override public function set data(value:Object):void
{
super.data = value;
// the data has changed. push these changes down in to the
// subcomponents here
}
/**
* #private
*
* Override this method to create children for your item renderer
*/
override protected function createChildren():void
{
super.createChildren();
// create any additional children for your item renderer here
}
/**
* #private
*
* Override this method to change how the item renderer
* sizes itself. For performance reasons, do not call
* super.measure() unless you need to.
*/
override protected function measure():void
{
super.measure();
// measure all the subcomponents here and set measuredWidth, measuredHeight,
// measuredMinWidth, and measuredMinHeight
}
/**
* #private
*
* Override this method to change how the background is drawn for
* item renderer. For performance reasons, do not call
* super.drawBackground() if you do not need to.
*/
override protected function drawBackground(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.drawBackground(unscaledWidth, unscaledHeight);
// do any drawing for the background of the item renderer here
}
/**
* #private
*
* Override this method to change how the background is drawn for this
* item renderer. For performance reasons, do not call
* super.layoutContents() if you do not need to.
*/
override protected function layoutContents(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.layoutContents(unscaledWidth, unscaledHeight);
// layout all the subcomponents here
}

Handle MouseEvent.CLICK, MOUSE_DOWN in subclasses

I'm building a panel of buttons, that are enclosed in Canvas container. For this purpose I created a MyButton class, which is a subclass of UIComponent. MyButton class has 2 other subclasses: MyRadioButton and MyMenuButton, which have another behavior on MouseEvent.MOUSE_DOWN. MyMenuButtoncreates and shows a menu, that I build from XML and it builds ok.
I add listeners in the superclass like this:
this.addEventListener(MouseEvent.CLICK, handleClick);
this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouse);
this.addEventListener(MouseEvent.MOUSE_UP, handleMouse);
It's done at the creation stage.
In my subclasses I override handleMouse handler.
override protected handleMouse(event:MouseEvent) : void
{
// subclass specific handling
}
These button objects are added to canvas container as following:
in class MyButtonsContainer.as:
this.rowChildren.addChild(button);
These buttons draw perfectly and in place. The problem is the behavior:
The event doesn't come to handleClick handler of superclass. And that's the question actually - why can it be?
Thanks in advance
EDIT: It appears that MOUSE_DOWN & MOUSE_UP interfere with CLICK event. When I remove listeners to them I get to click handler.How to cause them live together?
I managed to catch CLICK event as timing difference between MOUSE_DOWN & MOUSE_UP.
EDIT: A short/long explanation: For some reason my listeners for MOUSE_DOWN/MOUSE_UP events were interfering with MouseEvent.CLICK. I was suggested by somebody to give up on listening to MouseEvent.CLICK and instead start a timer in MouseEvent.MOUSE_DOWN and check again the timer in MouseEvent.MOUSE_UP. If the difference less than 0.1 (you can put there another threshold) then it was a click actually.
Now some sample code:
public class MyButton extends UIComponent
{
...
private var _clickTime : Number = 0.;
public function void MyButton()
{
super();
addEventListener(MouseEvent.MOUSE_DOWN, handleMouse);
addEventListener(MouseEvent.MOUSE_UP, handleMouse);
}
protected function checkForClick(event:MouseEvent) : Boolean
{
var down : Boolean = event.type == MouseEvent.MOUSE_DOWN;
if (down)
_clickTime = getTimer();
else {
var diff : Number = getTimer() - _clickTime;
if (diff/1000. < 1.) {
handleClick();
return true;
}
}
return false;
}
protected function handleClick(event:MouseEvent) : void
{
// handle the click here
}
protected function handleMouse(event:MouseEvent) : void
{
checkForClick(event);
...
// take care of your button graphic state
}
DISCLAIMER: This code works for me and good for me and my application needs. If you disagree with this way or have a better way to suggest, please do it in comments. Since this answer answers my own question and this EDIT was added only because I was asked to do so, please don't downvote it if you don't agree with it.
call the superclass method like so
override protected handleMouse(event:MouseEvent) : void
{
super.handleMouse(event);
// subclass specific handling
}
If I'm understanding you correctly, you just need to make sure that in your overriden handlers you end each one with a call to super -
override protected handleMouse(event:MouseEvent):void {
// my logic here for the subclass
trace("blah blah blah");
//now redispatch up to the superclass.
super.handleMouse(event);
}
I'm not 100% sure what you're asking, but I'd override click actions like this..
Base class:
public class MyButton extends UIComponent
{
/**
* Constructor
*/
public function MyButton()
{
// listeners
addEventListener(MouseEvent.CLICK, _click);
// ...other listeners
}
/**
* Called on dispatch of MouseEvent.CLICK
*/
private function _click(e:MouseEvent):void
{
doClick();
}
/**
* Override this method and fill with actions to
* do when this is clicked
*/
protected function doClick():void
{
trace("base class was clicked");
}
}
And the extending class:
public class MyOtherButton extends MyButton
{
/**
* Override doClick
*/
override protected function doClick():void
{
super.doClick();
trace("extending class was clicked");
}
}
After closely inspecting your code:
You've added two different listeners to one function for one, shouldn't these have seperate functions as they are called by contrasting events?
this.addEventListener(MouseEvent.MOUSE_DOWN, handleMouse);
this.addEventListener(MouseEvent.MOUSE_UP, handleMouse);
Also, it seems like you're expecting handleMouse to be fired by a click event judging by the below code, which isn't the case.
override protected handleMouse(event:MouseEvent) : void
{
// subclass specific handling
}
Either that, or you're forgetting to override handleClick as well - which is the method that you're running on a click event. You possibly need to add this code:
override protected handleClick(event:MouseEvent) : void
{
// subclass specific handling for a click
}