So einfach wie Renderers und Editoren klingen und trotz der Dutzend oder so SO Lesezeichen, die ich zu ähnlichen Themen zurückverweise, fehlt mir etwas elementares. Ich möchte jede alte Textdatei in eine JTable mit zwei Spalten ziehen, die erste Spalte enthält den Dateinamen und die zweite enthält eine JComboBox, deren Optionen vom Inhalt der gezogenen Datei abhängen. (Im Code unten fälsche ich nur ein paar Einträge.)JComboBox in JTable zeigt nicht die Auswahl an
Das alles funktioniert gut, bis ich eine Auswahl aus einem Kombinationsfeld treffen - die Auswahl wird nicht angezeigt - nur ein Kombinationsfeld, korrekt ausgefüllt, aber keine Auswahl getroffen. Ich weiß, dass es etwas mit meinem Missbrauch von Renderern/Redakteuren zu tun haben muss, aber nach mindestens zwei Wochen des Dreschens suche ich professionelle Hilfe. Und wenn Sie denken, dass ich das Boot total vermisst habe, wie Renderer und Redakteure geschrieben sind, dann bin ich froh, dass Sie meine früheren Versuche nicht gesehen haben.
Hoffentlich qualifiziert sich dieser Code als SSCCE - aufrichtige Entschuldigung, wenn ich etwas enthalten habe, das ich nicht haben sollte. Ich habe das DnD-Material behalten, nur für den Fall, dass es eine Bedeutung hat.
Für was es wert ist, verwende ich eine statische Liste von ComboBoxModels (eins pro Zeile), da jede JComboBox verschiedene Optionen enthält, und ebenso TableCellEditors (obwohl ich nicht weiß, ob das der richtige Weg ist).
Um dies auszuführen, ziehen Sie einfach eine Datei in die erscheinende Tabelle und treffen Sie eine Auswahl aus der JComboBox in der rechten Spalte und beobachten Sie, wie Sie ignoriert werden. Vielen Dank, auch wenn Sie einige Ratschläge haben, ohne sich darum zu kümmern.
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 - Ich vermutete, dass ich um eine Meile war - sehr geschätzt. Ich hatte den Code für die Überprüfung der Gültigkeit der gelöschten Elemente für den SSCCE entfernt - meine Live-Version enthält das. Es handelt sich um tabstoppgetrennte Dateien, deren erste Zeile eine Reihe von Spaltenüberschriften enthält, die die Kombinationsoptionen bilden. Ich hatte das convertColumnIndexToModel eingeschlossen, aber dann entfernt, da das Datenmodell und die Tabelle übereinstimmen, obwohl ich das möglicherweise auch falsch verstehe. Wie auch immer, heute arbeitslos, aber ich werde diesen Abend all diesen Informationen widmen - vielen Dank. – regger
@regger, 'Ich habe das convertColumnIndexToModel eingeschlossen, aber dann entfernt, da das Datenmodell und die Tabelle übereinstimmen '- der Benutzer kann die Spalten immer neu anordnen, indem er sie an eine neue Position zieht (es sei denn, Sie haben die Spaltenneuordnung im Tabellenheader deaktiviert)). – camickr
@camrickr - Ich wollte nur sagen, dass ich sehr viel gesagt habe und vielen Dank an euch, dass es jetzt funktioniert. Es ist nur eine Frage des vollständigen Verständnisses Ihrer Erklärungen, um es vollständig zu verstehen. Die Erweiterung von DefaultTableModel war für mich allerdings kein Schlüssel. Immer noch nicht klar, wann benutzerdefinierte Renderer notwendig sind, aber das ist hier nicht wichtig. Herzlichen Dank. – regger