LECON 316


Les tableaux, les vrais
Nous continuons notre descente aux enfers avec un autres composant assez complexe.
Il a été une de mes bêtes noires pendant quelques temps : j'espère donc que ce chapitre vous permettra de vous simplifier son apprentissage.

Bon, je vous propose de commencer dès maintenant !
Je vous le promets : ça ne fera pas mal... (ou peut-être un peu).
Sommaire du chapitre :

  • Premiers pas
  • Les cellules, c'est la vie
  • Contrôlez l'affichage de vos cellules
  • Des tableaux très actifs !
  • Ajouter des lignes et des colonnes
  • Ce qu'il faut retenir

Premiers pas

Bon. Les tableaux sont des composants qui permettent d'afficher des données de façon structurée : dans un tableau.

Tout le monde sait ce qu'est un tableau.
Non ? Vous me charriez, là... En voici un :


Le code source de ce programme est :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
 
 
public class Fenetre extends JFrame {
 
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(300, 120);
               
                //Les données du tableau
               Object[][] data = {    {"Cysboy", "28 ans", "1.80 cm"},
                               {"BZHHydde", "28 ans", "1.80 cm"},
                               {"IamBow", "24 ans", "1.90 cm"},
                               {"FunMan", "32 ans", "1.85 cm"}
               };
                //Les titres des colonnes
               String  title[] = {"Pseudo", "Age", "Taille"};
               JTable tableau = new JTable(data, title);
                //On ajoute notre tableau à notre contentPane dans un scroll
                //Sinon les titres des colonnes ne s'afficheront pas ! !    
               this.getContentPane().add(new JScrollPane(tableau));
        }
        
        
        public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
        
}


Code simple et clair !
Vous instanciez un objet JTable en lui passant en paramètres les données qu'il doit utiliser.
Attention : le tableau correspondant aux titres des colonnes de votre tableau peuvent être des String ou de type Object ! ! Tandis que les données, elles, sont des Object.

Pourquoi les données doivent-elles être de type Object ?

Vous allez voir, un peu plus loin, que nous pouvons mettre plusieurs types d'éléments dans un tableau ! Mais l'heure n'est pas encore venue pour ce genre de choses... Il vous faut voir comment fonctionne cet objet avant cela !

Je comprends que vous soyez pressés mais tout vient à point à qui sait attendre.
Bon, d'accord. Mais pourquoi tu as mis le tableau dans un scroll ?

En fait, ceux qui ont essayé d'ajouter le tableau dans le contentPane sans scroll ont dû voir que les titres de colonnes n'apparaissent pas...
Le scroll indique automatiquement à notre tableau où il doit afficher ses titres !
Si vous ne faites pas ceci, vous êtes OBLIGÉS de spécifier où mettre l'entête du tableau, comme ceci :

Code : Java -
1
2
3
4
//On indique que l'entête doit être au nord, donc au-dessus
this.getContentPane().add(tableau.getTableHeader(), BorderLayout.NORTH);
//Et le corps au centre !
this.getContentPane().add(tableau, BorderLayout.CENTER);


Bon, je pense qu'on a fait le tour des préliminaires... Rentrons dans le vif du sujet !

Les cellules, c'est la vie

Vos tableaux sont composés de ce qu'on appelle : des cellules !
Vous pouvez les voir facilement, elles sont encadrées par des bordures noires et contiennent les données que vous avez mises dans le tableau d'Object et de String.

Celles-ci peuvent être retrouvées par des coordonnées (x, y) où x correspond au numéro de ligne et y au numéro de la colonne !
Une cellule est donc une intersection d'une ligne et d'une colonne.
Donc, afin de pouvoir modifier une cellule, nous allons devoir récupérer la ligne et la colonne auxquelles elle appartient ?

Tout à fait... Et là... Vous devez avoir à peu près cette tête-là :
Ne vous inquiétez pas, nous allons prendre tout ça point par point.

Tout d'abord, nous allons commencer par changer la taille d'une colonne et d'une ligne, ce qui nous donne, au final, quelque chose comme ça :


Vous allez voir que le code utilisé est simple comme tout, encore fallait-il que vous sachiez quelles méthodes et quels objets utiliser... Voici le code permettant d'obtenir ce résultat :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        private JButton retablir = new JButton("Rétablir");
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(300, 240);
               
               Object[][] data = {    {"Cysboy", "28 ans", "1.80 cm"},
                               {"BZHHydde", "28 ans", "1.80 cm"},
                               {"IamBow", "24 ans", "1.90 cm"},
                               {"FunMan", "32 ans", "1.85 cm"}
               };
        
               String  title[] = {"Pseudo", "Age", "Taille"};
               this.tableau = new JTable(data, title);
               
               JPanel pan = new JPanel();
               
               change.addActionListener(new ActionListener(){
                       public void actionPerformed(ActionEvent arg0) {                            
                               changeSize(200, 80);
                               change.setEnabled(false);
                               retablir.setEnabled(true);
                       }                      
               });
               
               retablir.addActionListener(new ActionListener(){
                       public void actionPerformed(ActionEvent arg0) {
                               
                               changeSize(75, 16);
                               change.setEnabled(true);
                               retablir.setEnabled(false);
                       }                      
               });
               
               retablir.setEnabled(false);
               pan.add(change);
               pan.add(retablir);
               
               //On remplace cette ligne
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
               this.getContentPane().add(pan, BorderLayout.SOUTH);
 
        }
        
        /**
         * Change la taille d'une ligne et d'une colonne
         * J'ai mis deux boucles afin que vous puissiez voir comment parcourir les colonnes et les lignes
         * @param width
         * @param height
         */
        public void changeSize(int width, int height){
               //On crée un objet TableColumn afin de travailler sur notre colonne
               TableColumn col;
               for(int i = 0; i < tableau.getColumnCount(); i++){
                       if(i == 1){
                               //On récupère le modèle de la colonne
                               col = tableau.getColumnModel().getColumn(i);
                               //On lui affecte la nouvelle valeur
                               col.setPreferredWidth(width);
                       }
               }                              
               for(int i = 0; i < tableau.getRowCount(); i++){
                       //On affecte la taille de la ligne à l'indice spécifié !
                       if(i == 1)
                               tableau.setRowHeight(i, height);
               }
        }
        
        public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
        
}

Tout comme pour les tableaux vus dans la première partie de ce tuto, les indices de colonnes et de lignes d'un JTable commencent à 0 ! !


J'ai mis deux boucles afin que vous puissiez voir comment parcourir les colonnes et les lignes de votre objet mais, ici, nous aurions pu nous contenter d'un indice mis en dur dans notre code...

