2016-08-25 1 views
2

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

Répondre

2

faire une sélection de la JComboBox dans la colonne de droite et regardez-vous ignorer

Quelque chose ne va avec votre éditeur personnalisé et je ne sais pas quoi. Vous avez un gros problème en essayant d'utiliser un JComboBox comme données de l'éditeur. C'est complètement faux.

Mais la bonne nouvelle est que vous n'avez pas besoin d'utiliser un moteur de rendu personnalisé ou un éditeur personnalisé.

Vous ne devriez PAS stocker un JComboBox dans le TableModel. Vous stockez simplement la chaîne de l'élément sélectionné dans la zone de liste déroulante. (Cela sera fait pour vous automatiquement par l'éditeur de boîte combo par défaut).

Vous n'avez pas besoin de créer un nouvel éditeur pour chaque fichier glissé dans la table.

le second contiennent une JComboBox dont les options dépendent du contenu du fichier traîné

La seule partie de la table que vous devez personnaliser est la méthode getCellEditor(...).

Je suppose que vous auriez un éditeur différent pour une extension de fichier donnée.

Ainsi, le code de base pourrait être quelque chose comme:

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

Départ: How to add unique JComboBoxes to a column in a JTable (Java) pour un exemple de travail.La logique dans cette publication a un éditeur séparé par rangée à des fins de démonstration seulement. L'exemple montre que le code fonctionne avec les moteurs de rendu et les éditeurs par défaut. Tout ce que vous devez faire est de fournir les éléments pour chaque éditeur de boîte combo.

Dans votre cas, l'éditeur sera basé sur le type de fichier, de sorte que la logique doit tester les données dans la première colonne.

Remarque: l'instruction imbriquée if/else n'est pas une bonne solution. Vous pourriez vouloir utiliser une Hashmap de type/éditeur de fichier. Alors la méthode getCellEditor (...) serait juste une recherche Hashmap une fois que vous extrayez le type de fichier pour le fichier. Par conséquent, votre code de glissement ne doit rien avoir à voir avec les éditeurs de la table. Vous devez savoir à l'avance quels types de fichiers vous voulez prendre en charge et définir les éléments valides pour chacun de ces types de fichiers.

En outre, votre TableModel ne doit PAS étendre DefaultTableModel. Vous fournissez votre propre stockage de données et l'implémentation de toutes les méthodes, vous devriez simplement étendre le AbstractTableModel.

+0

@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

+0

@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

+0

@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