Row renderer with height depending on row index - apache-flex

I have an IDropInListItemRenderer that is being used inside a List. The height of the row depends on both the data and it's position in the List's view.
How do I remeasure as soon as the row index changes?
updateDisplayList doesn't get called every time, and also by that point it's too late because the List has already measured it.
edit
The effect I'm trying to achieve is similar to iOS where the header for a section sticks to the top
Here is the basic renderer that doesn't measure correctly:
import mx.controls.Label;
import mx.controls.listClasses.BaseListData;
import mx.controls.listClasses.IDropInListItemRenderer;
import mx.controls.listClasses.IListItemRenderer;
import mx.core.UIComponent;
import mx.events.FlexEvent;
public class MyRowRenderer extends UIComponent implements IListItemRenderer, IDropInListItemRenderer {
private static const HEADER_HEIGHT:int = 20;
private static const LABEL_HEIGHT:int = 20;
private var _data:Object;
private var _label:Label;
private var _header:Label;
private var _listData:BaseListData;
public function MyRowRenderer() {
super();
}
[Bindable("dataChange")]
public function get data():Object {
return _data;
}
public function set data(value:Object):void {
this._data = value;
invalidateProperties();
dispatchEvent(new FlexEvent(FlexEvent.DATA_CHANGE));
}
[Bindable("dataChange")]
public function get listData():BaseListData {
return _listData;
}
public function set listData(value:BaseListData):void {
_listData = value;
}
override protected function createChildren():void {
super.createChildren();
_header = new Label();
addChild(_header);
_label = new Label();
addChild(_label);
}
override protected function measure():void {
super.measure();
if (_label == null || _header == null) {
return;
}
var h:Number = LABEL_HEIGHT;
if (_listData.rowIndex == 0) {
h += HEADER_HEIGHT;
}
explicitHeight = measuredHeight = height = h;
}
override protected function commitProperties():void {
super.commitProperties();
_label.text = _data.label;
_header.text = _data.header;
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);
_header.visible = _header.includeInLayout = (_listData.rowIndex == 0);
if (_header.visible) {
_header.move(0, 0);
_header.setActualSize(unscaledWidth, HEADER_HEIGHT);
_label.move(0, HEADER_HEIGHT);
_label.setActualSize(unscaledWidth, LABEL_HEIGHT);
} else {
_label.move(0, 0);
_label.setActualSize(unscaledWidth, LABEL_HEIGHT);
}
}
}
}

I ended up creating a subclass of List and overriding the shiftRow function to dispatch a custom RowIndexChangeEvent. In the row renderer's set listData it adds an event listener to the owner for the RowIndexChangeEvent and invalidates it's properties, size and displaylist.

Related

JavaFx-14 resizeColumnToFitContent method

JavaFx-14 put this method in the TableColumnHeader, rather than in the Skin. How does one find a TableColumnHeader from a TableColumn and a TableView?
Don't know if you still need this, but if anyone else is interested, this is how I surpassed the problem in java, based on David Goodenough's scala code above.
The class for the TableSkin
import javafx.scene.control.TableColumnBase;
import javafx.scene.control.TableView;
import javafx.scene.control.skin.NestedTableColumnHeader;
import javafx.scene.control.skin.TableColumnHeader;
import javafx.scene.control.skin.TableHeaderRow;
import javafx.scene.control.skin.TableViewSkin;
import java.util.ArrayList;
import java.util.List;
public class CustomTableViewSkin extends TableViewSkin<Track> {
private List<CustomTableColumnHeader> columnHeadersList = new ArrayList<>();
private class CustomTableColumnHeader extends TableColumnHeader {
/**
* Creates a new TableColumnHeader instance to visually represent the given
* {#link TableColumnBase} instance.
*
* #param tc The table column to be visually represented by this instance.
*/
public CustomTableColumnHeader(TableColumnBase tc) {
super(tc);
}
public void resizeColumnToFitContent() {
super.resizeColumnToFitContent(-1);
}
}
public CustomTableViewSkin(TableView<Track> tableView) {
super(tableView);
}
#Override
protected TableHeaderRow createTableHeaderRow() {
return new TableHeaderRow(this) {
#Override
protected NestedTableColumnHeader createRootHeader() {
return new NestedTableColumnHeader(null) {
#Override
protected TableColumnHeader createTableColumnHeader(TableColumnBase col) {
CustomTableColumnHeader columnHeader = new CustomTableColumnHeader(col);
if (columnHeadersList == null) {
columnHeadersList = new ArrayList<>();
}
columnHeadersList.add(columnHeader);
return columnHeader;
}
};
}
};
}
public void resizeColumnToFit() {
if (!columnHeadersList.isEmpty()) {
for (CustomTableColumnHeader columnHeader : columnHeadersList) {
columnHeader.resizeColumnToFitContent();
}
}
}
}
And the class for the TableView
import javafx.scene.control.TableView;
public class CustomTableView extends TableView<Foo> {
private final CustomTableViewSkin thisSkin;
public CustomTableView() {
super();
setSkin(thisSkin = new CustomTableViewSkin(this));
}
public void resizeColumnsToFitContent() {
if (thisSkin != null && getSkin() == thisSkin) {
thisSkin.resizeColumnToFit();
}
}
}
Well this code is Scala not Java, but for the record the code below works:-
skin = new TableViewSkin(this) {
override protected def createTableHeaderRow:TableHeaderRow = {
new TableHeaderRow(this) {
override protected def createRootHeader:NestedTableColumnHeader = {
new NestedTableColumnHeader(null) {
override protected def createTableColumnHeader(col:TableColumnBase[_,_]) = {
val tableColumnHeader = new MyTableColumnHeader(col)
if(col == null || col.getColumns.isEmpty || col == getTableColumn) tableColumnHeader else new NestedTableColumnHeader(col)
}
}
}
}
}
}
private class MyTableColumnHeader(tc:TableColumnBase[_,_]) extends TableColumnHeader(tc) {
def resizeCol():Double = {
resizeColumnToFitContent(-1)
width.value
}
}
and then when I want to use it I use kleopatra's suggestion and:-
val w = columns.map { col =>
// To find the TableColumnHeader we can use column.getStyleableNode as suggested by kleopatra on StackOverflow:-
// you get the header from coumn.getStyleableNode (took a moment, had to check if it's really implemented) – kleopatra Jul 1 at 20:46
col.getStyleableNode() match {
case mtch:MyTableColumnHeader => mtch.resizeCol
case _ => col.width.get
}
}.sum
This is a hack'ish way of getting the TableColumnHeader:
public TableColumnHeader getTableColumnHeader(TableView<?> table, int index) {
return (TableColumnHeader) table.queryAccessibleAttribute(AccessibleAttribute.COLUMN_AT_INDEX, index);
}
Or, as #kleopatra suggested, for a non-hack'ish approach you can do:
public TableColumnHeader getTableColumnHeader(TableView<?> table, int index) {
return (TableColumnHeader) table.getColumns().get(index).getStyleableNode();
}
Make sure that the TableView is part of the scene graph.
However, the resizeColumnToFitContent method is protected and you won't be able to access it.

Error #1009, navigator is null

I've got some problem in Flex. Basically I want to navigate to other pages by using navigator.pushview from list through custom item renderer. This is my CustomItemRender.as. Edit:
package renderer
{
import flash.events.MouseEvent;
import mx.core.FlexGlobals;
import mx.events.FlexEvent;
import mx.events.ItemClickEvent;
import spark.components.LabelItemRenderer;
import spark.components.NavigatorContent;
import spark.components.ViewNavigator;
public class CustomItemRender extends LabelItemRenderer
{
protected var var_A:Image;
[Bindable]
public var navigator:ViewNavigator = new ViewNavigator();
public function PrgListItemRenderer()
{
super();
}
override public function set data(value:Object):void
{
super.data = value;
}
override protected function createChildren():void
{
super.createChildren();
if(!takeAtt)
{
var_A= new Image();
var_A.source = "data/pics/var_A.png";
var_A.width = 23;
var_A.height = 23;
var_A.buttonMode = true;
var_A.addEventListener(MouseEvent.CLICK, var_AItem);
addChild(var_A);
}
}
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
if(selected || showsCaret)
{
graphics.beginFill(0xffffff, 1);
graphics.endFill();
}
}
public function var_AItem(event:MouseEvent):void
{
trace("navigator: "+navigator);
navigator.pushView(nextView); //this is the line that have error #1009
}
}
}
But I got Error #1009. Help me please. Thanks.
I think it's a bad idea to listen to the click event inside the item renderer.
Your basic setup should look something like this:
->ViewNavigatorApplication>
-->SomeCustomView
---> SomeListBasedComponent id="list" itemRenderer="someCustomRenderer"
Fill the list with some data, which will be presented by your itemRenderer.
Now listen to the "IndexCangeEvent" of the list (from your view) and handle the 'click' there
To your view add:
private function init():void
{
list.addEventListener(IndexChangeEvent.CHANGE , onIndexChange );
}
protected function onIndexChange(e:IndexChangeEvent):void
{
// find out which item was selected. You can use the selectedItem property for this
var item:Object = list.selectedItem;
// start the view;
navigator.pushView(MyViewClass , item.someViewData );
}
Your view will hold the reference to the ViewNavigator.
P.S. dont forget to call the init() function onCreationComplete() of your view.
To your view declaration add:
View ... creationComplete="init()" >

How to add an EventListener to a moiveClips/Sprites's scaleX change event?

How can i trigger an event for the scaleX property change of the movieClip or Sprite in Flash AS 3.
For Example: CustomSprite.as
package view {
import flash.display.Sprite;
import flash.events.Event;
import events.ScaleChangeEvent;
public class CustomSprite extends Sprite {
override public function set scaleX(value:Number):void {
super.scaleX = value;
dispatchEvent(new ScaleChangeEvent(ScaleChangeEvent.SCALE_CHANGED));
}
public function CustomSprite() {
super();
}
override public function dispatchEvent(event:Event):Boolean {
if (willTrigger(event.type)) {
return super.dispatchEvent(event);
}
return true;
}
}}
ScaleChangeEvent.as
package events {
import flash.events.Event;
public class ScaleChangeEvent extends Event {
public static const SCALE_CHANGED:String = "scaleChanged";
public function ScaleChangeEvent(type:String, bubbles:Boolean = false, cancelable:Boolean = false) {
super(type, bubbles, cancelable);
}
override public function clone():Event {
return new ScaleChangeEvent(type, bubbles, cancelable);
}
override public function toString():String {
return formatToString("ScaleChangeEvent", type, bubbles, cancelable, eventPhase);
}
}}
Test.as
package {
import flash.display.Sprite;
import flash.events.Event;
import view.CustomSprite;
import events.ScaleChangeEvent;
public class Test extends Sprite {
public function Test() {
addEventListener(Event.ADDED_TO_STAGE, addedToStageListener, false, 0, true);
}
private function addedToStageListener(event:Event):void {
removeEventListener(Event.ADDED_TO_STAGE, addedToStageListener);
var customSprite:CustomSprite = new CustomSprite();
customSprite.addEventListener(ScaleChangeEvent.SCALE_CHANGED, scaleChangedListener, false, 0, true);
customSprite.scaleX = 0.2;
}
private function scaleChangedListener(event:ScaleChangeEvent):void {
trace(event.target.scaleX);
}
}}

Why is my override protected function createChildren being ignored?

Here is the error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at view::ScoreBoard/setChipCount()[C:\Flex Builder 3\StackOverflowQuestion\src\view\ScoreBoard.as:32]
at model::MainDeckScoreBoard()[C:\Flex Builder 3\StackOverflowQuestion\src\model\MainDeckScoreBoard.as:21]
at model::MainDeckScoreBoard$cinit()
at global$init()[C:\Flex Builder 3\StackOverflowQuestion\src\model\MainDeckScoreBoard.as:5]
at main()[C:\Flex Builder 3\StackOverflowQuestion\src\main.mxml:13]
at _main_mx_managers_SystemManager/create()
at mx.managers::SystemManager/initializeTopLevelWindow()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3188]
at mx.managers::SystemManager/http://www.adobe.com/2006/flex/mx/internal::docFrameHandler()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:3064]
at mx.managers::SystemManager/docFrameListener()[C:\autobuild\3.2.0\frameworks\projects\framework\src\mx\managers\SystemManager.as:2916]
Here is main.mxml:
<?xml version="1.0"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="initApp()"
>
<mx:Script>
<![CDATA[
import model.MainDeckScoreBoard;
public var _mainDeckScoreBoard:MainDeckScoreBoard = MainDeckScoreBoard.instance;
private function initApp():void {
this.addChild(_mainDeckScoreBoard);
}
]]>
</mx:Script>
</mx:Application>
Here is MainDeckScoreBoard.as:
package model {
import view.ScoreBoard;
[Bindable]
public dynamic class MainDeckScoreBoard extends ScoreBoard {
/** Storage for the singleton instance. */
private static const _instance:MainDeckScoreBoard = new MainDeckScoreBoard( SingletonLock );
/** Provides singleton access to the instance. */
public static function get instance():MainDeckScoreBoard {
return _instance;
}
public function MainDeckScoreBoard( lock:Class ) {
super();
// Verify that the lock is the correct class reference.
if ( lock != SingletonLock ) {
throw new Error( "Invalid Singleton access. Use MainDeckScoreBoard.instance." );
}
this.setChipCount("0");
}
} // end class
} // end package
class SingletonLock {
} // end class
Here is ScoreBoard.as:
package view {
import mx.containers.HBox;
import view.ScoreBoardLabel;
import view.ChipCountContainer;
import view.CardRankList;
public dynamic class ScoreBoard extends HBox {
/** The chip count. */
public var _chipCount:ChipCountContainer;
public function ScoreBoard() {
super();
this.width = 489;
this.height = 40;
this.setStyle("horizontalScrollPolicy", "off");
}
override protected function createChildren():void {
super.createChildren();
if(!_chipCount) {
_chipCount = new ChipCountContainer();
this.addChild(_chipCount);
}
}
public function setChipCount(labelText:String):void {
_chipCount._chipCountLabel.text = labelText;
invalidateDisplayList();
}
}
}
Here is ChipCountContainer.as:
package view {
import mx.containers.Canvas;
public dynamic class ChipCountContainer extends Canvas {
/** The label. */
public var _chipCountLabel:ChipCountLabel;
public function ChipCountContainer() {
super();
this.width = 20;
this.height = 20;
}
override protected function createChildren():void {
super.createChildren();
if(!_chipCountLabel) {
_chipCountLabel = new ChipCountLabel();
this.addChild(_chipCountLabel);
}
}
}
}
I've methodically moved things around and waved the invalidate Display List incense while performing a create Children dance but I've only succeeded in completely confusing myself. I've searched the Flex libraries for similar constructions, and it looks OK to me, but I guess I'm just not getting the concept.
I think you're confusing the order of instantiation. Namely, if you want to use setChipCount after the children of the component have been initialized, you should wait for the initialize event to fire, i.e.:
public dynamic class MainDeckScoreBoard extends ScoreBoard {
...
public function MainDeckScoreBoard( lock:Class ) {
super();
// Verify that the lock is the correct class reference.
if ( lock != SingletonLock ) {
throw new Error( "Invalid Singleton access. Use MainDeckScoreBoard.instance." );
}
// wait for the children to be created
addEventListener(FlexEvent.INITIALIZE, onInitialize);
}
// executes when the children of this component have been created
private function onInitialize(event:FlexEvent):void {
this.setChipCount("0");
}
} // end class
For a more detailed explanation of the component instantiation lifecycle, this adobe doc may be helpful:
http://livedocs.adobe.com/flex/3/html/help.html?content=components_06.html

Flex DataGrid with row number column

I want to extend the DataGrid component so that there is a (read-only) column for the row number like you see in spreadsheets. I came across this article http://www.cflex.net/showFileDetails.cfm?ObjectID=735 but it depends on the data being unique for each row so that it can index into the array. If the data is not unique (like for an empty grid) it doesn't work. How can I implement that?
This worked for me:
<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.controls.AdvancedDataGrid;
private var handleDataChangedEnabled:Boolean = false;
override public function set data(value:Object):void {
super.data = value;
if (!handleDataChangedEnabled) {
addEventListener("dataChange", handleDataChanged);
}
}
public function handleDataChanged(event:Event):void {
this.text = String(listData.rowIndex + (listData.owner as AdvancedDataGrid).verticalScrollPosition + 1);
}
]]>
</mx:Script>
Of course, you would have to change AdvancedDataGrid to DataGrid.
Cheers.
ensure that the dataProvider has a unique column or property, then dont show that column/property if you dont wont to.
The key is the dataProvider
Just use this class as your itemRenderer:
RowNumColumnRenderer.as
package
{
import mx.collections.IList;
import mx.controls.AdvancedDataGrid;
import mx.controls.Label;
import mx.controls.listClasses.ListBase;
public class RowNumColumnRenderer extends Label
{
override public function set data(value:Object):void
{
super.data = value;
if (listData != null)
this.text = (AdvancedDataGrid(listData.owner).itemRendererToIndex(this) + 1).toString();
}
}
}
I was able to do this by implementing a custom itemRenderer, RowNumberRenderer.as
package com.domain
{
import mx.collections.IList;
import mx.controls.Label;
import mx.controls.listClasses.ListBase;
public class RowNumberRenderer extends Label
{
public function RowNumberRenderer()
{
super();
}
override public function set data(value:Object):void
{
super.data = value;
this.text = String(IList(ListBase(listData.owner).dataProvider).getItemIndex(data) + 1);
}
}
}
how about the following:
RendererRowIndexPlusOne.as
package
{
import mx.controls.Label;
import mx.utils.StringUtil;
import mx.utils.ObjectUtil;
public class RendererRowIndexPlusOne extends Label
{
public override function set data(item:Object):void {
super.data = item;
trace('listData.label ' + listData.label);
trace('listData.rowIndex ' + listData.rowIndex);
trace('listData.columnIndex ' + listData.columnIndex);
trace('listData.owner ' + listData.owner);
text = String(listData.rowIndex + 1);
}
}
}

Resources