Ceci mis à part, vous constatez que la ligne et la colonne concernées changent bien de tailles lors du clic sur les boutons !
Vous venez donc de voir comment changer la taille des cellules de façon dynamique.

Je dis ça parce que, au cas où vous ne l'auriez pas remarqué, vous pouvez changer la taille des colonnes manuellement. Il vous suffit de cliquer sur un séparateur de colonne, de maintenir le clic et de déplacer le dit séparateur :

D'accord, on a bien compris !
Par contre, cette instruction nous semble bizarre : tableau.getColumnModel().getColumn(i); . Tu pourrais creuser un peu ?


J'allais y venir, mais le fait que vous ayez remarqué ceci est bien la preuve que vous commencez à vous poser les bonnes questions !

En fait, vous devez savoir qu'il y a un objet qui fait le lien entre votre tableau et vos données. Celui-ci est un ce qu'on appelle un modèle de tableau (ça vous rappelle les modèles d'arbres, non ?).
L'objet en question s'appelle JTableModel et vous allez voir qu'il permet de faire des choses très intéressantes ! !

Par exemple, vous avez vu que vous pouvez mettre des données de n'importe quel type héritant de la classe Object.

Essayez ce morceau de code :
Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.TableColumn;
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(600, 140);
               
               Object[][] data = {    {"Cysboy", new JButton("6boy"), new Double(1.80), new Boolean(true)},
                                                     {"BZHHydde", new JButton("BZH"), new Double(1.78), new Boolean(false)},
                                                     {"IamBow", new JButton("BoW"), new Double(1.90), new Boolean(false)},
                                                     {"FunMan", new JButton("Year"), new Double(1.85), new Boolean(true)}
               };
        
               String  title[] = {"Pseudo", "Age", "Taille", "OK ?"};
               this.tableau = new JTable(data, title);
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
               
        }
        
        public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
}


Ce code doit vous donner ceci :


Maintenant, vous allez créer votre modèle de tableau !
Pour cela, il vous suffit de créer une classe héritant de AbstractTableModel qui, vous l'avez sûrement deviné, est une classe abstraite...

Voici donc un code mettant en pratique mes dires :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
import java.awt.BorderLayout;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(600, 140);
               
               Object[][] data = {    {"Cysboy", new JButton("6boy"), new Double(1.80), new Boolean(true)},
                                                     {"BZHHydde", new JButton("BZH"), new Double(1.78), new Boolean(false)},
                                                      {"IamBow", new JButton("BoW"), new Double(1.90), new Boolean(false)},
                                                     {"FunMan", new JButton("Year"), new Double(1.85), new Boolean(true)}
               };
        
               String  title[] = {"Pseudo", "Age", "Taille", "OK ?"};
               
               ZModel model = new ZModel(data, title);
               System.out.println("Nombre de colonne : " + model.getColumnCount());
               System.out.println("Nombre de ligne : " + model.getRowCount());
               this.tableau = new JTable(model);
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
               
        }
         
        //CLASSE MODÈLE PERSONNALISÉE
        class ZModel extends AbstractTableModel{
 
               private Object[][] data;
               private String[] title;
               /**
                * Constructeur
                * @param data
                * @param title
                */
               public ZModel(Object[][] data, String[] title){
                       this.data = data;
                       this.title = title;
               }
               
               /**
                * Retourne le nombre de colonnes
                */
               public int getColumnCount() {
                       return this.title.length;
               }
               
               /**
                * Retourne le nombre de lignes
                */
               public int getRowCount() {
                       return this.data.length;
               }
               
               /**
                * Retourne la valeur à l'emplacement spécifié
                */
               public Object getValueAt(int row, int col) {
                       return this.data[row][col];
               }
                               
        }
        
        public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
}


Ce qui nous donne ceci :


Eh ! ! On n'a même plus les titres de nos colonnes !

Effectivement. Ceci est dû au fait que vous n'avez redéfini que les méthodes abstraites de la classe AbstractTableModel.
Afin de voir nos titres de colonnes apparaître, il faut redéfinir la méthode getColumnName(int col) , elle devrait ressembler à ceci :

Code : Java -
1
2
3
4
5
6
/**
* Retourne le titre de la colonne à l'indice spécifé
*/
public String getColumnName(int col) {
  return this.title[col];
}


Ré-exécutez votre code, après avoir rajouté cette méthode dans votre objet ZModel, et vous devriez avoir ceci :


Vous commencez à faire des choses sympas !
Je vais vous montrer un autre truc marrant avec les modèles.
Regardez ce que vous pouvez obtenir :


Vous avez vu ? Vos booléens se sont transformés en cases à cocher !
Les booléens valant vrai sont devenus des cases cochées et les booléens valant faux sont maintenant des cases non cochées !

Comment tu as fait ça ?

D'une manière toute simple, j'ai redéfini une méthode dans mon modèle et c'est automatique !
Cette méthode permet de retourner la classe du type de valeur d'un modèle et de transformer vos booléens en cases à cocher...
Au moment où notre objet crée le rendu des cellules, il invoque cette méthode et se sert de cette dernière pour créer certaines choses, comme ce que vous venez de voir.


Pour avoir ce rendu, il vous suffit de redéfinir la méthode getColumnClass(int col) . Cette méthode retourne un objet Class.
Je vous laisse réfléchir un peu sur le "comment renvoyer cette valeur".
Pour voir comment faire, c'est juste en dessous :
               
/**
* Retourne la classe de la donnée de la colonne
* @param col
*/
public Class getColumnClass(int col){
                //On retourne le type de la cellule à la colonne demandée
                //On se moque de la ligne puisque les données sur chaque ligne sont les mêmes
                //On choisit donc la première ligne
                return this.data[0][col].getClass();
}


Je ne sais pas si vous avez remarqué mais vos cellules ne sont plus éditables !
Oh non ! Qu'est-ce qui se passe ?

Je vous avait prévenus que ce composant était pénible...
En fait, vous devez aussi dire à votre modèle d'avertir votre JTable que certaines cellules peuvent être éditées et d'autres non (comme le bouton, par exemple).

Afin de réussir ceci, vous devez redéfinir la méthode isCellEditable(int row, int col) qui, dans la classe mère retourne tout le temps false ...
Rajouter donc ce morceau de code dans notre modèle :

Code : Java -
1
2
3
4
5
6
7
/**
 * Retourne vrai si la cellule est éditable : celle-ci sera donc éditable
 * @return boolean
 */
public boolean isCellEditable(int row, int col){
        return true; 
}


Vos cellules sont de nouveau éditables, cependant, vous n'avez pas dit à votre modèle que la cellule contenant votre bouton ne devait pas être éditable...
T'es gentil, mais comment on fait ça ?

