Java : Modifier et customiser un JTabbedPane

Partager cet article

Temps estimé pour la lecture de cet article : 38 min

Dans cet article je vais vous présenter comment personnaliser le JTabbedPane de swing, en effet par défaut le composant de swing est plutôt simple. Ici on va chercher à pouvoir fermer les onglets et même les customiser. Pour ce faire on va redéfinir les méthodes du composant BasicTabbedPaneUI.

Afin de pouvoir fermer un onglet, on définit une classe qui extends un JTabbedPane. On redéfinit certaines méthodes afin de dessiner une croix sur l’onglet permettant la fermeture. On crée une classe qui va implémenter MouseListener et MouseMotionListener afin d’ajouter la fermeture de l’onglet désiré. Enfin avant de commencer à expliquer le code, je vais vous montrer ce qu’on a par défaut et ce que l’on va obtenir :

JTabbedPane : comportement par défaut

JTabbedPane : comportement par défaut

Et voila le résultat que l’on va obtenir :

ClosableTabbedPane : notre nouveau JTabbedPane

ClosableTabbedPane : notre nouveau JTabbedPane

On va commencer par créer une classe main et fenetre, dans le main on créera seulement une instance de la classe Fenetre.

Main.java
import main.Fenetre;
public class Main {
public static void main(String[] args) {
Fenetre fen = new Fenetre();
}
}
Fenetre.java
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import net.miginfocom.swing.MigLayout;
public class Fenetre extends JFrame{
private static final long serialVersionUID = 1L;
private JPanel central;
public Fenetre() {
central = new JPanel(new MigLayout("fill,insets 0"));
central.setBackground(new Color(119, 135, 136));
central.setBorder(BorderFactory.createMatteBorder(1, 1, 1, 1,Color.GRAY));
ClosableTabbedPane p = new ClosableTabbedPane();
p.setUI(new Tabbed());
JPanel h = new JPanel();
central.add(p,"grow");
p.addTab("Onglet 1", h);
p.addTab("Onglet 2",new JPanel());
p.setupTabTraversalKeys(p);
this.setTitle("JTabbedPane");
this.setSize(800, 600);
this.setLocationRelativeTo(null); // centre l'appli
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(central);
this.setVisible(true);
}
}

On va ensuite créé la classe qui extends le BasicTabbedPaneUI qui va nous permettre de redéfinir le style des onglets et des fenêtres.

