Forums Développement Multimédia

Les formations Mediabox
Les formations Mediabox



Personnaliser le composant TabBar

Compatible Flex 2. Cliquer pour en savoir plus sur les compatibilités.Compatible ActionScript 3. Cliquer pour en savoir plus sur les compatibilités.Par ITERATIF (Olivier Bugalotto), le 15 février 2007

La personnalisation du composant TabBar n'est pas aussi simple que l'utilisation d'ItemRenderer avec les composants List et DataGrid pour ne citer qu'eux.

Dans ce tutoriel, nous allons ajouter au composant TabBar un bouton de fermeture pour un onglet.

L"extension Adobe Flash Plugin est nécessaire pour afficher ce contenu.

Pour commencer

Le composant TabBar utilise la classe Tab pour créer un bouton d'onglet :

...
public function TabBar() {
        super();
		...
        navItemFactory = new ClassFactory(Tab);
        ...
}
...

Nous pouvons donc etendre la class TabBar et modifier la propriété navItemFactory mais pour cela il faut utiliser le namespace mx_internal, de la manière suivante:

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);
		}
	}
}

Dans notre cas, nous n'avons aucun probleme Mais lorsque nous le faisons avec un autre type que Button une erreur se produit en nous indiquant que le type personnalisé doit être de type Button.

Il va donc nous falloir spécialiser le type Tab pour lui ajouter un bouton de fermeture et comprendre comment fonctionne le composant Button.

Je constate que dans le constructeur du Button, la propriété mouseChildren est définit a false ce qui fait en sorte que ces enfants ne peuvent emettre d'evenement. Or si nous ajoutons un bouton nous ne pourrons pas emettre l'evenement click, il faut donc y remedier. Dans un premier temps, dérivons la classe Tab en une classe spécialisée TabCloser et nous initialisons la propriété mouseChildren :

package net.iteratif.controls {
	import mx.controls.tabBarClasses.Tab;
	public class TabCloser extends Tab {
		super();
		mouseChildren = true;
	}
}

remarque : concernant la classe Tab, elle n'est pas visible a cause du metadata \[ExcludeClass\] donc pas de panique si vous ne la voyez pas apparaitre dans la completion de code.

Pour ajouter le bouton de fermeture, nous allons redéfinir la methode protégée 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();
 
                        // Nous vérifions si le bouton de fermeture a été créer
			if(!closeButton) {
				closeButton = new Button();
				closeButton.name = "closeButton";
				closeButton.label = "x";
	            closeButton.focusEnabled = false;
	            // Nous le rendons invisible
				closeButton.visible = false;
				closeButton.width = 16;
				closeButton.height = 16;
				addChild(closeButton);
			}
		}
	}
}

Nous pouvons mettre a jour notre classe TabControl :

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);
		}
	}
}

Disposition

Aprés essai, nous remarquons que le bouton se trouve en haut a gauche mais qu'il est occulté par l'etat du bouton, il faut donc s'arranger pour faire passer le bouton de fermeture en premier plan et le mettre dans le coin en haut a droit.

Nous pouvons rédefinir la methode de placement layoutContents mais pour cela il faut utiliser l'espace de nom mx_internal :

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;
	            // Nous le rendons 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);
 
            // Nous placons le bouton de fermeture en haut a droite
            closeButton.x = unscaledWidth - close.width - 5;
            closeButton.y = 2;
 
            // Et nous le placons en premier plan
			setChildIndex(closeButton, numChildren - 1);
		}
	}
}

Ce n'est pas par hazard que j'ai placé le code dans la methode layoutContents puisque c'est dans cette derniere que sont disposés l'icone, l'étiquette et l'apparence du bouton :

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);
        ...
}

Dimension

Il y a un probleme, le bouton de fermeture ecrase l'étiquette du bouton, il faut que nous redimmensionnons le bouton en prenant en compte la taille du bouton de fermeture :

...
public class TabCloser extends Tab {
...
	protected override function measure():void {
			super.measure();
 
		// Elles donnent la taille du composant
		// Nous ajoutons donc celle du bouton de fermeture
		measuredMinWidth = measuredWidth += closeButton.width;
	}
...
}

Visibilité

Voici un autre probleme, le bouton de fermeture est toujours présent, nous voulons le faire apparaitre uniquement quand l'onglet est sélectionné, nous allons le faire dans une méthode de la classe bouton qui s'occupe de rendre visible l'apparence du bouton :

...
public class TabCloser extends Tab {
...
	mx_internal override function viewSkin():void {
	    super.viewSkin();
 
	    // Nous utilisons la propriete selected
	    // pour rendre visible le bouton de fermeture
		close.visible = selected;
	}
...
}

C'est beaucoup mieux maintenant…

L'évènement de fermeture

Passons à la gestion d'évènement pour permettre la fermeture d'un onglet.

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 {
			// Nous diffusons un evenement personnalise
			dispatchEvent(new ItemCloseEvent(ItemCloseEvent.ITEM_CLOSE));
 
			// Nous stoppons la propagation de l'evenement CLICK
			// pour eviter que le composant TabControl ne le capture
			e.stopImmediatePropagation();
		}
 
		...
	}
}

Gestion de l'evenement ITEM_CLOSE dans 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;
 
	/**
	* Diffuse lorsque le bouton de fermeture a ete clique
	* 
	* @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);
		}
 
		/**
		* Nous redefinissons cette methode pour permettre
		* d'ecouter l'evenement emit par le controle TabCloser
		*/
		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;                         	
        }
 
        /**
        * Execute lorsque l'evenement ITEM_CLOSE se produit
        */
        protected function closeHandler(e:ItemCloseEvent):void {
        	var currentTarget:DisplayObject = DisplayObject(e.currentTarget);
        	currentTarget.removeEventListener(ItemCloseEvent.ITEM_CLOSE,closeHandler);
 
			// Nous recuperons l'index correspondant a l'onglet
        	var index:int = getChildIndex(currentTarget);
 
        	var event:ItemCloseEvent = new ItemCloseEvent(ItemCloseEvent.ITEM_CLOSE);
        	event.index = index;
 
            // Nous diffusons l'evenement
            dispatchEvent(event);
 
        	// Et stoppons la propagation de l'evenement
        	e.stopImmediatePropagation();
        }
	}
}

Voila notre composant est pret, passons a son utilisation :

<?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">
		<!-- Nous alimentons le controle TabControl avec le 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>

Bien entendu je vous laisse le soin de styliser le composant de manière a ce qu'il ressemble a celui du composant TabNavigation.

Code source de ce tutorial : TabControlProject.zip

A voir également : Personnaliser le composant TabNavigator

Par ITERATIF - BUGALOTTO Olivier (2007)