Grâce à la méthode getClass() de tout objet Java !
Vous pouvez déterminer de quelle classe est issu votre objet et, pour faire la comparaison avec une classe, on utilise le mot clé instanceof.
Regardez comment on procède, c'est clore comme de l'eau de ruche clair comme de l'eau de roche :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
/**
 * Retourne vrai si la cellule est éditable : celle-ci sera donc éditable
 * @return boolean
 */
public boolean isCellEditable(int row, int col){
        //On appelle la méthode getValueAt qui retourne la valeur d'une cellule
        //Et on fait un traitement spécifique si c'est un JButton
        if(getValueAt(0, col) instanceof JButton)
               return false;
        return true; 
}


Voilà ! ! !
Victoire ! Les cellules sont de nouveau éditables sauf le JButton !
Par contre, je vois que certains d'entre vous attendent toujours de voir apparaître un zoli bouton...
Pour réussir à faire ceci, nous n'allons pas utiliser un modèle de tableau, mais un objet qui s'occupe de gérer le contenu et la façon dont ce contenu est affiché.

Les modèles servent à faire un pont entre ce qu'affiche JTable et ce que fait l'utilisateur. Pour changer la façon dont sont affichées les cellules, nous devrons utiliser DefaultCellRenderer.

Contrôlez l'affichage de vos cellules

Bon, je ne vais pas vous faire languir plus longtemps...
Cependant, vous devrez faire la distinction entre un TableModel et un DefaultTableCellRenderer.
Le premier fait le lien entre vous et le tableau tandis que le second s'occupe de gérer l'affichage dans les cellules !

Le but du jeu est de définir une nouvelle façon de dessiner les composants dans les cellules.
En définitive, nous n'allons pas vraiment faire ça, mais nous allons dire à notre tableau que la valeur qu'il a dans telle ou telle cellule est un composant (bouton ou autre).

Rien de plus simple à faire, encore fallait-il le savoir.
Il suffit de créer une classe héritant de DefaultTableCellRenderer et de redéfinir la méthode public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) .
Waw ! Il y en a des paramètres !

En effet, mais dans le cas qui nous intéresse, nous n'en avons besoin que d'un seul : value.
Vous voyez que cette méthode retourne un objet Component.
Nous allons seulement spécifier de quel type d'objet il s'agit suivant le cas...

Regarder notre classe héritée :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
 
public class TableComponent extends DefaultTableCellRenderer {
 
        public Component getTableCellRendererComponent(JTable table,
                       Object value, boolean isSelected, boolean hasFocus, int row,
                       int column) {
               //Si la valeur de la cellule est un JButton, on transtype notre valeur
               if (value instanceof JButton){
                       return (JButton) value;
               }
               else
                       return this;
        }
}


Une fois cette classe créée, il suffit juste de dire à notre tableau d'utiliser ce rendu de cellules, avec cette instruction : this.tableau.setDefaultRenderer(JButton.class, new TableComponent()); .
Le premier paramètre permet de dire à notre tableau de faire attention à ce type d'objet et enfin, le second lui dit d'utiliser ce modèle de cellules.

Ce qui nous donne :






Voilà notre bouton en chair et en os !
Je me doute bien que les plus taquins d'entre vous ont dû essayer de mettre plus d'un type de composant dans le tableau... Et vous vous retrouvez le bec dans l'eau car il ne prend en compte que les boutons pour le moment...

En fait, une fois que vous avez défini une classe héritée afin de gérer le rendu de vos cellules, vous avez fait le plus gros du travail...
Tenez, si, par exemple, nous avons ce genre de données à mettre dans notre tableau :

Code : Java -
1
2
3
4
5
Object[][] data = {    {"Cysboy", new JButton("6boy"), new JComboBox(new String[]{"toto", "titi", "tata"}), new Boolean(true)},
                       {"BZHHydde", new JButton("BZH"), new JComboBox(new String[]{"toto", "titi", "tata"}), new Boolean(false)},
                       {"IamBow", new JButton("BoW"), new JComboBox(new String[]{"toto", "titi", "tata"}), new Boolean(false)},
                       {"FunMan", new JButton("Year"), new JComboBox(new String[]{"toto", "titi", "tata"}), new Boolean(true)}
               };


et si nous conservons l'objet de rendu de cellule tel qu'il est actuellement, nous aurons ceci :





J'ai augmenté un peu la taille de la fenêtre et des lignes... Mis à part ça, rien n'a changé !

Les boutons s'affichent toujours mais pas les combos !
Comment faire, alors ?

Je sais que certains d'entre vous ont presque trouvé la solution, j'en suis même sûr.
Vous n'auriez pas ajouté ce qui suit dans votre objet de rendu de cellule ?

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
 
 
public class TableComponent extends DefaultTableCellRenderer {
 
        public Component getTableCellRendererComponent(JTable table,
                       Object value, boolean isSelected, boolean hasFocus, int row,
                       int column) {
               
               if (value instanceof JButton){
                       return (JButton) value;
               }
               //LIGNE RAJOUTÉE
               else if(value instanceof JComboBox){
                       return (JComboBox) value;
               }
               else
                       return this;
        }
}


Ceux qui ont fait ceci ont trouvé la première partie de la solution !
Vous avez bien spécifié à votre objet de retourner une valeur castée en cas de rencontre avec une combo !
Seulement, et j'en suis quasiment sûr, vous avez dû oublier de dire à votre tableau de faire attention aux boutons ET aux combos !
Rappelez-vous cette instruction : this.tableau.setDefaultRenderer(JButton.class, new TableComponent()); .
Votre tableau ne fait attention qu'aux boutons ! !
D'accord. Comment fait-on, alors ?

Tout simplement en changeant JButton.class en JComponent.class .
Après avoir fait ces deux modifs, vous devriez avoir ceci :


Alors ! Elle n'est pas belle, la vie ?
Vous devez avoir saisi comment on fait pour utiliser les modèles de tableaux et les rendus de cellules, maintenant...

Cependant, vous aurez aussi constaté que vos composants sont inertes !
Ceci est dû au fait que vous allez devoir gérer la façon de réagir de vos cellules... Avant d'en arriver là, nous allons voir une autre façon d'afficher correctement des composants dans un JTable.
Nous allons pouvoir laisser de côté notre classe servant de modèle et nous concentrer sur les composants.

Nous allons commencer par revenir à un code plus sobre :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import java.awt.BorderLayout;
 
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(600, 180);
               
               Object[][] data = {    {"Cysboy", "6boy", "Combo", new Boolean(true)},
                                                     {"BZHHydde", "BZH", "Combo", new Boolean(false)},
                                                     {"IamBow", "BoW", "Combo", new Boolean(false)},
                                                     {"FunMan", "Year", "Combo", new Boolean(true)}
               };
        
               String  title[] = {"Pseudo", "Age", "Taille", "OK ?"};
               
               this.tableau = new JTable(data, title);
               this.tableau.setRowHeight(30);
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
               
        }
               public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
}


De là, nous allons créer une classe qui va permettre d'afficher un bouton dans les cellules de la seconde colonne et une combo dans les cellules de la troisième colonne...
Le principe est simple : nous allons créer une classe qui héritera de la classe JButton et qui implémentera l'interface TableCellRenderer.
Nous allons ensuite dire à notre tableau d'utiliser ce type de rendu pour la seconde colonne.

Voici notre classe ButtonRenderer :
Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
import java.awt.Component;
import javax.swing.JButton;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
 
public class ButtonRenderer extends JButton implements TableCellRenderer{
 
        public Component getTableCellRendererComponent(       JTable table, Object value,
                                                                                                  boolean isSelected, boolean isFocus,
                                                                                                  int row, int col) {
               //On écrit dans le bouton ce que contient la cellule
               setText((value != null) ? value.toString() : "");
               //on retourne notre bouton
               return this;
        }
}


Il nous suffit maintenant de mettre à jour le tableau grâce à cette méthode this.tableau.getColumn("Age").setCellRenderer(new ButtonRenderer()); : on recupère la colonne grâce à son titre et on applique le rendu !
Résultat :




Votre bouton est de nouveau éditable, mais ce problème sera réglé par la suite...


Pour le rendu de la cellule numéro 3, je vous laisse un peu chercher, ce n'est pas très difficile maintenant que vous avez appris cette méthode.

La suite logique est donc la gestion des événements...

Des tableaux très actifs !

Bon, la dernière ligne droite avant la fin du chapitre...

Comme je l'ai déjà dit, ici aussi, c'est simple : le tout est d'acquérir les connaissances requises !

Nous allons commencer par le plus difficile et nous terminerons par le plus simple !
Je vous le donne en mille : le composant le plus difficile à utiliser dans un tableau, entre un bouton et une combo c'est : le bouton !

Eh oui, vous verrez que la combo est presque gérée automatiquement, alors que vous allez devoir dire quoi faire à vos boutons...
Afin de réussir à faire ceci, nous allons donc créer une classe que notre tableau va utiliser afin de pouvoir utiliser les boutons pour faire des actions spécifiques.


Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTable;
 
public class ButtonEditor extends DefaultCellEditor {
          
        protected JButton button;
        private boolean   isPushed;
        private ButtonListener bListener = new ButtonListener();
        
        /**
         * Constructeur avec une checkBox
         * @param checkBox
         * @param count
         */
        public ButtonEditor(JCheckBox checkBox) {
               //Par défaut, ce type d'objet travaille avec un JCheckBox
               super(checkBox);
            //On crée à nouveau notre bouton
               button = new JButton();
            button.setOpaque(true);
            //On lui attribue un listener
            button.addActionListener(bListener);
        }
 
        public Component getTableCellEditorComponent(JTable table, Object value,
                           boolean isSelected, int row, int column) { 
               //On définit le numéro de ligne à notre listener
               bListener.setRow(row);
               //Idem pour le numéro de colonne
               bListener.setColumn(column);
               //On passe aussi le tableau pour des actions potentielles
               bListener.setTable(table);
               
               //On réaffecte le libellé au bouton
               button.setText( (value ==null) ? "" : value.toString() );
               //On renvoie le bouton
            return button;
        }
        
        /**
         * Notre listener pour le bouton
         * @author CHerby
         *
         */
        class ButtonListener implements ActionListener{
                 
                 private int column, row;
                 private JTable table;
                 private int nbre = 0;
                 
                 public void setColumn(int col){this.column = col;}
                 public void setRow(int row){this.row = row;}
                 public void setTable(JTable table){this.table = table;}
                 
                 public void actionPerformed(ActionEvent event) {
                       //On affiche un Zoli message mais vous pourriez faire les traitements que vous voulez
                       System.out.println("coucou du bouton : " + ((JButton)event.getSource()).getText() );
                       //On affecte un nouveau libellé à une cellule de la ligne
                       table.setValueAt("New Value " + (++nbre), this.row, (this.column -1));
                 }
          }
          
        }


Ce code n'est pas très difficile à comprendre... Vous commencez à avoir l'habitude de manipuler ce genre d'objet.
Maintenant, afin d'utiliser cet objet avec votre tableau, nous allons lui spécifier cet éditeur dans la colonne correspondante avec cette instruction : this.tableau.getColumn("Age").setCellEditor(new ButtonEditor(new JCheckBox())); .

Le rendu est très probant :


Vous pouvez voir que lorsque vous cliquez sur un bouton, la valeur dans la cellule juste à gauche est modifiée.
L'utilisation est donc très simple. Imaginez par conséquent que la gestion des combos est d'autant plus simple !

Un peu plus tôt, je vous ai fait développer une classe afin de pouvoir afficher la combo normalement. Cependant, il y a beaucoup plus simple... Vous avez pu voir que la classe DefaultCellEditor pouvait prendre un objet en paramètre : dans l'exemple utilisé dans le cas du JButton, il utilisait un JCheckBox.
Vous devez savoir que cet objet peut prendre d'autres types de paramètres :
  • un JComboBox ;
  • un JTextField.


Nous allons donc pouvoir utiliser l'objet DefaultCellEditor directement en lui passant une combo en paramètre...
Nous allons aussi enlever l'objet servant à afficher correctement la combo afin que vous puissiez juger de l'efficacité de cette méthode.

Voici le nouveau code du constructeur de notre fenêtre :

Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import java.awt.BorderLayout;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        //Contenu de notre combo
        private String[] comboData = {"Très bien", "Bien", "Mal"};
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(600, 180);
               //Données de notre tableau
               Object[][] data = {    {"Cysboy", "6boy", comboData[0], new Boolean(true)},
                                                     {"BZHHydde", "BZH", comboData[0], new Boolean(false)},
                                                     {"IamBow", "BoW", comboData[0], new Boolean(false)},
                                                     {"FunMan", "Year", comboData[0], new Boolean(true)}
               };
               //titre du tableau
               String  title[] = {"Pseudo", "Age", "Taille", "OK ?"};
               //Combo à utiliser
               JComboBox combo = new JComboBox(comboData);
               
               this.tableau = new JTable(data, title);               
               this.tableau.setRowHeight(30);
               this.tableau.getColumn("Age").setCellRenderer(new ButtonRenderer());
               this.tableau.getColumn("Age").setCellEditor(new ButtonEditor(new JCheckBox()));
               //On définit l'éditeur par défaut pour la cellule 
               //en lui spécifiant quel type d'affichage prendre en compte
               this.tableau.getColumn("Taille").setCellEditor(new DefaultCellEditor(combo));
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
        }
        
               public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
}


