java - JTable 中的 JComboBox 不显示选择

标签 java swing jtable jcombobox tablecelleditor

渲染器和编辑器听起来很简单,尽管我回过头来讨论类似的问题,但我还是错过了一些基本的东西。我想将任何旧文本文件拖到 2 列 JTable 中,让第一列显示文件名,第二列包含一个 JComboBox,其选项取决于拖动文件的内容。 (在下面的代码中,我只是伪造了一些条目。)

这一切都工作正常,直到我从组合框中进行选择 - 该选择不显示 - 只是一个组合框,正确填充但未进行选择。我知道这一定与我滥用渲染器/编辑器有关,但经过至少两周的挣扎后,我正在寻求专业帮助。如果您认为我完全错过了如何编写渲染器和编辑器的机会,那么我很高兴您没有看到我之前的尝试。

希望这段代码符合 SSCCE 的资格 - 如果我包含了一些不应该包含的内容,我深表歉意。我保留了 DnD 的内容,以防万一它具有某种意义。

就其值(value)而言,我使用 ComboBoxModels 的静态列表(每行一个),因为每个 JComboBox 包含不同的选项,同样还有 TableCellEditors(尽管我不知道这是否是正确的方法)。

要运行此命令,只需将任何文件拖到出现的表中,然后从右列的 JComboBox 中进行选择,然后观察它忽略您。非常感谢,即使您有一些建议而不需要费力运行这个。

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

最佳答案

make a selection from the JComboBox in the right column and watch it ignore you

您的自定义编辑器有问题,我不确定是什么。您有一个大问题,因为您尝试使用 JComboBox 作为编辑器的数据。这是完全错误的。

但好消息是您无需使用自定义渲染器或自定义编辑器。

您不应该将 JComboBox 存储在 TableModel 中。您只需存储组合框中所选项目的字符串即可。 (默认组合框编辑器将自动为您完成此操作)。

您无需为拖到表格中的每个文件创建新的编辑器。

the second contain a JComboBox whose options depend on the contents of the dragged file

因此,您需要自定义的表格的唯一部分是 getCellEditor(...) 方法。

我猜你会对给定的文件扩展名有不同的编辑器。

所以基本代码可能是这样的:

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

查看: How to add unique JComboBoxes to a column in a JTable (Java)一个工作示例。该帖子中的逻辑确实有一个单独的按行编辑器,仅用于演示目的。该示例演示了代码可以与默认渲染器和编辑器一起使用。您所需要做的就是为每个组合框编辑器提供项目。

在您的情况下,编辑器将基于文件类型,因此逻辑需要测试第一列中的数据。

注意:嵌套的 if/else 语句不是一个好的解决方案。您可能想使用文件类型/编辑器的 HashMap 。然后,一旦提取文件的文件类型,getCellEditor(...) 方法就只是一个 HashMap 查找。

所以你的拖动代码应该与表格的编辑器无关。您需要事先了解要支持哪些文件类型,并为每种文件类型定义有效项目。

此外,您的 TableModel 不应扩展 DefaultTableModel。您正在提供自己的数据存储并实现所有方法,因此您应该只扩展 AbstractTableModel。

关于java - JTable 中的 JComboBox 不显示选择,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39154390/

相关文章:

java - 显示 CardLayout 的上一个选项卡

java - 在扩展类中使用 MouseListener

Java - 使用高度图创建世界碰撞(JBullet)

java - 如何使用 new BufferedWriter(new FileWriter()); 的一个实例写入文件以不同的方法

java - 模式仅查找组中正则表达式的一个字符

java - 使用 JPanel 引用更改子组件?

java - 我们可以部分绘制 JTable 单元格吗?

java - 创建 TableModel 并动态填充 jTable

java - JTable rowCount问题

java - Maven 将 applicationContext.xml 从 src/main/resources 复制到 target/myproject/WEB-INF