Tabbed.java
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
/**
* La classe Tabbed permet de modifier l'apparence des onglets du JtabbedPane et 
* elle extends la class BasicTabbedPaneUI.
*/
public class Tabbed extends BasicTabbedPaneUI {
private FontMetrics boldFontMetrics;
private Font boldFont;
/**
* La méthode paintTabBackground permet de changer la couleur de
* fond des onglets, et notamment l'arrière plan.
*/
protected void paintTabBackground(Graphics g, int tabPlacement,
int tabIndex, int x, int y, int w, int h, boolean isSelected) {
Rectangle rect = new Rectangle();
g.setColor(new Color(170,180,179));
g.fillRect(x, y, w, h);
if(isSelected) {
g.setColor(new Color(62,193,189));
g.fillRect(x, y, w, h);
}
}
/**
* La méthode paintTabBorder permet de changer l'apparence des
* bordures des onglets, elle prend les mêmes paramètres que la
* méthode paintTabBackground
*/
protected void paintTabBorder(Graphics g, int tabPlacement,
int tabIndex, int x, int y, int w, int h, boolean isSelected) {
Rectangle rect = getTabBounds(tabIndex, new Rectangle(x, y, w, h));
g.setColor(new Color(19,19,19));
g.drawRect(x, y, w, h);
if(isSelected)
g.setColor(new Color(121,134,133));
}
/**
* Permet de redéfinir le focus, quand on a le focus sur un onglet
* un petit rectangle en pointillé apparaît en redéfinissant la méthode
* on peut supprimer ce comportement.
*/
protected void paintFocusIndicator(Graphics g, int tabPlacement, Rectangle[] 
rects, int tabIndex, Rectangle iconRect, Rectangle textRect, boolean isSelected) {
}
/**
* Calcul de la hauteur des onglets
*/
protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
int vHeight = fontHeight;
if (vHeight % 2 > 0)
vHeight += 2;
return vHeight;
}
/**
* Calcul de la largeur des onglets
*/
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics 
metrics){
return super.calculateTabWidth(tabPlacement, tabIndex, metrics) + 
metrics.getHeight()+15;
}
/**
* Définit l'emplacement ou se trouvera le texte, on peut ainsi le décaler, ici 
* on le laisse a 0. Le titre sera donc au centre de l'onglet.
*/
protected int getTabLabelShiftY(int tabPlacement,int tabIndex,boolean isSelected) {
return 0;
}
/**
* Permet d'affecter une marge (en haut, a gauche, en bas, a droite) entre les 
* onglets et l'affichage du contenu. 
*/
protected Insets getContentBorderInsets(int tabPlacement) {
return new Insets(0,0,0,0);
}
/**
* Permet de définir des points précis comme par exemple :
* tabAreaInsest.left => on décale les onglets de l'espace que l'on souhaite ici 
* on le laisse a 0, donc les onglets commenceront exactement au bord gauche du 
* JtabbedPane.
*/
protected void installDefaults() {
super.installDefaults();
tabAreaInsets.left = 0;
selectedTabPadInsets = new Insets(0, 0, 0, 0);
tabInsets = selectedTabPadInsets;
boldFont = tabPane.getFont().deriveFont(Font.BOLD);
boldFontMetrics = tabPane.getFontMetrics(boldFont);
}
/**
* On peut choisir la couleur du texte, et son font.
*/
protected void paintText(Graphics g, int tabPlacement, Font font, FontMetrics 
metrics, int tabIndex, String title, Rectangle textRect, boolean isSelected) {
if (isSelected) {	
int vDifference = (int)(boldFontMetrics.getStringBounds(title,g).getWidth()) 
- textRect.width;
textRect.x -= (vDifference / 2);
super.paintText(g, tabPlacement, boldFont, boldFontMetrics, tabIndex, 
title, textRect, isSelected);
}
else
super.paintText(g, tabPlacement, font, metrics, tabIndex, title, textRect, 
isSelected);
}
/**
* Permettent de redéfinir la bordure du haut des onglets.
*/
protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, int 
selectedIndex, int x, int y, int w, int h) {        	
}
/**
* Permettent de redéfinir la bordure de droite des onglets.
*/
protected void paintContentBorderRightEdge(Graphics g, int tabPlacement,int 
selectedIndex, int x, int y, int w, int h) {
}
/**
* Permettent de redéfinir la bordure de gauche des onglets.
*/
protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement,int 
selectedIndex, int x, int y, int w, int h) {
}
/**
* Permettent de redéfinir la bordure du bas des onglets.
*/
protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement,int 
selectedIndex, int x, int y, int w, int h) {
}
}

On créé maintenant la classe qui va extends le JTabbedPane permettant ainsi de dessiner la croix et d’y ajouter la classe qui va contrôler l’action de la croix dessinée.