Vous pouvez voir que c'est d'une simplicité enfantine !
Le résultat est, en plus, très convaincant :
 


Votre cellule se "transforme" en combo lorsque vous cliquez dessus !
En fait, lorsque le tableau sent une action sur cette cellule, celui-ci utilise l'éditeur que vous avez spécifié pour celle-ci et, comme nous en avons spécifié un afin d'afficher une combo, la dite combo est affichée !
Si vous préférez que la combo soit affichée directement même sans clic de souris, il vous suffit de laisser l'objet gérant l'affichage et le tour est joué !
De même, pour le bouton, si vous enlevez l'objet de rendu du tableau, celui-ci s'affiche comme un bouton lors du clic sur la cellule !


Il ne nous reste plus qu'à voir comment rajouter des informations dans notre tableau et l'affaire est entendue...
Certains d'entre-vous l'auront remarqué, les boutons ont un drôle de comportement...
Ceci est dû au fait que vous avez défini des comportements spéciaux à votre tableau... Il vous faut donc définir un modèle à utiliser afin de bien définir tous les points comme l'affichage, la mise à jour...


Nous allons donc utiliser un modèle de tableau perso dont les actions seront définis par nous !
Voici la classe Fenetre modifiée en conséquence :

Code : Java -
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
package com.sdz.jtable2;
import java.awt.BorderLayout;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
 
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        //Contenu de notre combo
        private String[] comboData = {"Très bien", "Bien", "Mal"};
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(600, 180);
               //Données de notre tableau
               Object[][] data = {    {"Cysboy", "6boy", comboData[0], new Boolean(true)},
                                                     {"BZHHydde", "BZH", comboData[0], new Boolean(false)},
                                                     {"IamBow", "BoW", comboData[0], new Boolean(false)},
                                                     {"FunMan", "Year", comboData[0], new Boolean(true)}
               };
               //titre du tableau
               String  title[] = {"Pseudo", "Age", "Taille", "OK ?"};
               //Combo à utiliser
               JComboBox combo = new JComboBox(comboData);
               
               //Nous devons utiliser un modèle d'affichage spécifique afin de pallier aux bugs d'affichage !
               ZModel zModel = new ZModel(data, title);
               
               this.tableau = new JTable(zModel);
               this.tableau.setRowHeight(30);
               this.tableau.getColumn("Age").setCellRenderer(new ButtonRenderer());
               this.tableau.getColumn("Age").setCellEditor(new ButtonEditor(new JCheckBox()));
               //On définit l'éditeur par défaut pour la cellule 
               //en lui spécifiant quel type d'affichage prendre en compte
               this.tableau.getColumn("Taille").setCellEditor(new DefaultCellEditor(combo));
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
        }
        
        
        class ZModel extends AbstractTableModel{
 
               private Object[][] data;
               private String[] title;
               /**
                * Constructeur
                * @param data
                * @param title
                */
               public ZModel(Object[][] data, String[] title){
                       this.data = data;
                       this.title = title;
               }
               /**
               * Retourne le titre de la colonne à l'indice spécifé
               */
               public String getColumnName(int col) {
                 return this.title[col];
               }
 
               /**
                * Retourne le nombre de colonnes
                */
               public int getColumnCount() {
                       return this.title.length;
               }
               
               /**
                * Retourne le nombre de lignes
                */
               public int getRowCount() {
                       return this.data.length;
               }
               
               /**
                * Retourne la valeur à l'emplacement spécifié
                */
               public Object getValueAt(int row, int col) {
                       return this.data[row][col];
               }
               
               /**
                * Défini la valeur à l'emplacement spécifié
                */
               public void setValueAt(Object value, int row, int col) {
                       //On interdit la modification sur certaine colonne !
                       if(!this.getColumnName(col).equals("Age") && !this.getColumnName(col).equals("Suppression"))
                               this.data[row][col] = value;
               }
                               
               /**
               * Retourne la classe de la donnée de la colonne
               * @param col
               */
               public Class getColumnClass(int col){
                       //On retourne le type de la cellule à la colonne demandée
                       //On se moque de la ligne puisque les données sur chaque ligne sont les mêmes
                       //On choisit donc la première ligne
                       return this.data[0][col].getClass();
               }
 
               public boolean isCellEditable(int row, int col){
                       return true;
               }
        }
        
        
        
        
               public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
}



Vous aurez remarqué que nous construisons notre tableau par le biais de notre modèle, ceci implique que nous allons aussi passer par note modèle afin de modifier notre tableau !
La conséquence direct de ceci : nous allons devoir modifier un peu notre classe ButtonEditor.

Voici la classe ButtonEditor utilisant le modèle de tableau pour gérer la modification des valeurs :

Code : Java -
package com.sdz.jtable2;
 
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
 
public class ButtonEditor extends DefaultCellEditor {
          
        protected JButton button;
        private boolean   isPushed;
        private ButtonListener bListener = new ButtonListener();
        
        /**
         * Constructeur avec une checkBox
         * @param checkBox
         * @param count
         */
        public ButtonEditor(JCheckBox checkBox) {
               //Par défaut, ce type d'objet travaille avec un JCheckBox
               super(checkBox);
            //On crée à nouveau notre bouton
               button = new JButton();
            button.setOpaque(true);
            //On lui attribue un listener
            button.addActionListener(bListener);
        }
 
        public Component getTableCellEditorComponent(JTable table, Object value,
                           boolean isSelected, int row, int column) { 
               //On définit le numéro de ligne à notre listener
               bListener.setRow(row);
               //Idem pour le numéro de colonne
               bListener.setColumn(column);
               //On passe aussi le tableau pour des actions potentielles
               bListener.setTable(table);
               
               //On réaffecte le libellé au bouton
               button.setText( (value ==null) ? "" : value.toString() );
               //On renvoie le bouton
            return button;
        }
        
        /**
         * Notre listener pour le bouton
         * @author CHerby
         *
         */
        class ButtonListener implements ActionListener{
                 
                 private int column, row;
                 private JTable table;
                 private int nbre = 0;
                 private JButton button;
                 
                 public void setColumn(int col){this.column = col;}
                 public void setRow(int row){this.row = row;}
                 public void setTable(JTable table){this.table = table;}
                 
                 public JButton getButton(){return this.button;}
                 
                 public void actionPerformed(ActionEvent event) {
                       //On affiche un Zoli message mais vous pourriez faire les traitements que vous voulez
                       System.out.println("coucou du bouton : " + ((JButton)event.getSource()).getText() );
                       //On affecte un nouveau libellé à une celulle de la ligne
                       ((AbstractTableModel)table.getModel()).setValueAt("New Value " + (++nbre), this.row, (this.column -1));        
                       //Permet de dire à notre tableau qu'une valeur a changé 
                       //à l'emplacement déterminé par les valeur passée en paramètre
                       ((AbstractTableModel)table.getModel()).fireTableCellUpdated(this.row, this.column - 1);
                       this.button = ((JButton)event.getSource());
                 }
          }
          
        }


Voilà, après ceci, le bug d'affichage devrait avoir disparu !
Je vous propose donc de continuer dans notre lancée.

Ajouter des lignes et des colonnes

Je vais profiter de ce point pour vous montrer une autre façon d'initialiser un tableau :

Code : Java -
1
2
//data et title sont toujours nos tableaux d'objets !
JTable tableau = new JTable(new DefaultTableModel(data, title));

Et l'intérêt de faire comme ça ?

C'est très simple : l'ajout et la suppression dynamiques d'entrées dans un tableau se fait via un modèle de rendu, mais vous n'avez pas l'obligation de recréer le vôtre, celui-ci fera très bien l'affaire !

Dans un premier temps, nous allons ajouter et retirer des lignes à notre tableau.
Nous allons garder le même code que précédemment avec deux - trois ajouts :
  • le bouton pour ajouter un ligne ;
  • le bouton pour effacer une ligne.


Grâce au modèle par défaut défini lors de la création du tableau, nous allons pouvoir avoir accès à deux méthodes fort utiles :
  • addRow(Object[] lineData) : permet d'ajouter une ligne au modèle et met automatiquement à jour le tableau ;
  • removeRow(int row) : efface une ligne du modèle et met automatiquement à jour le tableau.


Afin de pouvoir utiliser le dit modèle, nous allons devoir récupérer celui-ci.
En fait, c'est notre tableau qui va nous le fournir en invoquant la méthode getModel() qui retourne un objet TableModel.
Un cast sera nécessaire afin de pouvoir utiliser l'objet retourné !


Au final, voici que nous obtiendrons :
au lancement du programme
 





après ajout d'une ligne

 

après suppression d'une ligne



Vous constatez que j'ai ajouté un bouton "Ajouter une ligne" ainsi qu'un bouton "Supprimer la ligne" ; mis à part ça, l'IHM n'a pas changé.

Voilà les codes source utilisés dans cet exemple. Ceux-ci sont assez documentés et, mis à part l'utilisation de addRow(Object[] lineData) et de removeRow(int row) , rien n'a changé.
Souvenez-vous bien que nous passons par un modèle de tableau perso !
Nous allons devoir gérer les méthodes d'ajout et de suppression de ligne dans ce modèle.


De ce fait, c'est nous qui allons devoir déterminer et coder les actions d'ajout et de suppression de lignes dans notre tableau !

Fenetre.java



Code : Java -
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
package com.sdz.jtable;
 
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
 
 
public class Fenetre extends JFrame {
 
        private JTable tableau;
        private JButton change = new JButton("Changer la taille");
        private String[] comboData = {"Très bien", "Bien", "Mal"};
        private String supp = "Supprimer la ligne";
        private JComboBox combo;
        
        public Fenetre(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(600, 250);
               this.createContent();
        }
        
        private void createContent(){
               //Données de notre tableau
               Object[][] data = {    {"Cysboy", "6boy", comboData[0], new Boolean(true), supp},
                                                     {"BZHHydde", "BZH", comboData[0], new Boolean(false), supp},
                                                     {"IamBow", "BoW", comboData[0], new Boolean(false), supp},
                                                     {"FunMan", "Year", comboData[0], new Boolean(true), supp}
               };
 
               //titre du tableau
               String  title[] = {"Pseudo", "Age", "Taille", "OK ?", "Suppression"};
               //Combo à utiliser
               combo = new JComboBox(comboData);
               
               //Nous devons utiliser un modèle d'affichage spécifique afin de pallier aux bugs d'affichage !
               ZModel zModel = new ZModel(data, title);
               
               this.tableau = new JTable(zModel);            
               this.tableau.setRowHeight(30);
               this.tableau.getColumn("Age").setCellRenderer(new ButtonRenderer());
               this.tableau.getColumn("Age").setCellEditor(new ButtonEditor(new JCheckBox()));
               
               //On définit l'éditeur par défaut pour la cellule 
               //en lui spécifiant quel type d'affichage prendre en compte
               this.tableau.getColumn("Taille").setCellEditor(new DefaultCellEditor(combo));
               DefaultTableCellRenderer dcr = new DefaultTableCellRenderer();
               this.tableau.getColumn("Taille").setCellRenderer(dcr);
               
               //On définit un éditeur pour la colonne "supprimer"
               this.tableau.getColumn("Suppression").setCellEditor(new DeleteButtonEditor(new JCheckBox()));
               
               //On ajoute le tableau
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
               
               JButton ajouter = new JButton("Ajouter une ligne");
               ajouter.addActionListener(new MoreListener());
               this.getContentPane().add(ajouter, BorderLayout.SOUTH);
        }              
 
        class ZModel extends AbstractTableModel{
 
               private Object[][] data;
               private String[] title;
               /**
                * Constructeur
                * @param data
                * @param title
                */
               public ZModel(Object[][] data, String[] title){
                       this.data = data;
                       this.title = title;
               }
               /**
               * Retourne le titre de la colonne à l'indice spécifé
               */
               public String getColumnName(int col) {
                 return this.title[col];
               }
 
               /**
                * Retourne le nombre de colonnes
                */
               public int getColumnCount() {
                       return this.title.length;
               }
               
               /**
                * Retourne le nombre de lignes
                */
               public int getRowCount() {
                       return this.data.length;
               }
               
               /**
                * Retourne la valeur à l'emplacement spécifié
                */
               public Object getValueAt(int row, int col) {
                       return this.data[row][col];
               }
               
               /**
                * Défini la valeur à l'emplacement spécifié
                */
               public void setValueAt(Object value, int row, int col) {
                       //On interdit la modification sur certaine colonne !
                       if(!this.getColumnName(col).equals("Age") && !this.getColumnName(col).equals("Suppression"))
                               this.data[row][col] = value;
               }
                               
               /**
               * Retourne la classe de la donnée de la colonne
               * @param col
               */
               public Class getColumnClass(int col){
                       //On retourne le type de la cellule à la colonne demandée
                       //On se moque de la ligne puisque les données sur chaque ligne sont les mêmes
                       //On choisit donc la première ligne
                       return this.data[0][col].getClass();
               }
 
