2016-08-25 7 views
2

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; 
    } 
} 

Antwort

2

machen eine Auswahl aus der JComboBox in der rechten Spalte und beobachten Sie es Sie ignorieren

Etwas ist falsch mit Ihrem benutzerdefinierten Editor und ich bin mir nicht sicher was. Sie haben ein großes Problem darin, dass Sie versuchen, eine JComboBox als Daten des Editors zu verwenden. Das ist völlig falsch.

Aber die gute neue ist, dass Sie keinen benutzerdefinierten Renderer oder einen benutzerdefinierten Editor verwenden müssen.

Sie sollten keine JComboBox im TableModel speichern. Sie speichern einfach die Zeichenfolge des ausgewählten Elements aus dem Kombinationsfeld. (Dies wird automatisch vom Standard-Kombinationsfeld-Editor für Sie erledigt).

Sie müssen keinen neuen Editor für jede Datei erstellen, die in die Tabelle gezogen wird.

die zweite enthalten eine JComboBox der Optionen hängen von dem Inhalt der Datei gezogen

So ist der einzige Teil der Tabelle, die Sie anpassen müssen, ist die getCellEditor(...) Methode.

Ich würde vermuten, dass Sie einen anderen Editor für eine bestimmte Dateierweiterung hätten.

Also der grundlegende Code könnte so etwas wie sein:

int modelColumn = convertColumnIndexToModel(column); 

if (modelColumn == 1) 
{ 
    String file = getModel.getValueAt(row, 0); 

    if (file.endsWith(".txt")) 
     return txtEditor; 
    else if (file.endsWith(".html")) 
     return htmlEditor; 
} 

return super.getCellEditor(row, column); 

Check out: How to add unique JComboBoxes to a column in a JTable (Java) für ein funktionierendes Beispiel.Die Logik in diesem Beitrag enthält nur zu Demonstrationszwecken einen separaten Editor nach Zeilen. Das Beispiel zeigt, dass der Code mit den Standard-Renderern und -Redakteuren funktioniert. Sie müssen nur die Elemente für jeden Kombinationsfeld-Editor angeben.

In Ihrem Fall wird der Editor auf dem Dateityp basieren, so dass die Logik die Daten in der ersten Spalte testen muss.

Hinweis: Die geschachtelte if/else-Anweisung ist keine gute Lösung. Vielleicht möchten Sie eine Hashmap von Dateityp/Editor verwenden. Dann wäre die Methode getCellEditor (...) nur eine Hashmap-Suche, sobald Sie den Dateityp für die Datei extrahiert haben.

Ihr Drag Code sollte also nichts mit den Editoren der Tabelle zu tun haben. Sie müssen vorher wissen, welche Dateitypen Sie unterstützen möchten und die gültigen Elemente für jeden dieser Dateitypen definieren.

Außerdem sollte Ihr TableModel nicht DefaultTableModel erweitern. Sie stellen Ihren eigenen Datenspeicher zur Verfügung und implementieren alle Methoden, so dass Sie nur das AbstractTableModel erweitern sollten.

+0

@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

+0

@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

+0

@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

Verwandte Themen