简体   繁体   中英

Cannot display JComboBox in JTable with TableModel

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

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

Your TableModel is completely wrong.

The data of the TableModel must be stored in the TableModel, not as an Array external to the model.

Don't extend AbstractTableModel. Instead extend DefaultTableModel. The DefaultTableModel already provides data storage and methods to update the data of each cell. Then the only method you need to override is the getColumnClass(...) method, to make sure the appropriate renderer/editor is used for each column.

See: Sorting JTable programmatically for a basic example showing how to add initial data to a JTable. The getColumnClass(...) method in that example is more generic.

You can simplify the getColumnClass(...) method as follows:

@Override
public Class getColumnClass(int column)
{
    switch (column)
    {
        case 2: return Double.class;
        default: return super.getColumnClass(column);
    }
}

Then if you want the editor of the first column to be a combobox you would use:

badTable.getColumnModel().getColumn(0).setCellEditor( new DefaultCellEditor(col0ComboBox));

You should NOT use the same renderer for all 3 columns because typically numbers are displayed right aligned in the column.

There should be no need for custom renderers. Instead you change properties of the table:

table.setBackground(...);
table.setForeground(...);
table.setFont(...);

These values will then be used by each of the default column renderers.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM