Cannot display JComboBox in JTable with TableModel

Tags: , ,



Below code to display a JTable with 3 columns, which respectively contain a JComboBox, a String and a double, and which should display yellow. The problem is that I cannot get the JComboBox in the first column to display as … a combo box; instead I get a String saying “javax.swing.JComboBox...“. What am I doing wrong?

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.TableModel;
import java.awt.*;

public class BadDialog extends JDialog {
    //Instantiate the data for the table, which is 2 rows x 3 cols
    private final JComboBox col0ComboBox = new JComboBox(new String[]{"aaa", "bbb"}); //Goes in all rows of Col 0
    private final String[] col1Data = {"Mickey", "Mouse"};
    private final double[] col2Data = {111, 222};

    public BadDialog() {
        //Instantiate table
        JTable badTable = new JTable();

        //Assign a tableModel to the table, put the table in a scroller, add it to this dialog, and sort out the renderer
        TableModel badTableModel = new BadTableModel();
        badTable.setModel(badTableModel);
        JScrollPane scroller = new JScrollPane(badTable);
        add(scroller);
        BadTableCellRenderer badTableCellRenderer = new BadTableCellRenderer();
        badTable.setDefaultRenderer(JComboBox.class, badTableCellRenderer); //Col 0
        badTable.setDefaultRenderer(String.class, badTableCellRenderer); //Col 1
        badTable.setDefaultRenderer(Double.class, badTableCellRenderer); //Col 2

        //Assign col0ComboBox to Col 0
        badTable.getColumnModel().getColumn(0).setCellEditor(new DefaultCellEditor(col0ComboBox));

        //Show the dialog
        setPreferredSize(new Dimension(300, 470));
        pack();
        setModal(true);
        setLocation(10, 10);
        setVisible(true);
    }

    private final class BadTableModel extends AbstractTableModel {
        @Override
        public int getRowCount() {
            return 2;
        }

        @Override
        public int getColumnCount() {
            return 3;
        }

        @Override
        public Object getValueAt(int rowIndex, int colIndex) {
            if (colIndex == 0) return col0ComboBox;
            if (colIndex == 1) return col1Data[rowIndex];
            return col2Data[rowIndex];
        }

        @Override
        public Class<?> getColumnClass(int colIndex) {
            if (colIndex == 0) return JComboBox.class;
            if (colIndex == 1) return String.class;
            return Double.class;
        }
    }

    private static class BadTableCellRenderer extends DefaultTableCellRenderer {

        @Override
        public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
            Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);

            //Make all columns yellow
            c.setBackground(Color.YELLOW);
            c.setForeground(Color.RED);
            c.setFont(new Font("Dialog", Font.PLAIN, 12));
            return c;
        }
    }

    public static void main(String[] args) {
        new BadDialog();
    }
}

Answer

Never return a component in a TableModel. The whole point of having a separate model and view is that the model contains only data, not components. The model’s job is to provide data; the view’s job is to determine how to display that data.

Your TableModel’s getColumnClass method should look like this:

public Class<?> getColumnClass(int colIndex) {
    if (colIndex == 0) return String.class; // String, not JComboBox
    if (colIndex == 1) return String.class;
    return Double.class;
}

and your getValueAt method needs to return the actual data value at that row:

public Object getValueAt(int rowIndex, int colIndex) {
    if (colIndex == 0) return (rowIndex % 1 == 0 ? "aaa" : "bbb");
    if (colIndex == 1) return col1Data[rowIndex];
    return col2Data[rowIndex];
}

The cell renderer is part of the view, not the model, so it can make use of a JComboBox. Your render needs to use the value argument to modify your JComboBox:

private static class BadTableCellRenderer extends DefaultTableCellRenderer {

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int col) {
        if (row != 0) {
            return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
        }

        JComboBox c = col0ComboBox;
        c.setSelectedItem(value);

        //Make all columns yellow
        c.setBackground(Color.YELLOW);
        c.setForeground(Color.RED);
        c.setFont(new Font("Dialog", Font.PLAIN, 12));

        return c;
    }
}


Source: stackoverflow