cs_DARKSIDIOUS
Messages postés
15814
Date d'inscription
jeudi 8 août 2002
Statut
Membre
Dernière intervention
4 mars 2013
130
18 juil. 2007 à 22:08
Allez, c'est partit pour un petit topo sur le JTable pour te montrer comment faire pour qu'une colonne de la JTable affiche des combobox différent pour chaque ligne :
Je me baserai bien évidement sur mon JPropertyTable afin qu'on ai la même longueur d'onde.
On commence déjà par définir un CellRenderer qui permettra d'afficher les cellules avec un combo : un CellRenderer est l'objet utilisé par le JTable pour représenter les données :
public class CEnumRenderer implements TableCellRenderer {
private JComboBox m_cmbText; // le combo utilisé pour représenter les données
private JPanel m_panControl; // le panel de la cellule
public CEnumRenderer(Vector<String> oValue) { // Constructeur : on remplit le combo avec les données qu'on lui envoie
super();
m_panControl = new JPanel();
m_panControl.setLayout(new BorderLayout());
m_cmbText = new JComboBox(oValue);
m_panControl.add(m_cmbText, BorderLayout.CENTER);
}
public Component getTableCellRendererComponent(JTable arg0, Object oValue, boolean arg2, boolean arg3, int arg4, int arg5) { // Implémentation de la fonction : on renvoie le panel contenant le combo
m_cmbText.setSelectedIndex((Integer) oValue);
return m_panControl;
}
}
Ensuite, on utilise un CellEditor pour représenter l'éditeur des valeurs des cellules
public class CEnumEditor extends AbstractCellEditor implements TableCellEditor {
private JComboBox m_cmbText; // le combo utilisé pour modifier les valeurs
private JPanel m_panControl; // le panel de la cellule
public CEnumEditor(Vector<String> oValues) { // Constructeur : on initialise le Combo avec les données qu'on lui envoie
super();
m_panControl = new JPanel(new BorderLayout());
m_cmbText = new JComboBox(oValues);
m_panControl.add(m_cmbText, BorderLayout.CENTER);
}
public Component getTableCellEditorComponent(JTable oTable, Object oValue, boolean arg2, int iRow, int iColumn) { // Redéfinition de la fonction : renvoie le contrôle qui représente la cellule : ici, le panel contenant le combo, en oubliant pas de sélectionner la bonne valeur dans le combo (ici des entiers, puisque à l'origine, c'est l'index qu'on lui envoie)
m_cmbText.setSelectedIndex((Integer) oValue);
return m_panControl;
}
public Object getCellEditorValue() { // Redéfinition de la fonction : permet au JTable de récupèrer la valeur de la cellule après modification : ici l'index du combo
return m_cmbText.getSelectedIndex();
}
}
Enfin, la classe la plus importante : le Model de la table qui permet de stocker les données de la table, à ce niveau, on ne sait pas quel représentation les données vont avoir (d'ailleurs on s'en fiche !). C'est par cette classe que tu peux définir comment tu veux utiliser tes données en entrées : un Vector, un tableau, une liste, etc. Perso, j'utilise un Vector (c'est vrai, je ferais mieux d'utiliser un table de hash !) :
public class CCustomTableModel extends AbstractTableModel {
private Vector<String> m_vColumnNames; // Nom des colonnes
private Vector<CCustomCellData> m_vData; // Données à afficher dans les colonnes
private Vector<String> m_vRowName; // Nom des lignes (ici dans mon exemple car chaque ligne à une label)
private int m_iColumnCount; // Nombre de colonnes
private int m_iRowCount; // Nombre de lignes
public CCustomTableModel() {
m_vColumnNames = new Vector<String>();
m_vData = new Vector<CCustomCellData>();
m_vRowName = new Vector<String>();
}
private void resize() { // fonction permettant de redéfinir la taille des données (car j'utilise un Vector => plus efficace de lui spécifier la taille du Vector)
m_vColumnNames.setSize(m_iColumnCount);
m_vData.setSize(m_iRowCount * m_iColumnCount);
m_vRowName.setSize(m_iRowCount);
for (int i = 0; i < m_iRowCount; ++i) {
m_vRowName.setElementAt(new String(), i);
for (int j = 0; j < m_iColumnCount; ++j) {
CCustomCellData data = new CCustomCellData(EDataTypes.dataTypeString);
m_vData.setElementAt(data, i * m_iColumnCount + j);
}
}
}
public int getColumnCount() {
return m_iColumnCount;
}
public void setColumnCount(int iColumnCount) {
m_iColumnCount = iColumnCount;
resize();
}
public int getRowCount() {
return m_iRowCount;
}
public void setRowCount(int iRowCount) {
m_iRowCount = iRowCount;
resize();
}
public void setRowName(String sRowName, int iRowIndex) { // facultatif : ici les labels de chaque ligne de mon propertytable
m_vRowName.setElementAt(sRowName, iRowIndex);
}
public String getRowName(int iRowIndex) {
return m_vRowName.get(iRowIndex);
}
public String getColumnName(int iColumnIndex) { // Nom de la colonne
return m_vColumnNames.get(iColumnIndex);
}
public void setColumnName(int iColumnIndex, String sColumnName) {
m_vColumnNames.setElementAt(sColumnName, iColumnIndex);
}
public Object getValueAt(int iRowIndex, int iColumnIndex) {
return m_vData.get(iRowIndex * m_iColumnCount + iColumnIndex).getValue();
}
public CCustomCellData getValueAtName(String sRowName) {
int rowIndex = 0;
for (String s : m_vRowName) {
if (s.compareTo(sRowName) == 0) {
return m_vData.get(rowIndex * m_iColumnCount + 1);
}
rowIndex++;
}
return null;
}
public Class getColumnClass(int iColumnIndex) { // Permet de définir quelle classe est utilisé pour la colonne (pratique si toute une colonne utilise le même type de classe, par exemple, des booléens)
try {
return (getValueAt(0, iColumnIndex).getClass());
}
catch (Exception e) {
return new String().getClass();
}
}
public boolean isCellEditable(int row, int col) { // est-ce que la cellule à la coordonnées (row, col) est editable ?
return m_vData.get(row * m_iColumnCount + col).getEditable();
}
public void setCustomValueAt(CCustomCellData oValue, int iRowIndex, int iColumnIndex) { // Me permet de définir la valeur d'une cellule
m_vData.setElementAt(oValue, iRowIndex * m_iColumnCount + iColumnIndex);
fireTableCellUpdated(iRowIndex, iColumnIndex);
}
public void setCustomValueAtName(CCustomCellData oValue, String sRowName) { // Me permet de définir la valeur de la propriété donné
int rowIndex = 0;
for (String s : m_vRowName) {
if (s.compareTo(sRowName) == 0) {
m_vData.setElementAt(oValue, rowIndex * m_iColumnCount + 1);
}
rowIndex++;
}
fireTableCellUpdated(rowIndex, 1);
}
public void setValueAt(Object oValue, int iRowIndex, int iColumnIndex) { // C'est là que je définie le type de Données que j'envoie à ma table
CCustomCellData data;
if (oValue instanceof Integer) {
try {
data = new CCustomCellData(EDataTypes.dataTypeInteger, (Integer) oValue, editable);
} catch (NumberFormatException e) {
data = new CCustomCellData(EDataTypes.dataTypeInteger, 0, editable);
}
}
else if (oValue instanceof String) {
// c'est une saisie directe dans la table => on modifie le type
switch (test.getDataType()) {
case dataTypeBoolean:
data = new CCustomCellData(EDataTypes.dataTypeBoolean, Boolean.getBoolean(oValue.toString()), editable);
break;
case dataTypeColor:
data = new CCustomCellData<Color>(EDataTypes.dataTypeColor, Color.decode(oValue.toString()), editable);
break;
case dataTypeFloat:
data = new CCustomCellData<Double>(EDataTypes.dataTypeFloat, Double.parseDouble(oValue.toString()), editable);
break;
case dataTypeInteger:
try {
data = new CCustomCellData(EDataTypes.dataTypeInteger, Integer.decode(oValue.toString()), editable);
} catch (NumberFormatException e) {
data = new CCustomCellData(EDataTypes.dataTypeInteger, 0, editable);
}
break;
case dataTypeFileName:
data = new CCustomCellData<String>(EDataTypes.dataTypeFileName, (String) oValue, editable);
break;
case dataTypeString:
default:
data = new CCustomCellData<String>(EDataTypes.dataTypeString, (String) oValue, editable);
break;
}
}
else if (oValue instanceof Double) {
data = new CCustomCellData<Double>(EDataTypes.dataTypeFloat, (Double) oValue, editable);
}
else if (oValue instanceof Boolean) {
data = new CCustomCellData(EDataTypes.dataTypeBoolean, (Boolean) oValue, editable);
}
else if (oValue instanceof Color) {
data = new CCustomCellData<Color>(EDataTypes.dataTypeColor, (Color) oValue, editable);
}
else if (oValue instanceof Date) {
data = new CCustomCellData<Date>(EDataTypes.dataTypeDate, (Date) oValue, editable);
}
else {
return;
}
m_vData.setElementAt(test, iRowIndex * m_iColumnCount + iColumnIndex);
fireTableCellUpdated(iRowIndex, iColumnIndex);
}
public void setValueAtName(Object Value, String sRowName) {
int rowIndex = 0;
for (String s : m_vRowName) {
if (s.compareTo(sRowName) == 0) {
setValueAt(Value, rowIndex - 1, m_iColumnCount + 1);
}
rowIndex++;
}
}
public void resetRows() {
m_iRowCount = 0;
m_vRowName.clear();
m_vData.clear();
}
}
Et enfin, ma classe principale qu'on utilise directement : celle qui étends de JTable :
public class JPropertyTable extends JTable {
CCustomRowEditor m_oRowEditor = new CCustomRowEditor(); // Là par contre, j'utilise une table de hash pour savoir que tel type de données est rattaché à tel type de Editor/Renderer !
private CCustomTableModel m_oModeleTable;
public void addPropertyRow(Integer iRowIndex, String sName, EDataTypes eType, Object oValue, Vector<String> oValues) { // Fonction principale : permet d'ajouter une ligne à la propertytable : selon le type que je lui envoie, elle sélectionne le type d'Editor/renderer approprié qu'elle ajoute dans la table de hash
// on rajoute la première colonne : le nom de la propriété
CCustomCellData<String> data = new CCustomCellData<String>();
m_oModeleTable.setRowName(sName, iRowIndex);
data.setDataType(EDataTypes.dataTypeString);
data.setEditable(false);
data.setValue(sName);
m_oModeleTable.setCustomValueAt(data, iRowIndex, 0);
// on rajoute la seconde colonne : la valeur de la propriété
CCustomCellData value;
TableCellEditor editor;
TableCellRenderer renderer;
switch (eType) {
case dataTypeBoolean:
value = new CCustomCellData();
editor = new CBooleanEditor();
renderer = new CBooleanRenderer();
break;
case dataTypeString:
value = new CCustomCellData<String>();
if (oValues != null) {
editor = new CStringEditorCombo(oValues);
renderer = new CStringRendererCombo(oValues);
}
else {
editor = new CStringEditor();
renderer = new CStringRenderer();
}
break;
case dataTypeInteger:
value = new CCustomCellData();
editor = new CIntegerEditor();
renderer = new CIntegerRenderer();
break;
case dataTypeColor:
value = new CCustomCellData<Color>();
editor = new CColorEditor();
renderer = new CColorRenderer();
break;
case dataTypeEnum:
value = new CCustomCellData();
editor = new CEnumEditor(oValues);
renderer = new CEnumRenderer(oValues);
break;
case dataTypeFileName:
value = new CCustomCellData<String>();
editor = new CFileNameEditor();
renderer = new CFileNameRenderer();
break;
case dataTypeDate:
value = new CCustomCellData<Date>();
editor = new CDateEditor();
renderer = new CDateRenderer();
break;
case dataTypeFloat:
value = new CCustomCellData<Double>();
editor = new CFloatEditor();
renderer = new CFloatRenderer();
break;
case dataTypeHour:
value = new CCustomCellData<Time>();
editor = new CHourEditor();
renderer = new CHourRenderer();
break;
default:
value = new CCustomCellData<String>();
editor = new CStringEditor();
renderer = new CStringRenderer();
break;
}
value.setDataType(eType);
value.setEditable(true);
value.setValue(oValue);
m_oModeleTable.setCustomValueAt(value, iRowIndex, 1); // ajout de la donnée dans le model de la table
m_oRowEditor.addEditorForRow(iRowIndex, editor); // ajout de l'editor pour cette ligne
m_oRowEditor.addRendererForRow(iRowIndex, renderer); // ajout du renderer pour cette ligne
}
public TableCellEditor getCellEditor (int row, int col)
{ // Redéfinition de la fonction pour utiliser le bon editor : dans ton cas, le combo !
TableCellEditor editor = null;
if (m_oRowEditor != null) {
editor = m_oRowEditor.getEditor(row);
return editor;
}
else {
return super.getCellEditor(row,col);
}
}
public TableCellRenderer getCellRenderer (int row, int column) { // Redéfinition de la fonction pour utiliser le bon renderer : dans ton cas, le combo !
if (isCellEditable(row, column)) {
TableCellRenderer renderer = null;
if (m_oRowEditor != null) {
renderer = m_oRowEditor.getRenderer(row);
}
if (renderer != null) {
return renderer;
}
else {
return super.getCellRenderer(row, column);
}
}
return super.getCellRenderer(row, column);
}
}
Voilà grosso-modo ce qu'il faut retenir. Mon architecture n'est pas forcément la plus simple, je l'avoue, mais elle est assez efficace pour ce que j'en fait.
Donc à retenir :
Faire un CellEditor et CellRenderer, puis faire une classe qui étend de JTable en redéfinissant les fonctions getCellEditor et getCellRenderer pour lui appliquer l'editor et le renderer que tu veux.
Ensuite, utilise un TableModel perso pour avoir plus de liberté quand à la représentation de tes données pour la JTable.
Je pense que le JTable (avec le JTree) est certainement le composant le plus complexe à utiliser ! On est très loin du ListView de VB qui ne fait qu'afficher les données sous forme de liste, là on a un vrai contrôle personnalisable très simplement qui répond au modèle MVC.