               /**
                * Méthode permettant de retirer une ligne du tableau
                * @param position
                */
               public void removeRow(int position){
                       
                       int indice = 0, indice2 = 0, nbRow = this.getRowCount()-1, nbCol = this.getColumnCount();
                       Object temp[][] = new Object[nbRow][nbCol];
                       
                       for(Object[] value : this.data){
                               if(indice != position){
                                      temp[indice2++] = value;
                               }
                               System.out.println("Indice = " + indice);
                               indice++;
                       }
                       this.data = temp;
                       temp = null;
                       //Cette méthode permet d'avertir le tableau que les données ont été modifiées
                       //Ce qui permet une mise à jours complète du tableau
                       this.fireTableDataChanged();
               }
               
               /**
                * Permet d'ajouter une ligne dans le tableau
                * @param data
                */
               public void addRow(Object[] data){
                       int indice = 0, nbRow = this.getRowCount(), nbCol = this.getColumnCount();
                       
                       Object temp[][] = this.data;
                       this.data = new Object[nbRow+1][nbCol];
                       
                       for(Object[] value : temp)
                               this.data[indice++] = value;
                       
                               
                       this.data[indice] = data;
                       temp = null;
                       //Cette méthode permet d'avertir le tableau que les données ont été modifiées
                       //Ce qui permet une mise à jours complète du tableau
                       this.fireTableDataChanged();
               }
               
               
               public boolean isCellEditable(int row, int col){
                       return true;
               }
        }
 
        
        class MoreListener implements ActionListener{
               public void actionPerformed(ActionEvent event) {
                       Object[] donnee = new Object[]{"Angelo", "Rennais", comboData[0], new Boolean(false), supp};
                       ((ZModel)tableau.getModel()).addRow(donnee);
               }
        }
               
        public static void main(String[] args){
               Fenetre fen = new Fenetre();
               fen.setVisible(true);
        }
}


ButtonRenderer.java



Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
import java.awt.Component;
 
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
 
public class ButtonRenderer extends JButton implements TableCellRenderer{
 
        public Component getTableCellRendererComponent(       JTable table, Object value,
                                                                                                  boolean isSelected, boolean isFocus,
                                                                                                  int row, int col) {
               //On écrit dans le bouton avec la valeur de la cellule
               setText((value != null) ? value.toString() : "");
               //on retourne notre bouton
               return this;
        }
}


ButtonEditor.java



Code : Java -
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class ButtonEditor extends DefaultCellEditor {
          
        protected JButton button;
        private ButtonListener bListener = new ButtonListener();
        
        /**
         * Constructeur avec une checkBox
         * @param checkBox
         * @param count
         */
        public ButtonEditor(JCheckBox checkBox) {
               //Par défaut, ce type d'objet travaille avec un JCheckBox
               super(checkBox);
            //On crée à nouveau notre bouton
               button = new JButton();
            button.setOpaque(true);
            //On lui attribue un listener
            button.addActionListener(bListener);
        }
 
        public Component getTableCellEditorComponent(JTable table, Object value,
                           boolean isSelected, int row, int column) { 
               //On définit le numéro de lignes à notre listener
               bListener.setRow(row);
               //Idem pour le numéro de colonnes
               bListener.setColumn(column);
               //On passe aussi le tableau pour des actions potentielles
               bListener.setTable(table);
               //On réaffecte le libellé au bouton
               button.setText( (value ==null) ? "" : value.toString() );
               //On renvoie le bouton
            return button;
        }
        
        /**
         * Notre listener pour le bouton
         * @author CHerby
         *
         */
        class ButtonListener implements ActionListener{
          
          private int column, row;
          private JTable table;
          private int nbre = 0;
          private JButton button;
          
          public void setColumn(int col){this.column = col;}
          public void setRow(int row){this.row = row;}
          public void setTable(JTable table){this.table = table;}
          public JButton getButton(){return this.button;}
          
          public void actionPerformed(ActionEvent event) {
               //On affiche un Zoli message mais vous pourriez faire les traitements que vous voulez
               System.out.println("coucou du bouton : " + ((JButton)event.getSource()).getText() );
               //On affecte un nouveau libellé à une celulle de la ligne
               ((AbstractTableModel)table.getModel()).setValueAt("New Value " + (++nbre), this.row, (this.column -1));   
               //Permet de dire à notre tableau qu'une valeur a changé 
               //à l'emplacement déterminé par les valeur passée en paramètre
               ((AbstractTableModel)table.getModel()).fireTableCellUpdated(this.row, this.column - 1);
               this.button = ((JButton)event.getSource());
          }
        }
}


DeleteButtonEditor.java



Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
 
import javax.swing.DefaultCellEditor;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
 
public class DeleteButtonEditor extends DefaultCellEditor {
          
        protected JButton button;
        private DeleteButtonListener bListener = new DeleteButtonListener();
        
        /**
         * Constructeur avec une checkBox
         * @param checkBox
         * @param count
         */
        public DeleteButtonEditor(JCheckBox checkBox) {
               //Par défaut, ce type d'objet travaille avec un JCheckBox
               super(checkBox);
            //On crée à nouveau notre bouton
               button = new JButton();
            button.setOpaque(true);
            //On lui attribue un listener
            button.addActionListener(bListener);
        }
 
        public Component getTableCellEditorComponent(JTable table, Object value,
                           boolean isSelected, int row, int column) { 
               //On définit le numéro de lignes à notre listener
               bListener.setRow(row);
               //On passe aussi le tableau pour des actions potentielles
               bListener.setTable(table);
               //On réaffecte le libellé au bouton
               button.setText( (value ==null) ? "" : value.toString() );
               //On renvoie le bouton
            return button;
        }
        
        /**
         * Notre listener pour le bouton
         * @author CHerby
         *
         */
        class DeleteButtonListener implements ActionListener{
                 
                 private int row;
                 private JTable table;
                 
                 public void setRow(int row){this.row = row;}
                 public void setTable(JTable table){this.table = table;}
                 
                 public void actionPerformed(ActionEvent event) {
                       if(table.getRowCount() > 0){
                               //On affiche un Zoli message mais vous pourriez faire les traitements que vous voulez
                               System.out.println("coucou du bouton : " + ((JButton)event.getSource()).getText() );
                               //On affecte un nouveau libellé à une celulle de la ligne
                               ((ZModel)table.getModel()).removeRow(this.row);
                               
                       }
                 }
          }               
        }


ComboRenderer.java



Code : Java -
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.awt.Component;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.table.TableCellRenderer;
 
 
public class ComboRenderer extends JComboBox implements TableCellRenderer {
 
        public Component getTableCellRendererComponent(JTable table, Object value,
                       boolean isSelected, boolean isFocus, int row, int col) {
               
               this.addItem("Très bien");
               this.addItem("Bien");
               this.addItem("Mal");
               return this;
        }       
}


C'est assez simple finalement, mais c'est tout de même assez galère lorsqu'on ne sait pas par où commencer !

