Aussi simple que les Renderers et les éditeurs sonnent et malgré la douzaine de signets SO je reviens à des problèmes similaires, il me manque quelque chose d'élémentaire. Je veux faire glisser n'importe quel ancien fichier texte dans une JTable à deux colonnes, avoir la première colonne afficher le nom de fichier et la seconde contenir un JComboBox dont les options dépendent du contenu du fichier glissé. (Dans le code ci-dessous, je simule juste quelques entrées.)JComboBox dans JTable n'affiche pas la sélection
Tout cela fonctionne très bien jusqu'à ce que je fasse une sélection à partir d'une zone de liste déroulante - la sélection ne s'affiche pas - juste une liste déroulante, rempli correctement, mais aucune sélection faite. Je sais que cela doit avoir quelque chose à voir avec mon abus de créateurs/éditeurs, mais après au moins deux semaines d'agitation, je cherche de l'aide professionnelle. Et si vous pensez que j'ai complètement raté le bateau sur la façon dont les moteurs de rendu et les éditeurs sont écrits, eh bien, je suis content que vous n'ayez pas vu mes tentatives précédentes.
Espérons que ce code se qualifie comme un SSCCE - sincères excuses si j'ai inclus quelque chose que je ne devrais pas avoir. J'ai gardé les trucs de DnD juste au cas où ça aurait une signification.
Pour ce que ça vaut, j'utilise une liste statique de ComboBoxModels (un par ligne) puisque chaque JComboBox contient différentes options, et également TableCellEditors (bien que je ne sache pas si c'est la bonne façon de procéder).
Pour l'exécuter, faites simplement glisser n'importe quel fichier dans la table qui apparaît, puis faites une sélection à partir du JComboBox dans la colonne de droite et regardez-le vous ignorer. Merci beaucoup, même si vous avez un conseil sans prendre la peine de le faire.
Java 1.7/OS X 10.9.5/Eclipse Mars.2
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.AbstractCellEditor;
import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.MutableComboBoxModel;
import javax.swing.SwingUtilities;
import javax.swing.TransferHandler;
import javax.swing.event.ListDataListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;
public class Main extends JFrame {
static List<AComboBoxModel> priceComboModels = new ArrayList<AComboBoxModel>();
static List<DefaultCellEditor> editors = new ArrayList<DefaultCellEditor>();
public Main() {
setLayout(new BorderLayout());
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setPreferredSize(new Dimension(500, 400));
JPanel panel = new JPanel(new BorderLayout());
JTable table = new JTable(0, 2) {
public TableCellEditor getCellEditor(int rinx, int cinx) {
if (cinx == 0) {
return super.getCellEditor(rinx, cinx);
}
return editors.get(rinx);
}
};
table.setPreferredScrollableViewportSize(new Dimension(360, 80));
table.setTransferHandler(new ATransferHandler());
table.setModel(new ATableModel());
TableColumnModel tcm = table.getColumnModel();
tcm.getColumn(0).setHeaderValue("File Name");
tcm.getColumn(1).setHeaderValue("Selection");
TableColumn column = tcm.getColumn(1);
column.setCellRenderer(new ACellRenderer());
column.setCellEditor(new ACellEditor());
table.setDragEnabled(true);
table.setFillsViewportHeight(true);
JScrollPane sp = new JScrollPane(
table,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
);
panel.add(sp, BorderLayout.CENTER);
panel.setPreferredSize(new Dimension(200, 300));
add(panel, BorderLayout.CENTER);
pack();
}
public static int addComboModel(AComboBoxModel model) {
priceComboModels.add(model);
return priceComboModels.size() - 1;
}
public static AComboBoxModel getComboModelAt(int inx) {
return priceComboModels.get(inx);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Main().setVisible(true);
}
});
}
}
class ATableModel extends DefaultTableModel {
List<ARecord> data = new ArrayList<ARecord>();
public void addRow(ARecord row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
@Override
public int getRowCount() {
return data == null ? 0 : data.size();
}
@Override
public int getColumnCount() {
return 2;
}
public void setValueAt(Object value, int rinx, int cinx) {
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
row.setFilename((String) value);
break;
case 1:
row.setCbox((JComboBox) value);
break;
}
}
@Override
public Object getValueAt(int rinx, int cinx) {
Object returnValue = null;
ARecord row = data.get(rinx);
switch (cinx) {
case 0:
returnValue = row.getFilename();
break;
case 1:
returnValue = row.getCbox();
break;
}
return returnValue;
}
// I assume this is unnecessary since column 1 defaults to text
// and column 2 is handled by ACellRenderer. I think.
// @Override
// public Class getColumnClass(int cinx) {
// return cinx == 0 ? String.class : JComboBox.class;
// }
}
//////////////////////////////////////////////////////////////////////////////////
// This class handles the drag and drop.
class ATransferHandler extends TransferHandler {
int getSourceActions(JList<String> lst) {
return TransferHandler.COPY;
}
Transferable createTransferable(JList<String> list) {
return null;
}
void exportDone(JList<String> lst, Transferable data, int action) {
}
public boolean canImport(TransferHandler.TransferSupport info) {
return true;
}
//////////////////////////////////////////////////////////////////////////
// This is the method of interest where the dropped text file is handled.
//////////////////////////////////////////////////////////////////////////
public boolean importData(TransferHandler.TransferSupport info) {
if (! info.isDrop()) return false;
JTable table = (JTable)info.getComponent();
Transferable tr = info.getTransferable();
List<File> files = null;
try {
files = (List<File>)tr.getTransferData(DataFlavor.javaFileListFlavor);
} catch(UnsupportedFlavorException | IOException e) {
}
ATableModel tm = (ATableModel)table.getModel();
String[] options;
// For each dropped text file...
for (File fl : files) {
String fname = fl.getName();
// Just fill the JComboBox with some unique options for now
// (in practice this comes from the dropped text file contents).
String dummyText = fname.substring(0, 5);
options = new String[] { dummyText + "_A", dummyText + "_B", dummyText + "_C" };
// Create a ComboBoxModel for this JComboBox containing the selection options.
AComboBoxModel cboModel = new AComboBoxModel(options);
// Create the combo box itself.
JComboBox<String> cbox = new JComboBox<String>();
// Assign the model to the box.
cbox.setModel(cboModel);
// Create and add to the editor list the table cell editor.
Main.editors.add(new DefaultCellEditor(cbox));
// Also add the ComboBoxModel to the model list.
Main.addComboModel(cboModel);
// Add the row to the model data.
tm.addRow(new ARecord(fname, cbox));
}
return true;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
class ARecord {
String filename;
JComboBox cbox;
// Just a bean to describe a table row (a filename and a JComboBox).
public ARecord(String filename, JComboBox cbox) {
super();
this.filename = filename;
this.cbox = cbox;
}
public String getFilename() {
return filename;
}
public void setFilename(String filename) {
this.filename = filename;
}
public JComboBox getCbox() {
return cbox;
}
public void setCbox(JComboBox cbox) {
this.cbox = cbox;
}
}
///////////////////////////////////////////////////////////////////////////////////////////
// This is the model for the JComboBoxes. A different model is instantiated
// for each row since each one has different contents.
class AComboBoxModel implements MutableComboBoxModel {
List<String> items = new ArrayList<String>();
public AComboBoxModel(String[] items) {
this.items = Arrays.asList(items);
}
@Override
public int getSize() {
return items.size();
}
@Override
public Object getElementAt(int index) {
return items.get(index);
}
@Override
public void addListDataListener(ListDataListener l) {
}
@Override
public void removeListDataListener(ListDataListener l) {
}
@Override
public void setSelectedItem(Object anItem) {
}
@Override
public Object getSelectedItem() {
return null;
}
@Override
public void addElement(Object item) {
}
@Override
public void removeElement(Object obj) {
}
@Override
public void insertElementAt(Object item, int index) {
}
@Override
public void removeElementAt(int index) {
}
}
//////////////////////////////////////////////////////////////////////////////////////
// I won't pretend that I'm confident as to how this should work. My guess is that
// I should just retrieve the appropriate ComboBoxModel, assign it and return.
class ACellRenderer extends JComboBox implements TableCellRenderer {
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus,
int rinx, int cinx) {
setModel(Main.getComboModelAt(rinx));
return this;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
class ACellEditor extends AbstractCellEditor implements TableCellEditor {
static JComboBox box = null;
// This is where I think I'm actually lost. I don't understand the significance of
// returning a JComboBox when one was already created when the text file was
// dropped. Is it correct to just assign the appropriate ComboBoxModel to a JComboBox
// and return it here?
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int rinx,
int cinx) {
box = (JComboBox)(table.getModel().getValueAt(rinx, cinx));
box.setModel(Main.getComboModelAt(rinx));
return box;
}
@Override
public Object getCellEditorValue() {
return box;
}
}
@camrickr - Je me doutais que j'étais loin d'un mile - très apprécié. J'avais enlevé le code pour vérifier la validité des objets perdus pour le SSCCE - ma version en direct l'inclut. Ils seront des fichiers délimités par des tabulations dont la première rangée est une série d'en-têtes de colonnes qui composeront les options de combo. J'avais inclus le convertColumnIndexToModel mais l'ai enlevé puisque le modèle et la table de données concordent, bien que j'aie pu mal comprendre cela aussi. Quoi qu'il en soit, au chômage aujourd'hui, mais consacrera cette soirée à appliquer toutes ces informations - merci beaucoup. – regger
@regger, "J'ai inclus le convertColumnIndexToModel mais je l'ai supprimé car le modèle de données et la table correspondent" - l'utilisateur peut toujours réorganiser les colonnes en les faisant glisser vers une nouvelle position (sauf si vous avez désactivé le réordonnancement des colonnes). – camickr
@camrickr - Je voulais juste dire que cela m'a mis énormément et beaucoup merci à vous cela fonctionne maintenant. C'est juste une question de donner un sens complet à vos explications pour le comprendre pleinement. Ne pas étendre DefaultTableModel était une clé pour moi cependant. Toujours pas clair sur quand les rendus personnalisés sont nécessaires mais ce n'est pas important ici. Infiniment reconnaissant. – regger