Passing a state to the child button of a Flex4 ButtonBar - apache-flex

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?

Related

(flex) - effect play on data change - custom itemrenderer

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.

How can I show a line under each row of text in a Spark TextArea

I'm trying to show a horizontal line under each row of text in a Spark TextArea. I want to give the text area the look of legal paper.
Ah actually ran into a similar problem in Flex 3 doing a strikeout for a disabled link-button that was part of our styles. Just checked out the code and looked on the docs for the spark label and found the function I was using from a mx label explicitly says it won't work [from measureText() in spark label]:
Measures the specified text, assuming that it is displayed in a
single-line UITextField (or UIFTETextField) using a UITextFormat
determined by the styles of this UIComponent. Does not work for Spark
components since they don't use UITextField (or UIFTETextField). To
measure text in Spark components, get the measurements of a
spark.components.Label or spark.components.RichText
So I re-figured it out:
<?xml version="1.0" encoding="utf-8"?>
<s:Label xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark">
<fx:Script>
<![CDATA[
override protected function updateDisplayList(unscaledWidth : Number, unscaledHeight : Number) : void
{
super.updateDisplayList(unscaledWidth, unscaledHeight);
drawLines();
}
private function drawLines() : void
{
var totalHeight : Number = 0;
for (var i : int = 0; i < mx_internal::textLines.length; i++)
{
totalHeight = mx_internal::textLines[i].y;
graphics.lineStyle(1, 0x000000);
graphics.moveTo(0, totalHeight);
graphics.lineTo(width, totalHeight);
}
}
]]>
</fx:Script>
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
</s:Label>
Set the textDecoration style for the control.

Displaying black and red ToolTips as speech baloons

I'm porting a card game from pure Flash/AS3 to Flex 4.5:
I'm almost done, but the "speech baloons" marked by the blue color in the screenshot above are missing.
Those "speech baloons" fade in, display red (if they contain hearts or diamonds char) or black text and finally fade out.
I'm trying to implement those as mx.controls.ToolTips and have prepared a simple test case, where 3 users are represented by smiley-buttons and you can push a "Talk"-button to make them talk:
<?xml version="1.0"?>
<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"
width="400" height="300"
initialize="init();">
<fx:Declarations>
<s:Fade id="fadeIn" alphaFrom="0" alphaTo="1" duration="2000"/>
</fx:Declarations>
<fx:Script>
<![CDATA[
import mx.managers.ToolTipManager;
private var i:uint = 0;
private function init():void {
ToolTipManager.enabled = false;
ToolTipManager.showEffect = fadeIn;
}
private function talk():void {
var str:String = 'Me plays 10' + (i % 2 ? '♥' : '♠');
// this does not make the ToolTip appear?
this['user' + (++i % 3)].toolTip = str;
// how to set color according to isRed(str)?
}
private function isRed(str:String):Boolean {
return (str.indexOf('♦') > 0 || str.indexOf('♥') > 0);
}
]]>
</fx:Script>
<s:Button id="user0" horizontalCenter="0" bottom="0" label=":-)" />
<s:Button id="user1" left="0" top="0" label=":-)" />
<s:Button id="user2" right="0" top="0" label=":-)" />
<s:Button right="0" bottom="0" label="Talk!" click="talk()" />
</s:Application>
Can anybody please give me hints?
How to make ToolTips appear at will? (and not just on mouse hover)
How to change their color (I only found how to set it once by CSS)
UPDATE:
I've tried the following
private var tip0:ToolTip;
private var tip1:ToolTip;
private var tip2:ToolTip;
private function talk():void {
var str:String = 'Me plays 10' + (++i % 2 ? '♥' : '♠');
var btn:Button = this['user' + (i % 3)];
var tip:ToolTip = this['tip' + (i % 3)];
tip = ToolTipManager.createToolTip(str, btn.x + 10, btn.y + 10, "errorTipBelow", IUIComponent(btn)) as ToolTip;
}
but this does not work too well - no effects, no disappearing (I guess I have to call destroyToolTip myself). I wonder if ToolTip can be (ab)used for my purpose of representing "speech baloons" in an elegant way at all...
Personally, I have found the tool tip system rather limiting, and any time I want to do something a bit more different, it just seems easier to implement it manually. Generally in this case I would add a PopUpAnchor control to the components that need these overlay displays. Then you have full manual control over what is shown, and exactly how it is shown.
http://blog.flexexamples.com/category/spark/popupanchor-spark/
There are quite a few ways to do this though as well as just building the tooltip component as a subclass of Group, adding it as a child, and keeping track of it.

