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.
Related
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 :)
Let's say I have a 2D level that is build out of 2D blocks. Some of them are boxes.
The boxes look just the same. No difference! How can I "copy" or clone the graphics of one box to another ? The only difference the boxes will have is that sprite.x and sprite.y have different values. I would probably go that way:
public static function drawBox(graphics:Graphics):void
{
graphics.clear();
// draw box
}
drawBox(box1.graphics);
drawBox(box2.graphics);
drawBox(box3.graphics);
No textures will be used, only vector drawing!
Is this a good practice ? Is there another way to achieve the same ?
Update: Sometimes I draw sprites randomly (very hard to redraw them if I need many instances of one sprite and all its attributes).
You can use the function copyFrom.
Something like this:
var s:Sprite = new Sprite();
s.graphics.beginFill(0);
s.graphics.drawRect(0, 0, 100, 100);
s.graphics.endFill();
addChild(s);
var s2:Sprite = new Sprite();
// Copyfrom accepts a `Graphics` object.
s2.graphics.copyFrom(s.graphics);
s2.x = 100;
s2.y = 100;
addChild(s2);
Have a look at the documentation about copyFrom().
If you treat it like an object in your game you should probably rather consider the OOP-approach #Pier suggested.
This comes in a lot of flavors:
You could extend a class from Sprite and draw the box as soon as the box is ADDED_TO_STAGE by its parent.
public class box extends Sprite
{
protected var _color:uint;
protected var _size:int;
public function box(size:int=100, color:uint=0x000000)
{
super();
_size = size;
_color = color;
this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
}
protected function onAddedToStage(event:Event):void
{
this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
draw();
}
protected function draw():void
{
this.graphics.beginFill(_color);
this.graphics.drawRect(0,0,_size,_size);
this.graphics.endFill();
}
}
This box can be constructed/created by calling:
var b:box = new box();
this.addChild(b);
Or you could let the box contain itself - which could be more feasible if you deal with a lot of Objects. The box would just need a reference to its parent then - and of course it should provide a dispose()-function
public class box
{
private var _parent:Sprite;
protected var s:Sprite;
public function box(parent:Sprite)
{
_parent = parent;
s = new Sprite();
s.graphics.beginFill(0x00000);
s.graphics.drawRect(0,0,100,100);
s.graphics.endFill();
_parent.addChild(s);
}
public function dispose():void
{
_parent.removeChild(s);
}
}
}
In this case you would construct the box as follows - it requires a reference to a Sprite (or any extension of) that has already been added to the stage:
var b:box = new box(this);
In both cases you could dynamically change attributes and make the object more versatile:
public function set size(val:int):void
{
_size = val;
draw();
}
public function set color(val:uint):void
{
_color = val;
draw();
}
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.
I am trying to perform two way binding e.g I have a button (out of many controls), on its selection, I am showing the values of its diff properties(like height, width etc) in some textinput. This one way process works fine.
But the reverse process doesn't work. i.e When I select some button, and try to change its dimension by entering some value in height, width textinputs, the dimension are not changed.
How to know which button was selected by me? How events needs to be handled here ?
private void Form1_Load(object sender, System.EventArgs e)
{
//Create some data and bind it to the grid
dt1 = GetData(1000, 3);
this.UltraGrid1.DataSource = dt1;
//Set the grid's CreationFilter to a new instance of the NumbersInRowSelectors class.
this.UltraGrid1.CreationFilter = new NumbersInRowSelectors();
}
private void UltraGrid1_InitializeLayout(object sender, Infragistics.Win.UltraWinGrid.InitializeLayoutEventArgs e)
{
//Hide the default images that are drawn in the RowSelectors, like the pencil and asterisk, etc.
e.Layout.Override.RowSelectorAppearance.ImageAlpha = Infragistics.Win.Alpha.Transparent;
//Center the text in the RowSelectors.
e.Layout.Override.RowSelectorAppearance.TextHAlign = Infragistics.Win.HAlign.Center;
e.Layout.Override.RowSelectorAppearance.TextVAlign = Infragistics.Win.VAlign.Middle;
//There is no wy to change the width of the RowSelectors.
//Use a smaller font, so that 3-digit numbers will fit.
e.Layout.Override.RowSelectorAppearance.FontData.Name = "Small Fonts";
e.Layout.Override.RowSelectorAppearance.FontData.SizeInPoints = 6;
}
//The NumbersInRowSelectors class. This class Implements a CreationFilter and
//adds a TextUIElement to each RowSelector which displays the row number of
//the row.
public class NumbersInRowSelectors:Infragistics.Win.IUIElementCreationFilter
{
#region Implementation of IUIElementCreationFilter
public void AfterCreateChildElements(Infragistics.Win.UIElement parent)
{
//Don't need to do anything here
}
public bool BeforeCreateChildElements(Infragistics.Win.UIElement parent)
{
//Declare some variables
Infragistics.Win.TextUIElement objTextUIElement;
Infragistics.Win.UltraWinGrid.RowSelectorUIElement objRowSelectorUIElement;
Infragistics.Win.UltraWinGrid.UltraGridRow objRow;
int RowNumber;
//Check to see if the parent is a RowSelectorUIElement. If not,
//we don't need to do anything
if (parent is Infragistics.Win.UltraWinGrid.RowSelectorUIElement)
{
//Get the Row from the RowSelectorsUIElement
objRowSelectorUIElement = (Infragistics.Win.UltraWinGrid.RowSelectorUIElement)parent;
objRow = (Infragistics.Win.UltraWinGrid.UltraGridRow)objRowSelectorUIElement.GetContext(typeof(Infragistics.Win.UltraWinGrid.UltraGridRow));
//Get the Index of the Row, so we can use it as a row number.
RowNumber = objRow.Index;
//Check to see if the TextUIElement is already created. Since
//The RowSelectorsUIElement never has children by default, we
//can just check the count.
if (parent.ChildElements.Count == 0)
{
//Create a new TextUIElement and parent it to the RowSelectorUIElement
objTextUIElement = new Infragistics.Win.TextUIElement(parent, RowNumber.ToString());
parent.ChildElements.Add(objTextUIElement);
}
else
{
//There's already a TextUIElement here, so just set the Text
objTextUIElement = (Infragistics.Win.TextUIElement)parent.ChildElements[0];
objTextUIElement.Text = RowNumber.ToString();
}
//Position the TextUIElement into the RowSelectorUIElement
objTextUIElement.Rect = parent.RectInsideBorders;
//Return True let the grid know we handled this event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return true;
}
//Return false to let the grid know we did not handle the event.
//This doesn't really do anything, since the grid
//does not create any child elements for this object, anyway.
return false;
}
#endregion
}
}
Create a "currently selected item" member in the class where the button and the text edit are declared.
In the button selection event listener assign the event target to this member. Then use it in the text edit event listener.
For example:
// It's a declaration of the member variable
private var m_current_btn:Button = null;
// It's an event listener for your button
private function on_selection_change(event:Event):void
{
m_current_btn = event.target as Button;
// button_x and button_y are two text edits
button_x.text = m_current_button.x.toString();
button_y.text = m_current_button.y.toString();
}
// Event listener to track changes in the coordinate text inputs
private function on_coordinate_textedit_change(event:Event):void
{
if (m_current_btn != null)
{
m_current_btn.x = parseInt(button_x.text);
m_current_btn.y = parseInt(button_y.text);
}
}
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>