ClosableTabbedPane.java
import java.awt.AWTKeyStroke;
import java.awt.Component;
import java.awt.Graphics;
import java.awt.KeyboardFocusManager;
import java.awt.event.ActionEvent;
import java.util.HashSet;
import java.util.Set;
import javax.swing.AbstractAction;
import javax.swing.InputMap;
import javax.swing.JComponent;
import javax.swing.JTabbedPane;
import javax.swing.KeyStroke;
/**
* La classe ClosableTabbedPane extends la classe JTabbedPane permettant ainsi
* au JTabbedPane d'avoir un système de fermeture des onglets.
*/
public class ClosableTabbedPane extends JTabbedPane {
private static final long serialVersionUID = 1L;
private TabCloseUI closeUI = new TabCloseUI(this);
public void paint(Graphics g) {
super.paint(g);
closeUI.paint(g);
}
/**
* On réimplémente la méthode addTab de JTabbedPane
*/
@Override
public void addTab(String title, Component component) {
super.addTab(title + "  ", component);
}
/**
* La methode getTabTitleAt, permet de récupérer le nom de l'onglet
* qui se situe à l'index donné en paramètre.
* @param index 
* @return : renvoie le nom de l'onglet qui se situe a l'index donné.
*/
public String getTabTitleAt(int index) {
return super.getTitleAt(index).trim();
}
public boolean tabAboutToClose(int tabIndex) {
return true;
}
/**
* La methode setupTabTraversalKeys permet d'utiliser les raccourcis : 
* ctrl+ tab et ctrl + shift + tab afin de naviguer entre les onglets.
* Elle permet également d'utiliser le raccourci ctrl+w afin de fermer l'onglet 
* selectionné.
* @param tabbedPane : le systeme d'onglets à qui on veut attribuer la methode
*/
public void setupTabTraversalKeys(final JTabbedPane tabbedPane) {
KeyStroke ctrlTab = KeyStroke.getKeyStroke("ctrl TAB");
KeyStroke ctrlShiftTab = KeyStroke.getKeyStroke("ctrl shift TAB");
KeyStroke controlW = KeyStroke.getKeyStroke("control W");
/*
* Un Set est une collection qui n'accepte pas les doublons. Les Set sont particulièrement adaptés pour
* manipuler une grande quantité de données. La classe AWTKeyStroke correspond à une action sur le clavier. 
* Cette classe correpsond seulement à une pression ou un relachement d'une touche. (KEY_PRESSED and KEY_RELEASED)
* > getFocusTraversalKeys permet de récupérer les touches par défaut du composant.
* > on retire les touches ctrl+t et ctrl+w de la liste des touches du composant.
*/
Set forwardKeys = new HashSet(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
forwardKeys.remove(ctrlTab);
tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forwardKeys);
Set backwardKeys = new HashSet(tabbedPane.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
backwardKeys.remove(ctrlShiftTab);
tabbedPane.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backwardKeys);
/* On va alors ajouter une touche au composant JTabbedPane. InputMap fournit une liaison entre un événement 
* d'entrée et un objet. On utilise la classe ActionMap, afin de déterminer une action à effectuer lorsqu'
* une touche est enfoncée. 
*/
AbstractAction closeTabAction = new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
if(tabbedPane.getTabCount() > 0)
tabbedPane.remove(tabbedPane.getSelectedIndex());
}
};
/* On récupère l'entréee du composant utilisé, et on utilise ces constantes. 
On ajoute avec le put la liaison entre le keystroke et l'action du même nom
Pour le ctrl+w on ajoute la liaison à l'action anonyme.
*/
InputMap inputMap = tabbedPane.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(ctrlTab, "navigateNext");
inputMap.put(ctrlShiftTab, "navigatePrevious");
inputMap.put(controlW, "closeTab");
tabbedPane.getActionMap().put("closeTab", closeTabAction);
}
}

Enfin maintenant qu’on a dessiné une croix on va lui définir une action en implémentant les événements MouseListener et MouseMotionListener.

