tutorials flex tabcontrol
The TabControl component
Article written 15/02/2007 11:04.
By iteratif ( Bugalotto Olivier ).
Personalising the TabBar component is not as simple as using the ItemRenderer with the List components and the DataGrid (to cite just two examples). The idea is to add a close button to the TabBar component for a tab.
The TabBar component uses the Tab class to create a tab button:
... public function TabBar() { super(); ... navItemFactory = new ClassFactory(Tab); ... } ...
We can therefore extend the TabBar class and modify the ’navItemFactory’ property but for this you must use the ‘mx_internal’ namespace in the following way:
package net.iteratif.controls { import mx.core.mx_internal; import mx.controls.Button; import mx.controls.TabBar; use namespace mx_internal; public class TabControl extends TabBar { public function TabControl() { super(); navItemFactory = new ClassFactory(Button); } } }
In this case we have no problem. But once we do it with something other than a ‘Button’ type we get an error telling us that the personalised type must be a ‘Button’ type.
We will therefore have to specialise the ‘Tab’ type to add a close button to it to understand how the ‘Button’ component works.
In the ‘Button’ constructor, the ‘mouseChildren’ property is defined as ‘false’ which means that the children cannot emit events. So if we add a button we can’t emit the click event, we therefore need to sort this out. To start, let’s change the Tab class into a special ‘TabCloser’ class and then initialize the ‘mouseChildren’ property:
package net.iteratif.controls { import mx.controls.tabBarClasses.Tab; public class TabCloser extends Tab { super(); mouseChildren = true; } }
Note: the ‘Tab’ class is not visible because of the \[ExcludeClass\] metadata so don’t panic if you don’t see it in the code. To add the close button, we will define the protected method ‘createChildren’:
package net.iteratif.controls { import mx.controls.tabBarClasses.Tab; public class TabCloser extends Tab { private var closeButton:Button; public function TabCloser() { super(); mouseChildren = true; } protected override function createChildren():void { super.createChildren(); // We will verify if the close button was created if(!closeButton) { closeButton = new Button(); closeButton.name = "closeButton"; closeButton.label = "x"; closeButton.focusEnabled = false; // We make it invisible closeButton.visible = false; closeButton.width = 16; closeButton.height = 16; addChild(closeButton); } } } }
We can update the ‘TabControl’ class:
package net.iteratif.controls { import mx.core.mx_internal; import mx.controls.Button; import mx.controls.TabBar; use namespace mx_internal; public class TabControl extends TabBar { public function TabControl() { super(); navItemFactory = new ClassFactory(TabCloser); } } }
Layout
After trying it out we notice that the button is at the top left but it is overshadowed by the state of the button, we therefore need to sort things out so that the close button comes into the foreground and is then placed in the top right corner.
We can redefine the ‘layoutContents’ placement method but to do this we need to use the ‘mx_internal’ space:
package net.iteratif.controls { import mx.controls.tabBarClasses.Tab; import mx.core.mx_internal; use namespace mx_internal; public class TabCloser extends Tab { private var closeButton:Button; public function TabCloser() { super(); mouseChildren = true; } protected override function createChildren():void { super.createChildren(); if(!closeButton) { closeButton = new Button(); closeButton.name = "closeButton"; closeButton.label = "x"; closeButton.focusEnabled = false; // We make it invisible closeButton.visible = false; closeButton.width = 16; closeButton.height = 16; addChild(closeButton); } } mx_internal override function layoutContents(unscaledWidth:Number, unscaledHeight:Number, offset:Boolean):void { super.layoutContents(unscaledWidth,unscaledHeight,offset); // We place the close button at the top right closeButton.x = unscaledWidth - close.width - 5; closeButton.y = 2; // And we then put it in the foreground setChildIndex(closeButton, numChildren - 1); } } }
It’s not just by chance that I have put the code in the ‘layoutContents’ method as it’s there that the icon, label and the appearance of the button are:
mx_internal override function layoutContents(unscaledWidth:Number,
unscaledHeight:Number,
offset:Boolean):void {
super.layoutContents(unscaledWidth,unscaledHeight,offset);
...
if (currentSkin)
setChildIndex(DisplayObject(currentSkin), numChildren - 1);
if (currentIcon)
setChildIndex(DisplayObject(currentIcon), numChildren - 1);
if (textField)
setChildIndex(textField, numChildren - 1);
...
}
Sizing
There is a problem, the close button covers the button label, we therefore need to resize the button with respect to the size of the close button:
... public class TabCloser extends Tab { ... protected override function measure():void { super.measure(); // They give the size of the component // We add the size of the close button measuredMinWidth = measuredWidth += closeButton.width; } ... }
Visibility
There is another problem, the close button is still present and we only want it to appear when the tab is selected, we will do it in a button class method which can make the button visible:
... public class TabCloser extends Tab { ... mx_internal override function viewSkin():void { super.viewSkin(); // We will use the selected property // to make the close button visible close.visible = selected; } ... }
It’s much better now…
The close event
Let’s pass onto the management of the event to allow the tab to close.
package net.iteratif.controls { ... public class TabCloser extends Tab { private var closeButton:Button; ... protected override function createChildren():void { super.createChildren(); if(!closeButton) { closeButton = new Button(); closeButton.addEventListener(MouseEvent.CLICK,closed); ... } } private function closed(e:MouseEvent):void { // We broadcast a personalized event dispatchEvent(new ItemCloseEvent(ItemCloseEvent.ITEM_CLOSE)); // We stop the propagation of the CLICK event // to stop the TabControl component from capturing it e.stopImmediatePropagation(); } ... } }
Management of the 'ITEM_CLOSE' event in ‘TabControl':
package net.iteratif.controls { import flash.display.DisplayObject; import flash.events.Event; import flash.events.MouseEvent; import mx.controls.Button; import mx.controls.TabBar; import mx.core.ClassFactory; import mx.core.IFlexDisplayObject; import mx.core.mx_internal; import net.iteratif.events.ItemCloseEvent; use namespace mx_internal; /** * Broadcasts once the close button has been clicked * * @event Type net.iteratif.events.ItemCloseEvent */ [Event(name="itemClose",type="net.iteratif.events.ItemCloseEvent")] public class TabBar extends mx.controls.TabBar { public function TabBar() { super(); navItemFactory = new ClassFactory(TabCloser); } /** * We redefine the method to allow us to * listen to the event emited by the TabCloser control */ override protected function createNavItem( label:String, icon:Class = null):IFlexDisplayObject { var newButton:Button = Button(super.createNavItem(label,icon)); newButton.addEventListener(ItemCloseEvent.ITEM_CLOSE,closeHandler); return newButton; } /** * Executes when the ITEM_CLOSE event happens */ protected function closeHandler(e:ItemCloseEvent):void { var currentTarget:DisplayObject = DisplayObject(e.currentTarget); currentTarget.removeEventListener(ItemCloseEvent.ITEM_CLOSE,closeHandler); // We recuperate the index corresponding to the tab var index:int = getChildIndex(currentTarget); var event:ItemCloseEvent = new ItemCloseEvent(ItemCloseEvent.ITEM_CLOSE); event.index = index; // We broadcast the event dispatchEvent(event); // and stop the propagation of the event e.stopImmediatePropagation(); } } }
Our component is ready, let’s go on to its use:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:it="net.iteratif.controls.*" layout="vertical"> <mx:VBox width="299" height="191" verticalGap="-2"> <!—We supply the TabControl control with the ViewStack --> <it:TabBar dataProvider="{stack}" itemClose="stack.removeChildAt(event.index)" /> <mx:ViewStack id="stack" borderStyle="solid" width="100%" height="80%"> <mx:Canvas id="search" backgroundColor="#FFFFCC" label="Search" width="100%" height="100%" > <mx:Label text="Search Screen" color="#000000"/> </mx:Canvas> <mx:Canvas id="custInfo" backgroundColor="#CCFFFF" label="Customer Info" width="100%" height="100%"> <mx:Label text="Customer Info" color="#000000"/> </mx:Canvas> </mx:ViewStack> </mx:VBox> </mx:Application>
I will leave you to change the look of the component so that it looks like the 'TabNavigation' component.
Here is the source code for this tutorial: TabControlProject.zip
By ITERATIF - BUGALOTTO Olivier (2007)
Mediabox Training Centre © 2000 - 2008 All rights reserved.
Adobe Authorized Training Centre. State convention under number 25 14 02167 14.
Mediabox : SARL au capital de 62.000€ - Activity number: 25 14 02167 14 - SIRET : 493 716 468 00027
MEDIABOX, 102 Avenue des Champs Elysées, 75008 PARIS - Tel. +33(0)2.31.91.96.89 - Fax. +33(0)2.72.68.56.42