Maintenant que vous savez faire tout ceci, ajouter ou retirer des colonnes ne devrait pas vous faire sourciller...
Nous allons toujours utiliser notre modèle de rendu afin d'ajouter ou retirer des colonnes.
Cette fois, j'ai utilisé des menus contextuels afin de faire ceci : faut bien faire varier les plaisirs ! C'est surtout que, comme ça, vous verrez le rapprochement entre les arbres et les tableaux.

Voilà ce que j'ai obtenu :
avant effacement d'une colonne


après effacement d'une colonne

après ajout d'une colonne nommée "Ville"


Pour avoir ce rendu, j'ai refait une classe épurée... On commençait à ne plus s'y retrouver...

Voici le code de cette classe :

Code : Java -
import java.awt.BorderLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
 
import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
 
 
public class TableFrame extends JFrame {
        private JTable tableau;
        private String supp = "Supprimer la ligne";
        
        public TableFrame(){
               this.setLocationRelativeTo(null);
               this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
               this.setTitle("JTable");
               this.setSize(300, 120);
               this.createContent();
        }
        
        private void createContent(){
               Object[][] data = {    {"Cysboy", "6boy", "Homme"},
                                                     {"BZHHydde", "BZH", "Homme"},
                                                     {"IamBow", "BoW", "Femme"},
                                                     {"FunMan", "Year", "Homme"}
               };
 
               String  title[] = {"Pseudo", "Age", "sexe"};
               this.tableau = new JTable(new DefaultTableModel(data, title));
               this.tableau.getTableHeader().addMouseListener(new MouseAdapter(){
                       public void mouseReleased(MouseEvent event){
                               if(event.getButton() == event.BUTTON3){
                                      //Si on est dans le tableau
                                      if(tableau.columnAtPoint(new Point(event.getX(), event.getY())) != -1){
                                              
                                              //ajouter une colonne
                                              JMenuItem ajouter = new JMenuItem("Ajouter une colonne");
                                              ajouter.addActionListener(new AddListener());
                                              
                                              //Le menu effacer
                                              JMenuItem erase = new JMenuItem("Effacer cette colonne");
                                              erase.addActionListener(new EraseListener(new Point(event.getX(), event.getY())));
 
                                              //Ajout du menu contextuel
                                              JPopupMenu menu = new JPopupMenu();
                                              menu.add(ajouter);
                                              menu.add(erase);
                                              menu.show(tableau, event.getX(), event.getY());
                                      }
                               }
                       }
               });
               this.getContentPane().add(new JScrollPane(tableau), BorderLayout.CENTER);
        }              
 
        /**
         * Classe gérant la suppression d'une colonne
         * @author CHerby
         */
        class EraseListener implements ActionListener{
               private Point point = null;
               private Object[][] data;
               private Object[] title;
               public EraseListener(Point col){
                       this.point = col;
               }
               public void actionPerformed(ActionEvent event) {
                       //On récupère l'indice de la colonne
                       int col = tableau.columnAtPoint(this.point);
                       //On en déduit la colonne sur laquelle on se trouve
                       TableColumn column = tableau.getColumn(tableau.getColumnName(col));
                       
                       //On avertit le modèle que le tableau a perdu une colonne
                       initNewData(column);
                       //On donne les nouvelles données au modèle
                       ((DefaultTableModel)tableau.getModel()).setDataVector(this.data, this.title);
                       
               }
               /**
                * Méthode qui génère un nouveau contenu au modèle
                * @param column
                * @return
                */
               private void initNewData(TableColumn column){
                       this.data = new Object[tableau.getRowCount()][tableau.getColumnCount()-1];
                       this.title = new Object[tableau.getColumnCount()-1];
                       
                       //On parcourt toutes les lignes
                       for(int i = 0; i < tableau.getRowCount(); i++){
                               
                               //Toutes les colonnes
                               for(int j = 0, k = 0; j < tableau.getColumnCount(); j++){
                                      //Si la colonne concernée n'est pas celle à effacer
                                       if(!((DefaultTableModel)tableau.getModel()).getColumnName(j).equals(column.getHeaderValue())){
                                              
                                              //On récupère les titres de colonnes au premier passage
                                              if(i == 0)this.title[k] = ((DefaultTableModel)tableau.getModel()).getColumnName(j);
                                              //on récupère les données
                                              this.data[i][k] = tableau.getValueAt(i, j);
                                              k++;
                                      }
                               }
                       }
               }
        }
        
        /**
         * Permet d'ajouter une colonne au tableau, enfin, au modèle du tableau
         * @author CHerby
         */
        class AddListener implements ActionListener{
               public void actionPerformed(ActionEvent event) {
                       //On affiche une pop-up
                       JOptionPane jop = new JOptionPane(), jop2 = new JOptionPane();
                       String nom = jop.showInputDialog(null, "Saisissez le nom de la nouvelle colonne", "Ajout d'une colonne", JOptionPane.QUESTION_MESSAGE);
                       //Si les contrôles d'usage sont bons
                       if(nom != null){
                               if(!nom.trim().equals("")){
                                      //On ajoute une colonne au modèle
                                      ((DefaultTableModel)tableau.getModel()).addColumn(nom);                                              
                               }
                       }
               }
        }
        
        
        public static void main(String[] args){
               TableFrame fen = new TableFrame();
               fen.setVisible(true);
        }
}


Voilà un chapitre rondement mené.
Je crois qu'il est temps de faire un topo.

Ce qu'il faut retenir

  • Un tableau est en fait un composant appelé JTable.
  • Celui-ci prend en paramètre un tableau d'objets à deux dimensions (les données) et un tableau de chaînes de caractères (le titre des colonnes).
  • Vous pouvez utiliser, afin de gérer vous-mêmes le contenu du tableau, un modèle de données (TableModel).
  • Afin de pouvoir ajouter ou retirer des lignes dans un tableau, il faut passer par un modèle de données. Ainsi, l'affichage est mis à jour automatiquement.
  • Il en va de même pour l'ajout et la suppression de colonnes.
  • La gestion de l'affichage brut (hors édition) des cellules peut se gérer colonne par colonne en utilisant une classe dérivant de TableCellRenderer.
  • La gestion de l'affichage brut lors de l'édition d'une cellule peut se gérer colonne par colonne en utilisant une classe dérivant de DefaultCellEditor.
  • Pour un tableau contenant plusieurs types de données, il peut être préférable de gérer entièrement la façon dont le tableau traite ses cellules.


Une bonne chose de faite...

Je vous avais dit que ce chapitre allait être assez complexe, comme le précédent !
Vous vous en êtes sortis indemnes, ou presque...


0 comments to "LECON 316"

Post a Comment

Powered by Blogger.

About This Blog

Aller au debut de la page