TabCloseUI.java
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
public class TabCloseUI implements MouseListener, MouseMotionListener {
private ClosableTabbedPane tabbedPane; 
private int closeX = 0, closeY = 0, meX = 0, meY = 0;
private int selectedTab; // un int qui récupere l'onglet selectionné
private final int width = 8, height = 8;  // largeur et hauteur de la croix
private Rectangle rectangle = new Rectangle(0, 0, width, height); // la croix sera dessiné dans un rectangle
/**
* Le constructeur de la classe TabCloseUI prends un ClosableTabbedPane en paramètre,
* car cela permet d'ajouter à ce JTabbedPane redéfinit la fermeture des onglets.
* @param pane
*/
public TabCloseUI(ClosableTabbedPane pane) {
tabbedPane = pane;
tabbedPane.addMouseMotionListener(this);
tabbedPane.addMouseListener(this);
}
@Override
public void mouseEntered(MouseEvent me) {
}
@Override
public void mouseExited(MouseEvent me) {
}
@Override
public void mousePressed(MouseEvent me) {
}
@Override
public void mouseClicked(MouseEvent me) {
}
@Override
public void mouseDragged(MouseEvent me) {
}
/**
* Si on relache le bouton de la souris, on ferme l'onglet choisit.
*/
public void mouseReleased(MouseEvent me) {
if (closeUnderMouse(me.getX(), me.getY())) {
boolean isToCloseTab = tabAboutToClose(selectedTab);
if (isToCloseTab && selectedTab > -1) {
tabbedPane.removeTabAt(selectedTab);
}
selectedTab = tabbedPane.getSelectedIndex();
}
}
/**
* La methode mouseMoved, permet si les coordonnes de la souris sont les mêmes 
* qu'un onglet d'afficher une infobulle et la croix permettant la fermeture 
* de l'onglet.
*/
public void mouseMoved(MouseEvent me) {
meX = me.getX();
meY = me.getY();
if (mouseOverTab(meX, meY)) {
controlCursor();
tabbedPane.repaint();
}
}
/**
* Contrôle de l'affichage du curseur, il y a possibilté d'afficher une
* infobulle
*/
private void controlCursor() {
if (tabbedPane.getTabCount() > 0)
if (closeUnderMouse(meX, meY)) {
tabbedPane.setCursor(new Cursor(Cursor.HAND_CURSOR));
if (selectedTab > -1)
tabbedPane.setToolTipTextAt(selectedTab, "Close "
+ tabbedPane.getTitleAt(selectedTab));
} else {
tabbedPane.setCursor(new Cursor(Cursor.DEFAULT_CURSOR));
if (selectedTab > -1)
tabbedPane.setToolTipTextAt(selectedTab, "");
}
}
private boolean closeUnderMouse(int x, int y) {
rectangle.x = closeX;
rectangle.y = closeY;
return rectangle.contains(x, y);
}
public void paint(Graphics g) {
int tabCount = tabbedPane.getTabCount();
for (int j = 0; j < tabCount; j++)
if (tabbedPane.getComponent(j).isShowing()) {
int x = tabbedPane.getBoundsAt(j).x + tabbedPane.getBoundsAt(j).width - width - 5;
int y = tabbedPane.getBoundsAt(j).y + 5;
drawClose(g, x, y);
break;
}
if (mouseOverTab(meX, meY)) {
drawClose(g, closeX, closeY);
}
}
/**
* La methode drawClose, permet d'afficher la croix voulu si la souris est sur l'onglet
* @param g 
* @param x 
* @param y
*/
private void drawClose(Graphics g, int x, int y) {
if (tabbedPane != null && tabbedPane.getTabCount() > 0) {
Graphics2D g2 = (Graphics2D) g;
drawColored(g2, isUnderMouse(x, y) ? Color.GRAY : new Color(163, 163, 163), x, y);
}
}
private void drawColored(Graphics2D g2, Color color, int x, int y) {
// change la taille de l'ombre de la croix
g2.setStroke(new BasicStroke(3, BasicStroke.JOIN_ROUND,BasicStroke.CAP_ROUND));
g2.setColor(Color.BLACK);
g2.drawLine(x, y, x + width, y + height);
g2.drawLine(x + width, y, x, y + height);
g2.setColor(color);
g2.setStroke(new BasicStroke(3, BasicStroke.JOIN_ROUND,BasicStroke.CAP_ROUND));
g2.drawLine(x, y, x + width, y + height);
g2.drawLine(x + width, y, x, y + height);
}
/**
* La methode isUnderMouse renvoit un boolean permettant de savoir en fonction des coordonnes de la souris
* et de celle passe en parametre, si les coordonnes de la souris et de la croix de l'onglet concordent.
* @param x : abscisse de l'onglet
* @param y : ordonnee de l'onglet
* @return
*/
private boolean isUnderMouse(int x, int y) {
if (Math.abs(x - meX) < width && Math.abs(y - meY) < height)
return true;
return false;
}
private boolean mouseOverTab(int x, int y) {
int tabCount = tabbedPane.getTabCount();
for (int j = 0; j < tabCount; j++)
if (tabbedPane.getBoundsAt(j).contains(meX, meY)) {
selectedTab = j;
closeX = tabbedPane.getBoundsAt(j).x + tabbedPane.getBoundsAt(j).width - width - 5;
closeY = tabbedPane.getBoundsAt(j).y + 5;
return true;
}
return false;
}
public boolean tabAboutToClose(int tabIndex) {
return true;
}
}

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *