Grrr so close yet still failing...
I display this tree, in Flex, which contains two nodes types:
Regular nodes, which are rendered normally like text (because they are!)
Rich (HTML) nodes - that's where things get twisted
Note that my issue is when I dynamically add a new (HTML) node to my tree.
So... How do I display HTML nodes?
I subclass TreeItemRenderer
In that subclass, I override set data() and add a text child to my renderer
Therefore I now have:
[icon] [label]
[text component]
Why?
The default label is a pure text component, not HTML-capable, hence the extra component: I want to display the new guy and forget the default label.
(continued) I override updateDisplayList() and, if the node is a rich one, I set label's height to zero, set my component's x and y to label'x and and y.
So...what am I missing? Ah, yes: I need to set my node's height since HTML text can be bigger or smaller than its text counterpart.
(continued) I override measure()
If my node is not a rich one, I simply invoke super.measure() and return
If it is a rich one, I give my html component a width (htmlComponent.width = explicitWidth - super.label.x;) and its height should be automatically computed.
This gives me a fairly reliably unreliable result!
When I fold/unfold my tree, every other time, I seem to get a correct height for my HTML node. The other time I get a height of '4' which happens to be the HTML component's padding alone, without content.
I know that I must be doing something fairly stupid here...but I am not sure what. I will post my code if my rambling is too incoherent to make any sense of...
**** EDIT: here is the source code for my renderer
As you can see, only 'notes' nodes use HTML.
I add a 'htmlComponent' child that will display the rich text while the default label is zero-sized and disappears.
It's definitely very raw code, as it's in progress!
package com.voilaweb.tfd
{
import mx.collections.*;
import mx.controls.Text;
import mx.controls.treeClasses.*;
import mx.core.UITextField;
import mx.core.UIComponent;
import flash.text.TextLineMetrics;
public class OutlinerRenderer extends TreeItemRenderer
{
private function get is_note():Boolean
{
return ('outlinerNodeNote' == XML(super.data).name().localName);
}
override public function set data(value:Object):void
{
super.data = value;
var htmlComponent:Text = super.getChildByName("htmlComponent") as Text;
if(!htmlComponent)
{
htmlComponent = new Text();
htmlComponent.name = "htmlComponent";
addChild(htmlComponent);
}
if(is_note)
htmlComponent.htmlText = XML(super.data).attribute('nodeText');
else
htmlComponent.htmlText = null;
setStyle('verticalAlign', 'top');
}
/*
* Today we've learnt a valuable lesson: there is no guarantee of when createChildren() will be invoked.
* Better be dirty and add children in set data()
override protected function createChildren():void
{
super.createChildren();
var htmlComponent:Text = new Text();
htmlComponent.name = "htmlComponent";
addChild(htmlComponent);
}
*/
override protected function measure():void
{
if(is_note)
{
super.measure();
var htmlComponent:Text = super.getChildByName("htmlComponent") as Text;
//Setting the width of the description field
//causes the height calculation to happen
htmlComponent.width = explicitWidth - super.label.x;
//We add the measuredHeight to the renderers measured height
//measuredHeight += (htmlComponent.measuredHeight - label.measuredHeight);
// Note the silly trick here...hopefully in the future I figure out how to avoid it
//
// Here is what happens: we check if measuredHeight is equal to decoration such as margin, insets...rather than that + some height
// If so, then we need to come up with an actual height which we do by adding textHeight to this height
// Note that I care about text being equal to margin etc but do not have proper access to these
// For instance UITextField.TEXT_HEIGHT_PADDING == 4 but is not accessible
// I am going to check if "<10" that will cover this case...
trace("For text " + htmlComponent.htmlText);
trace("width = " + htmlComponent.getExplicitOrMeasuredWidth()+" x height = " + htmlComponent.getExplicitOrMeasuredHeight());
var m:TextLineMetrics = htmlComponent.measureHTMLText(htmlComponent.htmlText);
//if(10 > htmlComponent.measuredHeight && !isNaN(htmlComponent.explicitHeight))
//htmlComponent.explicitHeight = m.height + htmlComponent.measuredHeight;
//if(htmlComponent.measuredHeight < 10) htmlComponent.explicitHeight = 50;
//measuredHeight += (htmlComponent.getExplicitOrMeasuredHeight() - super.label.getExplicitOrMeasuredHeight());
measuredHeight += (htmlComponent.getExplicitOrMeasuredHeight() - label.getExplicitOrMeasuredHeight());
trace("m:"+m.height+" Height: " + htmlComponent.getExplicitOrMeasuredHeight());
}
else
{
super.measure();
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
label.height = label.getExplicitOrMeasuredHeight(); // If you tell me my height, then I shall use my variable height!
graphics.clear();
if(is_note)
{
label.height = 0;
var htmlComponent:Text = super.getChildByName("htmlComponent") as Text;
htmlComponent.x = label.x;
htmlComponent.y = label.y;
htmlComponent.height = htmlComponent.getExplicitOrMeasuredHeight();
graphics.beginFill(0x555555);
graphics.drawRect(0, 0, unscaledWidth, unscaledHeight);
graphics.endFill();
}
var complete:XMLList = XML(super.data).attribute('complete');
if(complete.length() > 0 && true == complete[0])
{
var startx:Number = data ? TreeListData(listData).indent : 0;
if(disclosureIcon)
startx += disclosureIcon.measuredWidth;
if(icon)
startx += icon.measuredWidth;
graphics.lineStyle(3, getStyle("color"));
var y:Number = label.y + label.getExplicitOrMeasuredHeight() / 2;
graphics.moveTo(startx, y);
graphics.lineTo(startx + label.getExplicitOrMeasuredWidth(), y);
}
}
}
}
You made false assumption about label component in default renderer - it is capable of displaying html content. This renderer works for me:
public class HtmlTreeItemRenderer extends TreeItemRenderer {
override protected function commitProperties():void {
super.commitProperties();
label.htmlText = data ? listData.label : "";
invalidateDisplayList();
}
}
It would certainly help if you could post some code.
I was wondering though why you are using a custom html renderer. Is it because you want to display an icon next to the label since you mention [icon] [label]? If so, you're probably better off using an iconField or iconFunction.
Another thing that comes to mind is the variableRowHeight property. You might need to set this if your nodes have different heights.
Try it.
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
width="100%" height="100%" verticalScrollPolicy="off"
horizontalScrollPolicy="off">
<mx:Script>
<![CDATA[
import mx.core.UITextField;
private var texto:UITextField;
override protected function createChildren():void
{
super.createChildren();
texto = new UITextField();
texto.setColor(0xFFFFFF);
texto.multiline = true;
texto.wordWrap = true;
texto.autoSize = TextFieldAutoSize.LEFT;
this.addChild(texto);
//texto.text = data.title;
}
override public function set data(value:Object):void
{
super.data = value;
if (value)
{
texto.htmlText = value.title;
this.invalidateDisplayList();
}
}
override protected function measure():void
{
super.measure();
}
override protected function updateDisplayList(unscaledWidth:Number,
unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (texto)
texto.width = this.width;
}
]]>
</mx:Script>
</mx:Canvas>
Related
I tried extending TabNavigator and overriding contentY, but it references private tabBarHeight, so to get around that I simply copied the getter contents into my override and subtracted the unwanted 12 pixels, but that made absolutely zero difference.
Anyone have any ideas (Aside from switching to a viewstack!)?
public class Tabber extends TabNavigator
{
public function Tabber(){
super();
}
override protected function get contentY():Number {
var paddingTop:Number = getStyle("paddingTop");
if (isNaN(paddingTop))
paddingTop = 0;
var tabHeight:Number = getStyle("tabHeight");
if (isNaN(tabHeight))
tabHeight = tabBar.getExplicitOrMeasuredHeight();
var tabBarHeight:Number = tabHeight - borderMetrics.top;
return tabBarHeight + paddingTop - 12;
}
}
Have you tried setting tabHeight (with setStyle method)?
Because TabNavigator uses it:
private function get tabBarHeight():Number
{
var tabHeight:Number = getStyle("tabHeight");
if (isNaN(tabHeight))
tabHeight = tabBar.getExplicitOrMeasuredHeight();
return tabHeight - borderMetrics.top;
}
Or maybe changing the verticalGap style...
I have a gird data, and want to set bg color of the grid row based on condition means if there is flag =1 then background grid row will be showing in grey color otherwise as it is in action script file. I used blazeds service.
please help me out. I already tried to setStyle(), but not got success.
Thanks in advance.
This is an actionscript 3 solution.
You need to do it in a special itemRenderer class.
I suggest you extend the Label class and in it override the updateDisplayList function.
public class DynamicItemRenderer extends Label
{
private var _backgroundColorFunction:Function=null;
override protected function commitProperties():void
{
super.commitProperties();
var cellBackgroundColor:Boolean=this.document.hasOwnProperty("cellColorFunction");
if (cellBackgroundColor && _backgroundColorFunction == null)
{
_backgroundColorFunction=this.document.cellColorFunction as Function;
}
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
if (_backgroundColorFunction != null)
{
_backgroundColorFunction.call(this, data, graphics, unscaledWidth, unscaledHeight);
}
}
}
Then in the mxml file, where your DataGrid is defined, you need to set up a function that will color the cell, which will be your backgroundColorFunction.
<mx:Script>
<![CDATA[
public function cellColorFunction(... args):void
{
var item:Object=args[0];
var g:Graphics=args[1] as Graphics;
var width:Number=args[2] as Number;
var height:Number=args[3] as Number;
g.clear();
if (grid.isItemSelected(item) || grid.isItemHighlighted(item))
return;
if (item.flag == 1)
{
g.beginFill(0xE2E2E2);
g.drawRect(0, 0, width, height + 4);
g.endFill();
}
}
]]>
</mx:Script>
Then you need to define the item renderer on your gridColumn and that's it.
<mx:DataGridColumn itemRenderer="path.to.your.DynamicItemRenderer"/>
This is the best solution that I have come up with.
If anyone knows a better way I'd like to see it :)
There's got to be a better way to do this than overriding every component's updateDisplayList with..
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
var componentWithScaling:UIComponent = this.parent as UIComponent;
while(componentWithScaling.scaleX != 1)
{
componentWithScaling = componentWithScaling.parent as UIComponent;
}
var scaleXThatIWant:Number = componentWithScaling.scaleX;
}
I feel like this would be very difficult if I wasn't using just a single top(ish) level scale (note it's not set at the stage level) since multiples would not be equal to 1.
I am developing an app using AS3/Air for mobile devices. I am using Feathers UI and Starling but I would like to create a panel slide (menu, information panel etc) that comes with MadComponents. I was considering just creating a class which held the 'panel', import that on to my screen and use Greensock for the tweening.
My questions are:
Would this be the best way using Starling and Feathers?
Can I use MadComponents with Starling considering it is built using Flash display not Starling?
Any other suggestions how I could achieve the same result?
Basically I just want a button, user clicks the button and the screen tweens left/right and 'opens' the information panel. Push the button again and it closes it.
Many thanks
If I've understood what you've described correctly then it sounds like the ScreenNavigator Class of Feathers is exactly the thing you need?
See ScreenNavigator API
And the Demo of the Feathers Component Explorer that uses the SlideNavigator in the same way you describe.
It looks like Josh Tynjala is looking to add this feature to Feathers: https://github.com/joshtynjala/feathers/issues/527
In the meantime, I wanted the same thing, so I just whipped this up, and I am happy to share it (WARNING: Has not been tested extensively):
package com.hiddenachievement.space.components
{
import feathers.core.FeathersControl;
import starling.animation.Transitions;
import starling.animation.Tween;
import starling.core.Starling;
import starling.display.Sprite;
/**
* Creates a control that emerges from one side of the stage, while moving the main view to show it.
*/
public class EdgeSlideControl extends FeathersControl
{
private var _mainView:Sprite;
private var _open:Boolean;
protected var _tween:Tween;
protected var _side:int;
protected var _callback:Function;
protected var _uncover:Boolean;
static public const NORTH:int = 0;
static public const SOUTH:int = 1;
static public const EAST:int = 2;
static public const WEST:int = 3;
/**
* Creates an EdgeSlideControl.
*
* #param mainView The main view (possibly a ScreenNavigator) that will move to show the menu.
* #param side The side that the control will emerge from.
* #param uncover When true, the main view will slide away to uncover the control. When false, the control will slide in from the side, as the main view slides out of the way.
*
*/
public function EdgeSlideControl(mainView:Sprite, side:int, uncover:Boolean = true)
{
super();
_mainView = mainView;
_side = side;
_uncover = uncover;
}
/**
* Implements the standard FeathersControl's initialize.
*/
override protected function initialize():void
{
_open = false;
visible = false;
}
/**
* Begin the animation to display the control.
*/
public function show(callback:Function=null):void
{
_callback = callback;
_tween = new Tween(_mainView, 0.2, Transitions.LINEAR);
_tween.onUpdate = updateSlide;
_tween.onComplete = slideComplete;
switch(_side)
{
case NORTH:
_tween.animate("y", height);
break;
case SOUTH:
_tween.animate("y", -width);
break;
case EAST:
_tween.animate("x", -height);
break;
case WEST:
_tween.animate("x", width);
break;
}
Starling.juggler.add(_tween);
_open = true;
visible = true;
}
/**
* Begin the animation to hide the control.
*/
public function hide(callback:Function=null):void
{
_callback = callback;
_tween = new Tween(_mainView, 0.2, Transitions.LINEAR);
_tween.onUpdate = updateSlide;
_tween.onComplete = slideComplete;
switch(_side)
{
case NORTH:
case SOUTH:
_tween.animate("y", 0);
break;
case EAST:
case WEST:
_tween.animate("x", 0);
break;
}
Starling.juggler.add(_tween);
_open = false;
}
/**
* If the control is moving (not in "uncover" mode), move the control into place, next to the main view.
*/
public function updateSlide():void
{
if (!_uncover)
{
switch(_side)
{
case NORTH:
y = _mainView.y - height;
break;
case SOUTH:
y = _mainView.height - _mainView.y;
break;
case EAST:
x = _mainView.width - _mainView.x;
break;
case WEST:
x = _mainView.x - width;
break;
}
}
}
/**
* Perform any actions needed after the transition is done animating.
*/
public function slideComplete():void
{
if (_callback != null)
{
_callback();
}
visible = _open;
}
public function get isOpen():Boolean
{
return _open;
}
}
}
You may need to add an explicit size to the component, in the direction of the sliding, if it's coming up zero. Otherwise, the use should be pretty straightforward. Let me know if you need any more guidance on how to use it.
Refining my OOP structure, and trying to make the display of code as intuitive as possible,
using basic OOP principals this definitely helps but my craft has only gone so far. I am looking to improve upon my practice:
package
{
import flash.display.*;
import flash.text.*;
public class Button extends Sprite
{
protected var _spr:Sprite = new Sprite();
protected var _tf :TextField = new TextField();
protected var c :Number;
protected var xx :Number;
protected var yy :Number;
protected var w :Number;
protected var h :Number;
protected var t :String;
protected var a :Number;
protected var l :Number;
protected var lC :Number;
function Button
(
_col:Number, //beginFill
_x:Number, _y:Number, _w:Number, _h:Number, //drawRect
_t:String = "", //TextField (optional)
_a:Number = 1, //beginFill (optional)
_l:Number = 0, _lC:Number = 0xFFFFFF //lineStyle (optional)
)
{
c = _col;
xx = _x;
yy = _y;
w = _w;
h = _h;
t = _t;
a = _a;
l = _l;
lC = _lC;
_spr.addChild(_tf);
draw();
}
public function draw ():void
{
_spr.graphics.clear ();
_spr.graphics.lineStyle(l, lC);
_spr.graphics.beginFill(c);
_spr.graphics.drawRect (xx, yy, w, h);
var f:TextFormat = new TextFormat;
f.font = "Arial";
_tf.text = t;
_tf.autoSize = TextFieldAutoSize.LEFT;
_tf.x = xx + w/2 - _tf.textWidth / 2;
_tf.y = yy + h/2 - _tf.textHeight / 1.5;
_tf.width = w - 2;
_tf.height = h - 2;
_tf.alpha = 0.75;
_tf.setTextFormat(f);
_tf.selectable = false;
_tf.mouseEnabled = false;
_spr.graphics.endFill ();
}
/* ----------------------- *
* GETTERS *
* ----------------------- */
//OVERRIDE
override public function get x () :Number {return (xx)}
override public function get y () :Number {return (yy)}
override public function get width () :Number {return (w)}
override public function get height () :Number {return (h)}
//SUPPLEMENTARY
public function get col () :Number {return (c)}
public function get text () :String {return (t)}
public function get line () :Number {return (l)}
public function get lineC () :Number {return (lC)}
public function get getObj () :Sprite {return (_spr)}
/* ----------------------- *
* SETTERS *
* ----------------------- */
//OVERRIDE
override public function set x (_n:Number) :void
{ xx = getObj.x = Math.round(_n - xx) }
override public function set y (_n:Number) :void
{ yy = getObj.y = Math.round(_n - yy) }
override public function set width (_n:Number) :void
{ w = Math.round(_n) }
override public function set height (_n:Number) :void
{ h = Math.round(_n) }
//SUPPLEMENTARY
public function set col (_n:Number) :void
{
c = _n;
draw();
}
public function set text (_s:String) :void
{
t = _s;
draw();
}
public function set line (_n:Number) :void
{
l = _n;
draw();
}
public function set lineC(_n:Number) :void
{
lC = _n;
draw();
}
}
}
From the above Button class, what interpretations can you make about my structure & organization? I have read and followed many articles on how you should perform OOP logically and I think I am ready for some tuning, or criticism rather on my understanding of how a class should be in AS3 (circumstantial i know.)
Some specific questions:
When I instantiate 'Button' from my Main class, should I add it to the stage there or
within the Button Class itself using Super?
Will the "1047" Error ever be fixed? What is a sustainable (or more efficient) work-around besides the ones already mentioned here?: http://curtismorley.com/2008/10/15/actionscript-error-1047-parameter-initializer-unknown-or-is-not-a-compile-time-constant/
In order to update width/height changes, I must call my draw function after modification from outside the Override function for width & height specifically. Is there a proper way to call draw() through the Override functions? No error output was provided, it seems doing so is against the rules though so I am just calling draw() from Main as a hack fix.. Possibly write a function within button tethered to event of a changed property?
If anyone bothered to parse this wall of text, thanks for reading and I appreciate any criticism you may have, harsh or otherwise :)
First, your variable names are not decriptive. If you're going to name your variables "c", "t", "lc", etc., at least put inline comments that describe what they're for. Even if you comment the parameters that populate them lower down, there's unnecessary overhead with what you've done.
If you've done that because of the historically poor code completion in the IDE, use Flash Builder or one of the excellent code editing tools that can help you type out lineColor, textField, etc.
On closer inspection, it looks like you've done this at least partially because you've reversed the normal convention, which is to have parameters to functions not use underscores, whereas private/protected storage should use underscores, but otherwise should have the same name as the public property. Changing your conventions to match the rest of the AS3 world would go a long way to making your code more readable.
If you don't want to lose the underscores in your function parameters, you could use a snippet in FB to make setting the instance variable to a parameter of the same name.
Invalidation is very well understood in ActionScript, so I'm not sure why you have an issue. It looks something like this:
protected function invalidate():void {
_isInvalid = true;
addEventListener(Event.ENTER_FRAME, validate);
}
protected function validate(e:Event):void {
removeEventListener(Event.ENTER_FRAME, validate);
if (_isInvalid) {
draw();
}
_isInvalid = false;
}
Call this everywhere you're now calling draw(), and it will reduce the number of times you have to update the display list.
MPO is that you should not have constructor arguments for View Classes, as this makes it impossible to use them from the timeline.
You'll probably find that if you're open to using the timeline, a lot of this hard work goes away--you simply add the behavior via an AS Class and draw the graphics in the IDE (using things like 9-slice scaling, filters, etc., to minimize maintenance hassle). If you want a button that looks different, you draw a different one, attach your behavior Class, and done.
A lot of people don't realize that it's possible to maintain good OOP while allowing the timeline to take over much of the heavy lifting. Hope this gives you what you want, or at least gives you some search terms you can move forward with. Note if you're interested in knowing more about leveraging the timeline, post back. The links to the source code on that one were eaten when the post was archived.