Hi,
I'm working on a custom Flex component--a callout bubble with a Spark Label for text--and I'm having problems with its size. On screen, the component is 179 x 138 pixels. However, when I trace() the width/height of the component after it is created, I'm told it is 134 x 108. This is making it impossible to correctly position the callout based on its size!
As you can see, I'm overriding updateDisplayList() and using the Graphics API to draw the callout bubble with a gradient:
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
graphics.clear();
label.move(15, 15);
var matrix:Matrix = new Matrix();
matrix.createGradientBox(unscaledWidth + 30, unscaledHeight + 30, Math.PI / 2, 0, unscaledHeight / 2);
graphics.beginGradientFill(GradientType.LINEAR, [0x000000, 0x000000], [0.7, 0.7], [0, 255], matrix, SpreadMethod.PAD, InterpolationMethod.RGB);
graphics.drawRoundRect(0, 0, unscaledWidth + 30, unscaledHeight + 30, 20, 20);
var arrowWidth:Number = 15;
var arrowHeight:Number = 36;
var arrowTop:Number = (unscaledHeight + 30 - arrowHeight) / 2;
graphics.moveTo(0, arrowTop);
graphics.lineTo(-arrowWidth, arrowTop + arrowHeight / 2);
graphics.lineTo(0, arrowTop + arrowHeight);
graphics.endFill();
}
And, as you can see, when the text of the callout's label is changed, I'm invalidating properties and size:
public function set text(value:String):void
{
_text = value;
textChanged = true;
label.text = text;
label.addEventListener(FlexEvent.UPDATE_COMPLETE, labelReady);
}
private function labelReady(event:FlexEvent):void
{
label.removeEventListener(FlexEvent.UPDATE_COMPLETE, labelReady);
invalidateProperties();
invalidateSize();
}
override protected function commitProperties():void
{
super.commitProperties();
if (textChanged)
{
textChanged = false;
invalidateDisplayList();
}
}
Any ideas what I'm doing wrong?
I think I've discovered the problem (but no solution yet). It just occurred to me that the difference between what I see on screen (179 x 138) and what trace() is telling me (134 x 108) is precisely 45 x 30. Since I'm drawing the bubble exactly 45 x 30 pixels larger than the label, I did a trace() of only the label's width/height, and sure enough it is 134 x 108. So, for some odd reason, it seems Flex is not considering what I draw with the Graphics API when it calculates the width/height of the entire component. Is this a bug? If not, how can I coax Flex into giving me the width/height of the entire component?
Really? Based on everything I've read so far, the measure() method is where the component's default width/height is defined.
I've determined that in order to read the component's width/height properties, I must do so after it's creationComplete event fires. Even though within the component itself, I can trace the component's measuredHeight & measuredWidth properties and they are valid immediatedly after the component is added to the DisplayList, the parent does not see them. Does that sound right?
My component won't ever change size actually, so should I be setting it's maxWidth & minWidth properties too?
thx
Yes, really.
Measure() should only provide measuredWidth/Height and measuredMinWidth/Height. Later, in updateDisplayList(), each parent calls setActualSize or setLayoutBoundsSize on its children to give them their sizes (so they can apply sizes to their children). Only then is the width/height valid.
Setting width/height any sooner locks in the size of the component prevent resizing if needed to shrink or grow to fit in. It can also cause the parent to have to stop and re-measure which will be a drag on performance.
You only need to set maxWidth/Height if the layout is going to try to stretch them.
-Alex
Hi,
Maybe I mist-stated what I was doing... here is one of my custom components. Does it look correct?
thx
package components
{
import flash.display.Shape;
import flash.geom.ColorTransform;
import flash.text.TextField;
import flash.text.TextFieldAutoSize;
import flash.text.TextFormat;
import flash.text.TextFormatAlign;
import mx.core.UIComponent;
import ui.LayoutConstants;
public class Tab extends UIComponent
{
private var _tabName:String;
private var _tabNameChanged:Boolean;
private var _labelTextfield:TextField;
private var _bkgrndShape:Shape;
private var _bkgrndColor:uint;
private var _bkgrndColorChanged:Boolean;
private var _tabType:String;
private var _textFormat:TextFormat;
public function Tab()
{
super();
_bkgrndColor = LayoutConstants.TAB_ACTIVE_COLOR;
}
override protected function createChildren():void
{
super.createChildren();
if (!_bkgrndShape)
{
_bkgrndShape = new Shape();
_bkgrndShape.graphics.lineStyle(1, _bkgrndColor);
_bkgrndShape.graphics.beginFill(_bkgrndColor);
_bkgrndShape.graphics.moveTo(0, 0);
_bkgrndShape.graphics.lineTo(0, -40);
_bkgrndShape.graphics.lineTo(2, -42);
_bkgrndShape.graphics.lineTo(4, -44);
_bkgrndShape.graphics.lineTo(LayoutConstants.TAB_ WIDTH - 10, -44);
_bkgrndShape.graphics.lineTo(LayoutConstants.TAB_ WIDTH, -34);
_bkgrndShape.graphics.lineTo(LayoutConstants.TAB_ WIDTH, 0);
_bkgrndShape.graphics.lineTo(0, 0);
_bkgrndShape.graphics.endFill();
addChild(_bkgrndShape);
}
if (!_labelTextfield)
{
_textFormat = new TextFormat();
_textFormat.color = "0x000000";
_textFormat.font = LayoutConstants.UI_BOLD_FONT;
_textFormat.bold = true;
_textFormat.size = 12;
_textFormat.align = TextFormatAlign.CENTER;
_labelTextfield = new TextField();
_labelTextfield.selectable = false;
_labelTextfield.mouseEnabled = false;
_labelTextfield.wordWrap = true;
addChild(_labelTextfield);
_labelTextfield.autoSize = TextFieldAutoSize.CENTER;
}
}
override protected function commitProperties():void
{
super.commitProperties();
if (_tabNameChanged)
{
_labelTextfield.text = _tabName;
_labelTextfield.setTextFormat(_textFormat);
_tabNameChanged = false;
}
if (_bkgrndColorChanged)
{
var colorTransform:ColorTransform = new ColorTransform();
colorTransform.color = _bkgrndColor;
_bkgrndShape.transform.colorTransform = colorTransform;
_bkgrndColorChanged = false;
}
}
override protected function measure():void
{
super.measure();
measuredWidth = _bkgrndShape.width;
measuredHeight = _bkgrndShape.height;
measuredMinHeight = _bkgrndShape.height;
measuredMinWidth = _bkgrndShape.width;
}
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
_labelTextfield.x = (unscaledWidth / 2) - (_labelTextfield.width / 2);
_labelTextfield.y = -(unscaledHeight / 2) - (_labelTextfield.height / 2);
}
[Bindable]
public function get tabName():String
{
return _tabName;
}
public function set tabName(value:String):void
{
_tabName = value;
_tabNameChanged = true;
invalidateProperties();
invalidateDisplayList();
}
[Bindable]
public function get tabType():String
{
return _tabType;
}
public function set tabType(value:String):void
{
_tabType = value;
}
public function set bkgrndColor(value:uint):void
{
_bkgrndColor = value;
_bkgrndColorChanged = true;
invalidateProperties();
//invalidateDisplayList();
}
}
}
Unless the child has been given an explicit width and height by having those properties set directly, because parents size and position their children a child’s width/height is not known until the parent component has completed its updateDisplayList call. There is no event for that, but an updateComplete event should follow shortly and can be forced by calling validateNow on the parent or one of its parents that have enough information to determine the width and height.
North America
Europe, Middle East and Africa
Asia Pacific