I need some code to run in a component after all the sizes of its children are known. The component has an absolute layout. I thought my component's measure() function would get called after all it's children's measure() functions had been called, but it seems like since it has an absolute layout, it never even calls measure.
Anyways, it's a Canvas and my override protected function measure():void never runs. What am I doing wrong?
updateDisplayList() is called in UIComponent's commitProperties. I believe this is called after the child sizes are known, and it is where you are supposed to put your layout code.
measure() is not called when you explicitly set a width and height on your components. If you have an absolute layout on your Canvas, but do not set a width and height, measure() will be called.
HTH;
Amy
You don't need to override anything, simply add a listener to the creationComplete of your component. This event is "Dispatched when the component has finished its construction, property processing, measuring, layout, and drawing."
Example where the component inherits from s:Group
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="400" height="300"
creationComplete="group1_creationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function group1_creationCompleteHandler(event:FlexEvent):void
{
//TODO add code here
}
]]>
</fx:Script>
</s:Group>
Related
We have a list of variable height items that we display in a Spark List control. When the user clicks and drags the vertical scrollbar the list scrolls smoothly. When the up/down arrows are used it moves in small and discrete steps. When the mouse wheel is used the list scrolls in very large discrete steps that is problematic for the user.
We would like to enable smooth scrolling for the mouse wheel. The height of our items vary significantly and it is easy to get lost when you scroll with the moouse due to the discrete scrolling.
Our implementation is fairly simple:
<s:List id="chartList"
dataProvider="{pm.charts}"
itemRenderer="charts.ChartItemRenderer"
horizontalScrollPolicy="off"
verticalScrollPolicy="on"
useVirtualLayout="false"
cachePolicy="auto">
</s:List>
<?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"
xmlns:mx="library://ns.adobe.com/flex/mx"
autoDrawBackground="false"
xmlns:charts="charts.*"
>
<fx:Script>
<![CDATA[
private var _canvas:BitmapData;
public function set canvas(value:BitmapData):void
{
_canvas = value;
}
[Bindable]
public function get canvas():BitmapData
{
return _canvas;
}
public function render(x:int,y:int, data:int):void
{
_canvas.draw(this);
}
]]>
</fx:Script>
<charts:DefaultChartContainer
chart="{data}"
cachePolicy="on"/>
</s:ItemRenderer>
There does not appear to be an out of the box method for implementing smooth scrolling in a Spark List. How would one go about implementing smooth scrolling in a spark list for variable height items?
Edit:
Here is another way to do this:
http://forums.adobe.com/message/3844410#3844410
Ok, so this wont be easy, but it is doable.
1) Create a custom skin for your list
2) In the custom skin, replace the scroller with a custom scroller (MyScroller)
3) Create a new class that extends Scroller, called MyScroller
4) Adobe in their infinite wisdom made skin_mouseWheelHandler private - which they seem to do all over the place, so you would have to override something higher up, maybe attachSkin and detachSkin. Or you could try adding your own skin_mouseWheelHandler with a higher priority in attachSkin and prevent default on it so the default does not get called.
5) Replicate the code in skin_mouseWheelHandler, and modify it to suit your requirements.
Like #Sunil_D. suggested, listening the mousewheel event and manually adjusting the HorizontalScrollPosition is an easy way to handle this. Add the EventListener for your component
chartList.addEventListener(MouseEvent.MOUSE_WHEEL, chartList_mouseWheelHandler);
and increase/decrease the horizontalScrollPosition with, for example, multiples of event.delta
protected function chartList_mouseWheelHandler(event:MouseEvent):void
{
chartList.verticalScrollPosition -= (event.delta * SOME_CORRECT_VALUE);
}
Finding the proper SOME_CORRECT_VALUE might need some experimenting, but it shouldn't be to hard to find.
I have a collection of UIMovieClip components which reside in an s:HGroup tag. In ActionScript code I am modifying the width of a child clip in one of the UIMovieClips but these changes are not reflected by the s:HGroup.
<s:HGroup id="_buttonGroup">
<uiassets:NavigationTabButtonSWC id="_lobby" />
<uiassets:NavigationTabButtonSWC id="_achievements" />
</s:HGroup>
<fx:Script>
<![CDATA[
protected function init() : void
{
// The HGroup does not pickup this change and so my buttons
// are no longer evenly spaced out and overlap!
_lobby.getChildByName("background").width += 200;
}
]]>
</fx:Script>
Thanks!
There's a few reasons for this. Just changing one child's width doesn't mean it'll change the whole UIMovieClip's width, so you should check that first.
Second, Flex has a very specific way of doing things (called the component lifecycle), which the UIMovieClip doesn't implement so you can't manage the width yourself in the 'measure' function. I'm guessing that you just have other children in your movieclip that doesn't let you resize it all. Try changing the width of the MovieClip itself and it should work. If it doesn't, then there's another problem.
The UI in my app uses a lot of buttons that are mostly the same minus the FXG graphics used in their skins. Their skins look something like this
<?xml version="1.0" encoding="utf-8"?>
<s:Skin xmlns:s="library://ns.adobe.com/flex/spark" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:ai="http://ns.adobe.com/ai/2009" xmlns:d="http://ns.adobe.com/fxg/2008/dt" xmlns:flm="http://ns.adobe.com/flame/2008" xmlns:graphics="assets.graphics.*" xmlns:ATE="http://ns.adobe.com/ate/2009">
<fx:Metadata>[HostComponent("spark.components.Button")]</fx:Metadata>
<s:states>
<s:State name="up"/>
<s:State name="over"/>
<s:State name="down"/>
<s:State name="disabled"/>
</s:states>
<graphics:RectangleGraphic bottom="0"/>
<graphics:ButtonTextGraphic alpha.disabled="0.45" x="22" y="6"/>
<graphics:ButtonSymbolGraphic alpha.disabled="0.45" x="4" y="4">
<graphics:filters>
<s:GlowFilter includeIn="over, down" blurX="3" blurY="3" inner="false" color="#3F5F93" strength="2" alpha="1.0" quality="2" knockout="false"/>
<s:GlowFilter includeIn="down" blurX="3" blurY="3" inner="false" color="0x5380d0" strength="2" alpha="1.0" quality="2" knockout="false"/>
</graphics:filters>
</graphics:ButtonSymbolGraphic>
<graphics:SmallButtonLineSeparatorGraphic bottom="-0.5"/>
</s:Skin>
where RectangleGraphic and SmallButtonLineSeparatorGraphic are FXGs that are used across all buttons, and ButtonTextGraphic and ButtonSymbolGraphic are FXGs that are specific to each button.
I would love to have a single template skin that is fed the two FXGs specific to each button, but I'm not sure how to best do that.
You'll have to do some ActionScript magic to make it work. Probably Leave RectangleGraphic and SmallButtonLineSeparatorGraphic as they are in MXML. Create variables for ButtonTextGraphic and ButtonSymbolGraphic. I'll just demonstrate using ButtonTextGraphic a sample. Something like this:
public var buttonTextGraphicClass : Class;
Also have variables for the class instance:
public var buttonTextGraphic : Class;
In the constructor, you can set the class values. Or if you're stuck w/ MXML, then use a preinitialize event handler:
buttonTextGraphicClass = ButtonTextGraphic;
in createChildren create the instances and add them:
protected function override createChildren():void{
super.createChildren();
buttonTextGraphic = new buttonTextGraphicClass()
// set other properties
addElement(buttonTextGraphicClass);
}
Finally, in updateDisplayList() size and position them elements, something like this:
buttonTextGraphic.x = 22
buttonTextGraphic.y = 6
buttonTextGraphic.width = unscaledWidth // or some other value
buttonTextGraphic.height = unscaledHeight // or some value
This way you can use the same exactly skin; just replacing the class instances for your changing FXG assets at runtime.
I suggest reading up on the Flex Component LifeCycle. If you have Z-Order issues; you may have to switch to creating all the skin's children in ActionScript; or possibly use the "addElementAt" method.
To accommodate for different states in the ActionScript code, you'll have to override the set currentState method to manually make our state changes, such as changing your glow or changing the alpha.
Pretty much everything I describe here is the approach to creating ActionScript skins. You may benefit from looking at some of the existing Flex 4.5 Mobile Skins. I'd start with the Mobile ButtonSkin. I think the border is an FXG Asset; which is implemented using a similar approach to what I describe here.
I have a UIComponent (tried it also with Canvas) and some icons in it (sub views). On the UIComponent I defined some extra ContextMenuItems.
Now when I'm testing it, the context menu appears only on the subviews (icons) with a right-click.
I've checked the documentation but found nothing about required properties for using context menus.
Do you have any ideas why it's only on subviews?
This is probably happening because your UIComponent's (or Canvas') graphics are clean/empty. If the component doesn't have any "content" it will act as transparent, which means the click event will not be caught.
If you are using a Canvas there's a simple way to check this out, try to add some background color and it should work:
<mx:Canvas backgroundColor="#FFFFFF" backgroundAlpha="0.001"/>
I think this is what you're looking for:
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns:s="library://ns.adobe.com/flex/spark"
creationComplete="onCreationComplete()">
<fx:Script>
<![CDATA[
private function onCreationComplete():void
{
var menu:ContextMenu = new ContextMenu();
menu.customItems.push(new ContextMenuItem('weee'));
menu.customItems.push(new ContextMenuItem('weee2'));
menu.hideBuiltInItems();
canvas.contextMenu = menu;
}
]]>
</fx:Script>
<mx:Canvas id="canvas" backgroundColor="#000000" height="50%" width="50%" >
<s:Button label="blarg" />
</mx:Canvas>
</s:Application>
Notice how I'm creating a menu, then adding items, which then replaces the contextMenu property. This should work on any object that extends InteractiveObject.
What the problem was is the mouseEnabled="{false}" property in one of the parent-parent containers. I removed it and it works now.
If you know Flex, you probably know what the property "includeInLayout" does. If not, this property make the parent of your component disregard the bounds (like width and height) of your component in render their own bounds.
Description in reference below:
Specifies whether this component is
included in the layout of the parent
container. If true, the object is
included in its parent container's
layout and is sized and positioned by
its parent container as per its layout
rules. If false, the object size and
position are not affected by its
parent container's layout.
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/mx/core/UIComponent.html#includeInLayout
In Flex, for example:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="application1_creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function application1_creationCompleteHandler( event:FlexEvent ):void
{
trace( container.width, container.height ); // output: 200 200
}
]]>
</mx:Script>
<mx:Canvas id="container">
<mx:Button label="Test"
width="100"
height="100" />
<mx:Button label="Test2"
width="200"
height="200" />
</mx:Canvas>
</mx:Application>
Now if I set includeInLayout="false" in second button:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="application1_creationCompleteHandler(event)">
<mx:Script>
<![CDATA[
import mx.events.FlexEvent;
protected function application1_creationCompleteHandler( event:FlexEvent ):void
{
trace( container.width, container.height ); // output: 100 100
}
]]>
</mx:Script>
<mx:Canvas id="container">
<mx:Button label="Test"
width="100"
height="100" />
<mx:Button label="Test2"
width="200"
height="200"
includeInLayout="false" />
</mx:Canvas>
</mx:Application>
I know of all framework architecture involved to implement this property and know than this property is a property from Flex Framework. What I wanna is this behavior in pure actionscript. For example:
import flash.display.Shape;
var myBox:Shape = new Shape();
myBox.graphics.beginFill(0xFF0000);
myBox.graphics.drawRect(0, 0, 100, 100);
myBox.graphics.endFill();
addChild(myBox);
trace(width, height); // output: 100 100
var myAnotherBox:Shape = new Shape();
myAnotherBox.graphics.beginFill(0xFF00FF, .5);
myAnotherBox.graphics.drawRect(0, 0, 200, 200);
myAnotherBox.graphics.endFill();
addChild(myAnotherBox);
trace(width, height); // output: 200 200
Is there some equivalent implementation in pure Actionscript to reproduce this behavior on "myAnotherBox"?
I already tried:
Change transform matrix;
Change transform pixelBounds;
Change scrollRect;
Apply masks;
And no successful.
Cheers...
You are trying to compare to different things.
In the first example, the width and height are from the UIComponent class, which actually represents a "measured" value, as provided by the Flex Framework.
In the second case, the width and height are the actual size of the rendered shape, as provided by the Flash Player.
If you open the UIComponent's implementation, you will notice that it is actually hiding the original meaning of width and height into $width and $height and provide their own implementation to it.
So to answer your question, you cannot achieve what you want working directly with Shapes (pure Flash components)
Every time you draw something in a graphics context, the width and height will update accordingly.
You options:
draw the first one in one graphics, and the second one in another graphics and measure only the first shape
compute the width and height yourself. This is what actually the containers do, and just skip the includeInLayout=false child components.
If you look up in the UIComponent source code, you'll find, that flag includeInLayout is used in invalidation mechanism (exatcly in size invalidation). If includeInLayout of the component is false, then its parent's size and its size is not recalculated.
Invalidation is native flex framework mechanism, which does not exist in pure AS projects.
You need to override width and height in your container:
override public function get width() : Number {
// place your code here
return 100;
}
override public function get height() : Number {
// place your code here
return 100;
}