Spark TextArea or RichText autosize

I have done lots of searching on this subject, but it seems what I am finding is either out of date or just does not seem to work.
With TextFields in the past, you could set the TextField to a certain width, set wordWrap to true and you would end up with a textfield that changed height according to the text you added.
Now I am trying to do this with either the Spark TextArea or RichText.
I tried this HeightInLines = NAN, but that seems to be out of date.
I also tried this routine:
var totalHeight:uint = 10;
this.validateNow();
var noOfLines:int = this.mx_internal::getTextField().numLines;
for (var i:int = 0; i < noOfLines; i++)
{
var textLineHeight:int =
this.mx_internal::getTextField().getLineMetrics(i).height;
totalHeight += textLineHeight;
}
this.height = totalHeight;
But the mx_internal is not in the Spark components.
I am trying to do this with AS3, not MXML. If anyone has any suggestions or links that could help me figure this out using AS3, I'd really appreciate it.
Been struggling with this all afternoon. But it looks like the RichEditableText component will autosize if you set its width and leave its height undefined.
This works fine:
<?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"
xmlns:mx="library://ns.adobe.com/flex/mx" minWidth="955" minHeight="600">
<s:TextArea updateComplete="event.currentTarget.heightInLines = NaN" />
</s:Application>
Found in comments here. You can do the same in ActionScript using the same updateComplete event.
This is how I set the height of a TextArea to fit its content when used inside an ItemRenderer (e.g. for a List component):
private function onUpdateComplete( e: Event ): void
{
// autoresize the text area
if ( theText ) {
var actualNumOfLines: int = theText.textFlow.flowComposer.numLines;
theText.heightInLines = actualNumOfLines;
invalidateSize();
}
}
ItemRenderer must have this property set:
<s:ItemRenderer ... updateComplete="onUpdateComplete(event)>
Maybe the updateComplete event is not the optimal trigger for auto-resize actions but works fine for me.
You can remove scrollers from TextArea's skin and it becomes autoresizable. You can download completed skin here: http://www.yumasoft.com/node/126
Here's a solution for spark text areas (it works as mx text areas do):
var ta_height:int;
for(var i:int=0; i < StyleableTextField(myTextArea.textDisplay).numLines; i++) {
ta_height += StyleableTextField(myTextArea.textDisplay).getLineMetrics(i).height;
}
myTextArea.height = ta_height;
This seems to work for me:
<s:TextArea id="testTextArea"
change="testTextArea_changeHandler(event)"
updateComplete="testTextArea_updateCompleteHandler(event)"/>
<fx:Script>
<![CDATA[
protected function testTextArea_changeHandler(event:TextOperationEvent):void
{
testTextArea.height = RichEditableText(testTextArea.textDisplay).contentHeight + 2;
}
protected function testTextArea_updateCompleteHandler(event:FlexEvent):void
{
testTextArea.height = RichEditableText(testTextArea.textDisplay).contentHeight + 2;
}
]]>
</fx:Script>
Been doing the same head banging over that s:TextArea, and then found out that this gets the job done :
<s:RichEditableText id="txtArea" left="0" right="0" backgroundColor="#F7F2F2"
change="textChanged()" />

Flex/Accordion: Conditionally hide a child

How do I hide a child in a accordion? Using visible doesn't seem to work and enabled isn't what I'm after.
<mx:Accordion>
<mx:VBox width="100%" height="100%" label="Foo" id="viewOverview" visible="false">
...
</mx:VBox>
...
</mx:Accordion>
I think you can't hide it. Strange that the visible property doesn't work... Anyway, I would control the children through code and remove and insert them as needed by the app. Hiding:
function hideFoo():void {
this.theAccordion.removeChild(this.vboxFoo);
}
You'll probably want to keep a reference to the "hidden" child so that you can add it later again.
This isn't an answer, just some curious things I found out while trying to find another solution to this problem:
Accordion headers have a visible property and a setActualSize method. The latter takes a height and width, and setting each to zero...
acc.getHeaderAt(0).setActualSize(0,0);
...accomplishes the same thing as setting visible = false, that is it hides the contents of the header, but does not remove its area from the accordion. I was hoping to trick the accordion into hiding the child, but no such luck...nonetheless, it might be a path to continue to try. If I get more time I will continue to explore but I'm out of bandwidth at the moment...
You can also create a descendant of accordion with methods like showHeader, hideHeader, isHeaderHidden that contains hash table to keep track of hidden elements similar to the one below:
public class AccordionHideHeader extends Accordion
{
private var _hiddenHeader:Dictionary=new Dictionary();
public function AccordionHideHeader()
{
super();
}
public function hideHeader(header:DisplayObject):void
{
if (contains(header))
{
_hiddenHeader[header]=getChildIndex(header);
removeChild(header);
}
}
public function showHeader(header:DisplayObject):void
{
if (!contains(header))
{
addChildAt(header, _hiddenHeader[header]);
delete _hiddenHeader[header]
}
}
public function isHeaderHidden(header:DisplayObject):Boolean
{
for (var key:Object in _hiddenHeader)
{
if (key==header)
return true;
}
return false;
}
}
Sorry I'm not agree with removing child, because you will having problem when adding it back to its position in exact order.
Example: If you have 5 page in accordion, you remove child 1 and 3, now in any condition you want number 3 back to acordion how do you put it back? because the index is not 3 anymore (rember that 1 is removed too).
I found a good solution here. In short you make your own acordion with enalbe and disable ability where enable and disable define on the child container.
here i paste the acordion code:
/**
* http://blog.flexexamples.com/2008/05/30/preventing-users-from-clicking-on-an-accordion-containers-header-in-flex/
*/
package comps {
import mx.containers.accordionClasses.AccordionHeader;
import mx.events.FlexEvent;
public class MyAccHeader extends AccordionHeader {
public function MyAccHeader() {
super();
addEventListener(FlexEvent.INITIALIZE, accordionHeader_initialize);
}
private function accordionHeader_initialize(evt:FlexEvent):void {
enabled = data.enabled;
}
}
}
Maybe my answer not relevant anymore for you, but i hope can help someone else who face the same problem.
You can override Accordion logic and user includeInLayout property to control visibility of children.
This will work if you set all children in MXML.
import flash.events.Event;
import mx.containers.Accordion;
import mx.core.UIComponent;
public class DynamicAccordion extends Accordion
{
public function DynamicAccordion()
{
}
private var allChildern:Array;
override protected function childrenCreated():void
{
allChildern = new Array();
for (var i:int = numChildren - 1; i >= 0 ; i--)
{
var child:UIComponent = getChildAt(i) as UIComponent;
if (child)
{
child.addEventListener("includeInLayoutChanged", childIncludeLayoutChangedHandler);
if (!child.includeInLayout)
{
removeChild(child);
}
allChildern.push(child);
}
}
allChildern = allChildern.reverse();
super.childrenCreated();
}
private function childIncludeLayoutChangedHandler(event:Event):void
{
var child:UIComponent = event.currentTarget as UIComponent;
if (child.includeInLayout)
{
var index:int = allChildern.indexOf(child);
addChildAt(child, index);
}
else
{
removeChild(child);
}
}
}
I think you might have to actually remove the accordion child itself (e.g. using the State removeChild() mechanism). If you need to preserve the object itself, just keep a reference to it in a global variable.
Cheers
Accordion controls always have 1 child open. By opening another child, the current one will close.
If you want to have more than 1 child open at a time or have all children closed, you can use the VStack component available at: http://weblogs.macromedia.com/pent/archives/2007/04/the_stack_compo.html
<mx:Script>
<![CDATA[
private function hideFn():void
{
acc.removeChildAt(0);
}
private function showFn():void
{
acc.addChildAt(helloBox , 0);
}
]]>
</mx:Script>
<mx:VBox>
<mx:Accordion id="acc" width="200" height="200">
<mx:VBox id="helloBox" label="Test">
<mx:Label text="hello"/>
</mx:VBox>
<mx:VBox label="Test2">
<mx:Label text="hello again"/>
</mx:VBox>
</mx:Accordion>
<mx:Button label="hide" click="hideFn()"/>
<mx:Button label="show" click="showFn()"/>
</mx:VBox>
Here my solution :
http://weflex.wordpress.com/2011/01/25/flex-accordion-hideshow-headers/
I copied and modified the code of the Accordion, so if a child has its "includeInLayout" property to false, it won't be displayed.
Try this one
accrod.getHeaderAt(0).enabled=false;
accrod.getHeaderAt(0).visible=false;
Here is the solution, how to collapse the accordion on click of header.
<mx:Script>
<![CDATA[
import mx.events.IndexChangedEvent;
private var isAccordionClosed:Boolean;
private function myAccordion_clickHandler(event:MouseEvent):void
{
trace(event.currentTarget.label);
var selIdx:int = myAccordion.selectedIndex;
isAccordionClosed = (isAccordionClosed) ? false : true;
if (isAccordionClosed)
{
collapseAccordion(selIdx, !isAccordionClosed);
}
else
{
collapseAccordion(selIdx, !isAccordionClosed);
}
}
private function collapseAccordion(idx:int, showHide:Boolean):void
{
switch(idx)
{
case 0:
vb1.scaleX = vb1.scaleY = int(showHide);
break;
case 1:
vb2.scaleX = vb2.scaleY = int(showHide);
break;
case 2:
vb3.scaleX = vb3.scaleY = int(showHide);
break;
case 3:
vb4.scaleX = vb4.scaleY = int(showHide);
break;
case 4:
vb5.scaleX = vb5.scaleY = int(showHide);
break;
}
}
private function myAccordion_changeHandler(event:IndexChangedEvent):void
{
isAccordionClosed = true;
}
]]>
</mx:Script>
<mx:Accordion id="myAccordion" x="200" y="200" click="myAccordion_clickHandler(event)" resizeToContent="true"
width="399" verticalGap="0" change="myAccordion_changeHandler(event)">
<mx:VBox id="vb1" label="Chapter 1">
<mx:Label text="Accordion 1" width="397" textAlign="center" height="38"/>
</mx:VBox>
<mx:VBox id="vb2" label="Chapter 2">
<mx:Label text="Accordion 2" width="397" textAlign="center" height="43"/>
</mx:VBox>
<mx:VBox id="vb3" label="Chapter 3">
<mx:Label text="Accordion 3" width="397" textAlign="center" height="43"/>
</mx:VBox>
<mx:VBox id="vb4" label="Chapter 4">
<mx:Label text="Accordion 4" width="397" textAlign="center" height="43"/>
</mx:VBox>
<mx:VBox id="vb5" label="Chapter 5">
<mx:Label text="Accordion 5" width="397" textAlign="center" height="43"/>
</mx:VBox>
</mx:Accordion>

Resources