I've got datagrid with custom itemrenderer. I want to play animatecolor effect for a cell in which data was changed. I don't want to play that effect for all cells.
Here is code of itemrenderer:
<?xml version="1.0" encoding="utf-8"?>
<s:GridItemRenderer xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" clipAndEnableScrolling="true">
<fx:Declarations>
<s:Sequence id = "updateEffect">
<s:AnimateColor colorFrom ="0xffffff"
colorTo ="0xb9d30d"
target = "{lblData}"
duration ="5000"/>
<s:AnimateColor colorFrom ="0xb9d30d"
colorTo ="0xffffff"
target = "{lblData}"
duration ="5000"/>
</s:Sequence>
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.events.FlexEvent;
override public function prepare(hasBeenRecycled:Boolean):void {
if(data)
{
lblData.text = data[column.dataField];
if(data.selected == true)
{
lblData.setStyle("color","#61afda");
lblData.setStyle("textAlign","center");
lblData.setStyle("fontWeight","bold");
lblData.setStyle("fontSize","19");
lblData.setStyle("paddingLeft","10");
lblData.setStyle("paddingRight","10");
lblData.setStyle("paddingBottom","0");
lblData.setStyle("paddingTop","2");
containter.setStyle("backgroundAlpha","1.0");
containter.setStyle("backgroundColor","0x2f3437");
}
else
{
lblData.setStyle("color","#d4d4d4");
lblData.setStyle("textAlign","center");
lblData.setStyle("fontWeight","bold");
lblData.setStyle("fontSize","12");
lblData.setStyle("paddingLeft","10");
lblData.setStyle("paddingRight","10");
lblData.setStyle("paddingBottom","5");
lblData.setStyle("paddingTop","4");
containter.setStyle("backgroundAlpha","0.0");
updateEffect.play();
}
}
}
]]>
</fx:Script>
<s:SkinnableContainer
id ="containter"
width ="100%"
height ="100%">
<s:HGroup
width ="100%"
height ="100%">
<s:Label
id ="lblData"
width ="100%"
height ="100%"
maxDisplayedLines ="1"
styleName ="FPlayGcItemRenderStyle"/>
</s:HGroup>
</s:SkinnableContainer>
Any suggestions?
I don't know if its the best way but I generally add a listener to my data when I need to update properties dynamically in my item renderers.
//This is inside an item renderer
private var _myClass:FunkyClass;
override protected function set data( value:Object ):void
{
super.data = value;
if( value != null )
{
if( _myClass != null )
{
_myClass.removeEventListener(
'MyFunkyClass.DataChange' , updateSomething);
}
_myClass = value as FunkyClass;
_myClass.addEventListener(
'MyFunkyClass.DataChange' , updateSomething);
}
}
private function updateSomething( event:Event ):void
{
//change colors, visibility and so forth
}
Of course your data class has to extend EventDispatcher. I would also be interested in seeing how other folks in the community have solved this problem.
Related
My 1st question here...
Spark component TextArea does have a gestureZoom event property, but it seems that it has no functionality?
I would like to implement a simple zoom feature in my TextArea, which simply increases or decreases fontSize property, making text appear larger or smaller.
After implementing all the necessary code (and it works if instead of TextArea I use Image), pinch&zoom does not trigger the gestureZoom event on TextArea object.
Any suggestions? (I don't insist on using pinch&zoom, it just seems appropriate...)
Here 's the code:
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
applicationComplete="application1_applicationCompleteHandler(event)">
<fx:Script>
<![CDATA[
import mx.binding.utils.BindingUtils;
import mx.events.FlexEvent;
[Bindable]
public var currFontSize:int = 24;
protected function application1_applicationCompleteHandler(event:FlexEvent):void {
Multitouch.inputMode = MultitouchInputMode.GESTURE;
if(Multitouch.supportsGestureEvents){
txtbox.addEventListener(TransformGestureEvent.GESTURE_ZOOM, onGestureZoom);
} else {
status.text="gestures not supported";
}
}
// THIS NEVER GETS CALLED?
private function onGestureZoom( event : TransformGestureEvent ) : void {
info.text = "event = " + event.type + "\n" +
"scaleX = " + event.scaleX + "\n" +
"scaleY = " + event.scaleY;
// Zomm in/out simply by increasing/decreasing font size
if (event.scaleX <1 || event.scaleY <1){
if (currFontSize > 12) currFontSize -=2;
}
else{
if (currFontSize < 64) currFontSize +=2;
}
}
protected function button1_clickHandler(event:MouseEvent):void {
info.text = "";
currFontSize = 24;
}
]]>
</fx:Script>
<s:Label id="status" top="10" width="100%" text="Transform Gestures on TextArea"
textAlign="center"/>
<s:HGroup left="12" right="12" top="40">
<s:TextArea id="info" width="100%" height="117" editable="false"/>
<s:Button label="Reset" click="button1_clickHandler(event)"/>
</s:HGroup>
<s:TextArea id="txtbox" left="12" right="12" bottom="12" height="400"
fontSize="{currFontSize}"
gestureZoom="onGestureZoom(event)"
text="Here is some sample text I want enlarged or shrunk."/>
</s:Application>
If the TextArea doesn't need to be editable.. see if you can use a Label. that should work with the pinch and zoom.
I have a Spark ButtonBar that has a custom skin, which defines a custom skin for the "middleButton" requirement. My CustomButtonBarSkin has a custom state, minimized, which I want to pass into my middleButton skin so it can modify its design.
Is it possible to do this? I can see that my button skin could use parentDocument.currentState to get the minimized state, but that's really ugly. Any way to pass a skin from the bar to the child button(s)?
I think you should extend default ButtonBar. Something like this:
package
{
import mx.core.IFactory;
import spark.components.ButtonBar;
[SkinState("minimized")]
[SkinState("minimizedDisabled")]
public class MinimizableButtonBar extends ButtonBar
{
public function MinimizableButtonBar()
{
super();
itemRendererFunction = defaultButtonBarItemRendererFunction;
}
[SkinPart(required="true", type="mx.core.IVisualElement")]
public var middleButtonMinimized:IFactory;
private var _minimized:Boolean;
[Bindable]
public function get minimized():Boolean
{
return _minimized;
}
public function set minimized(value:Boolean):void
{
if (_minimized == value)
return;
_minimized = value;
invalidateSkinState();
itemRendererFunction = defaultButtonBarItemRendererFunction;
}
override protected function getCurrentSkinState():String
{
if (_minimized)
return enabled ? "minimized" : "minimizedDisabled";
return super.getCurrentSkinState();
}
private function defaultButtonBarItemRendererFunction(data:Object):IFactory
{
var i:int = dataProvider.getItemIndex(data);
if (i == 0)
return firstButton ? firstButton : (_minimized ? middleButtonMinimized : middleButton);
var n:int = dataProvider.length - 1;
if (i == n)
return lastButton ? lastButton : (_minimized ? middleButtonMinimized : middleButton);
return (_minimized ? middleButtonMinimized : middleButton);
}
}
}
So using this code you can declare your custom skin with the following way:
<?xml version="1.0" encoding="utf-8"?>
<s:Skin alpha.disabledGroup="0.5" xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Metadata>[HostComponent("MinimizableButtonBar")]</fx:Metadata>
<s:states>
<s:State name="normal" />
<s:State name="disabled" stateGroups="disabledGroup" />
<s:State name="minimized" stateGroups="minimizedGroup" />
<s:State name="minimizedDisabled" stateGroups="disabledGroup,minimizedGroup" />
</s:states>
<fx:Declarations>
<fx:Component id="firstButton">
<s:ButtonBarButton skinClass="spark.skins.spark.ButtonBarFirstButtonSkin" />
</fx:Component>
<fx:Component id="middleButton">
<s:ButtonBarButton skinClass="spark.skins.spark.ButtonBarMiddleButtonSkin" />
</fx:Component>
<fx:Component id="middleButtonMinimized">
<s:ButtonBarButton skinClass="MinimazedButtonBarMiddleButtonSkin" />
</fx:Component>
<fx:Component id="lastButton">
<s:ButtonBarButton skinClass="spark.skins.spark.ButtonBarLastButtonSkin" />
</fx:Component>
</fx:Declarations>
<s:DataGroup height="100%" id="dataGroup" width="100%">
<s:layout>
<s:ButtonBarHorizontalLayout gap="-1" />
</s:layout>
<s:layout.minimizedGroup>
<s:VerticalLayout />
</s:layout.minimizedGroup>
</s:DataGroup>
</s:Skin>
Hope this solves your problem.
And if your minimized state is only about changing middle button skin you can remove all states related code both from custom component and from skin.
I was working with skinning the button bar recently and wanted to expand on/remove some of the default behavior. Rather than extend & overwrite or copy/paste the ButtonBar code I just rolled my own minimalistic component:.
public class HButtonBarGroup extends HGroup {
public function HButtonBarGroup() {
addEventListener(ElementExistenceEvent.ELEMENT_ADD, refreshSkins);
super();
gap = -1;
}
private function refreshSkins(event : * = null) : void {
var buttonCount : int = numElements;
for (var i : int = 0; i < buttonCount; i++) {
var button : Button = getElementAt(i) as Button;
var skinClass : Class
if ((buttonCount == 0) || (buttonCount > 2 && (i != 0 && i != buttonCount)))
skinClass = GreyButtonBarMiddleButtonSkin;
else if (i == 0)
skinClass = GreyButtonBarFirstButtonSkin;
else
skinClass = GreyButtonBarLastButtonSkin;
Button(getElementAt(i)).setStyle("skinClass", skinClass);
}
}
}
This would give you the ability to do most anything you want without having to tiptoe around ButtonBar, ButtonBarBase, and ButtonBarSkin - all unnecessary unless you want togglebutton/selectedIndex. IMO it is a pain to create buttons based on a dataProvider instead of just declaring buttons in MXML and assigning handlers and other properties there.
I recently needed to change skin on a component based on its parents state. I used the same solution I would have used in HTML, using CSS. In your case, something like:
s|ButtonBar:minimized s|ButtonBarButton {
skinClass: ClassReference("CustomButtonBarSkin");
}
s|ButtonBarButton {
skinClass: ClassReference("spark.skins.spark.ButtonBarMiddleButtonSkin");
}
:minimized is the Pseudo Selector (for States).
Unfortunately, this didn't seem to get picked up by child (bug?) unless I changed styleName on parent element on state change:
<s:Skin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/mx"
styleName.normal="foo" styleName.minimized="foo"
>
Maybe there is some invalidate-method I should have called on parents state change instead to make child pick up the change, but merely change the styleName to something bogus did the trick.
This is maybe not a widely used technique in Flex due to the fact that Flex 3 only supported basic CSS selectors.
Maybe I'm not getting what you're trying to do exactly, but it seems fairly obvious and easy to me. Just have your custome button bar skin set the state of your middle button when the minimized state is active:
<s:Skin>
<s:states>
<s:State name="minimized" />
</s:states>
<s:ButtonBarButton currentState.minimized="someState" />
</s:Skin>
Get it?
I have prepared a simple test case for a PopUpButton opening a TileList with black and red entries and it mostly works, but has 2 annoyances.
I've searched a lot, tried several variants (added [Bindable] members in my renderer; added color member to the bids array; created my public override set data() method; ...) and has been getting some answers too, but they are way too general.
I would appreciate if someone can suggest code to fix the 2 issues in my code:
1) Scrolling "tl2" right-left doesn't work well: the entries are displayed in a mix of red and black. I know the TileList reuses itemRenderer, but how do I fix the problem?
2) In debug-mode I get numerous warnings:
warning: unable to bind to property 'label' on class 'Object' (class is not an IEventDispatcher)
Thank you,
Alex
MyRenderer.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
width="100%" height="100%">
<mx:Script>
<![CDATA[
public static function findColor(str:String):uint {
return (str.indexOf('♥') != -1 ||
str.indexOf('♦') != -1) ? 0xFF0000 : 0x000000;
}
]]>
</mx:Script>
<mx:Label truncateToFit="true" width="60"
text="{data.label}" color="{findColor(data.label)}"/>
</mx:Canvas>
MyTest.mxml:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
creationPolicy="all" applicationComplete="init(event);">
<mx:Style>
#font-face {
src:url("C:\\WINDOWS\\Fonts\\arial.ttf");
fontFamily: myFont;
unicodeRange:
U+0020-U+0040, /* Punctuation, Numbers */
U+0041-U+005A, /* Upper-Case A-Z */
U+005B-U+0060, /* Punctuation and Symbols */
U+0061-U+007A, /* Lower-Case a-z */
U+007B-U+007E, /* Punctuation and Symbols */
U+0410-U+0451, /* cyrillic */
U+2660-U+266B; /* card suits */
}
List, CheckBox, Label, Button, PopUpButton, TileList {
fontFamily: myFont;
fontSize: 24;
}
</mx:Style>
<mx:Script>
<![CDATA[
import mx.controls.*;
import mx.events.*;
[Bindable]
private var bids:Array;
private var tl:TileList;
private function init(event:FlexEvent):void {
bids = createBids();
pub.popUp = createList(bids);
}
private function createBids():Array {
var arr:Array = [{label: 'Pass'}];
for (var i:uint = 6; i <= 10; i++)
for (var j:uint = 0; j < 5; j++)
arr.unshift({label: i+'♠♣♦♥ '.charAt(j%5)});
return arr;
}
private function createList(arr:Array):TileList {
tl = new TileList();
tl.maxColumns = 5;
tl.width = 350;
tl.height = 250;
tl.dataProvider = arr;
tl.itemRenderer = new ClassFactory(MyRenderer);
tl.addEventListener('itemClick', itemClickHandler);
if (arr.length > 0) {
tl.selectedIndex = arr.length - 1;
pub.label = arr[tl.selectedIndex].label;
}
return tl;
}
private function itemClickHandler(event:ListEvent):void {
var index:uint = tl.columnCount * event.rowIndex + event.columnIndex;
var label:String = bids[index].label;
pub.label = label;
pub.setStyle('color', MyRenderer.findColor(label));
pub.close();
tl.selectedIndex = index;
}
]]>
</mx:Script>
<mx:Panel title="TileList scrolling problem" height="100%" width="100%"
paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10">
<mx:Label width="100%" color="blue" text="Select your bid:"/>
<mx:TileList id="tl2" height="200" width="200"
maxColumns="5" rowHeight="30" columnWidth="60"
dataProvider="{bids}" itemRenderer="MyRenderer"/>
</mx:Panel>
<mx:ApplicationControlBar width="100%">
<mx:Spacer width="100%"/>
<mx:CheckBox id="auto" label="Auto:"/>
<mx:Button id="left" label="<<"/>
<mx:PopUpButton id="pub" width="90"/>
<mx:Button id="right" label=">>"/>
</mx:ApplicationControlBar>
</mx:Application>
Update:
Thank you Wade, the warning is gone now (I guess it was not ok to use {data.label} in my label), but the "tl2" still has scrolling issues.
New MyRenderer.mxml (still has scrolling issues):
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
width="100%" height="100%">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void {
super.data = value;
var str:String = String(value.label);
myLabel.text = str;
myLabel.setStyle('color', findColor(str));
}
public static function findColor(str:String):uint {
return (str.indexOf('♥') != -1 ||
str.indexOf('♦') != -1) ? 0xFF0000 : 0x000000;
}
]]>
</mx:Script>
<mx:Label id="myLabel" truncateToFit="true" width="60"/>
</mx:Canvas>
You can take care of both of your issues by overriding the set data method on your item renderer:
<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml"
verticalScrollPolicy="off" horizontalScrollPolicy="off"
width="100%" height="100%">
<mx:Script>
<![CDATA[
override public function set data(value:Object):void {
super.data = value;
var str:String = value.label;
this.myLabel.text = str;
this.myLabel.setStyle("color", (str.indexOf('♥') != -1 ||
str.indexOf('♦') != -1) ? 0xFF0000 : 0x000000);
}
]]>
</mx:Script>
<mx:Label id="myLabel" truncateToFit="true" width="60"/>
</mx:Canvas>
Since the renderers are re-used, the best way to ensure they are correctly updated is to use the set data method since it always gets called when a renderer gets re-used. This also gets rid of your binding warning since you are no longer binding to data.label. Note: I haven't tested this code, it may need some tweaking :) Hope that helps.
EDIT: Your "tl2" issue looks like it's caused by horizontally scrolling your tile list, whereas the TileList appears to be optimized for vertical scrolling. Since your data set is finite and relatively small, I would make the tile list full size to show all of the elements (eliminating item renderer re-use) and wrap it in a canvas set to the desired dimensions and let the canvas handle the scrolling. Probably not the answer you are looking for, sorry.
First of all I know there is a spark VolumeBar component but, for design requirements I can't use it..
I'm trying to create a custom component but heights are not responding as should
[Update]
This is were I call the class
<components:VolumeSlider steps="4" height="100" />
The problem is that the volume slider is adapting perfectly, but My custom items component doesn't.
<s:HGroup width="100%" height="100%" maxHeight="{height}" >
<s:VGroup width="100%" height="100%" paddingBottom="20" paddingTop="20">
<s:VSlider id="slider" width="100%" height="100%" maximum="{_steps-1}" />
</s:VGroup>
<s:VGroup id="items" width="100%" height="100%" />
</s:HGroup>
<fx:Script>
<![CDATA[
import mx.events.FlexEvent;
[Bindable]
private var _steps:uint = 10;
public function set steps( value:uint ):void
{
_steps = value;
if ( items != null && items.numChildren != 0 )
{
items.removeAllElements();
}
create();
}
private function create():void
{
for ( var i:uint = 0; i < _steps; ++i )
{
var item:VolumeSliderItem = new VolumeSliderItem();
item.percentHeight = item.percentWidth = 100;
if ( items != null )items.addElement(item );
}
}
]]>
</fx:Script>
where VolumeSliderItem is a spark button
I don't see any call to create(). I added 'creationComplete="create()"' on the Application tag, and then it created 10 sliders to the VGroup with id 'items'. Is that what you're looking for?
I have a custom component that is basically a VBox with a Label and a TextField.
<mx:VBox width="50%">
<mx:Label width="100%"/>
<mx:TextField width="100%"/>
</mx:VBox>
What I want, basically, is to have two of these VBoxes layed out on a HBox and each would take exactly 50% - their children Label and TextField should just obey that.
If I set both Label and TextField's width to 100%, and the Label text doesn't fit, the default behaviour is to expands the VBox width - I don't want that to happen. The TextField should always take 100% of the width, and I'd want the Label to be explicitly set to the width of the TextField, so the text would be truncated and not expand the VBox width.
Is there a way to tell the Label to just obey the VBox (or TextField) width and not be included in the measurement of the VBox width?
Not sure if I was clear. :)
Thanks in advance.
it wasn't that easy as I thought. At the beginning I wanted to suggest maxWidth but it doesn't work correctly with label. However I just tested this:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
import mx.controls.TextInput;
import mx.controls.Label;
private function onTextChanged(event:Event):void {
var currentText:String = TextInput(event.target).text;
var shortened:Boolean = false;
// adding 30 to leave some space for ...
while ( lbl.measureText(currentText).width > (lbl.width-30) ) {
currentText = currentText.substr(0,currentText.length-1);
shortened = true;
}
lbl.text = currentText + ((shortened) ? "..." : "" );
}
]]>
</mx:Script>
<mx:VBox width="50%">
<mx:Label id="lbl" width="100%" />
<mx:TextInput width="100%" change="onTextChanged(event);" />
</mx:VBox>
</mx:WindowedApplication>
It probably isn't written in a way (just attributed) you expected but it does what you need.
If this isn't a solution you could think of extending the Label in the following manner.
Crete custom Label:
radekg/MyLabel.as
package radekg {
import mx.controls.Label;
public class MyLabel extends Label {
public function MyLabel() {
super();
}
private var _myText:String;
override public function get text():String {
return _myText;
}
override public function set text(value:String):void {
if ( value != null && initialized ) {
// store the copy so the getter returns original text
_myText = value;
// shorten:
var shortened:Boolean = false;
while ( measureText(value).width > (width-30) ) {
value = value.substr(0,value.length-1);
shortened = true;
}
super.text = value + ((shortened) ? "..." : "");
}
}
}
}
And use it like that:
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:controls="radekg.*">
<mx:VBox width="50%">
<controls:MyLabel width="100%" id="lbl" text="{ti.text}" />
<mx:TextInput width="100%" id="ti" />
<mx:Button label="Display label text" click="mx.controls.Alert.show(lbl.text)" />
</mx:VBox>
</mx:WindowedApplication>
This can be used simply with binding. Once you type very long text into the text input and the Label displays ... click on the button. You'll notice that text() getter returns original text.
Hope this helps.