无法使用 TableModel 在 JTable 中显示 JComboBox

问题描述

下面的代码显示一个 JTable 有 3 列,分别包含一个 JComboBox一个 String一个 double,并且应该显示为黄色。问题是我无法将第一列中的 JComboBox 显示为...组合框;相反,我得到一个 String 说“javax.swing.JComboBox...”。我做错了什么?

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

解决方法

永远不要在 TableModel 中返​​回组件。拥有单独的模型和视图的全部意义在于模型仅包含数据,而不包含组件。模型的工作是提供数据;视图的工作是确定如何显示该数据。

您的 TableModel 的 getColumnClass 方法应如下所示:

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

并且您的 getValueAt 方法需要返回该行的实际数据值

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

单元渲染器是视图的一部分,而不是模型的一部分,因此它可以使用 JComboBox。您的渲染需要使用 value 参数来修改您的 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;
    }
}
,

您的 TableModel 完全错误。

TableModel 的数据必须存储在 TableModel 中,而不是作为模型外部的 Array。

不要扩展 AbstractTableModel。而是扩展 DefaultTableModel。 DefaultTableModel 已经提供了数据存储和更新每个单元格数据的方法。那么您需要覆盖的唯一方法是 getColumnClass(...) 方法,以确保为每一列使用适当的渲染器/编辑器。

请参阅:Sorting JTable programmatically 了解如何将初始数据添加到 JTable 的基本示例。该示例中的 getColumnClass(...) 方法更通用。

您可以将 getColumnClass(...) 方法简化如下:

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

然后,如果您希望第一列的编辑器成为组合框,您可以使用:

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

您不应该对所有 3 列使用相同的渲染器,因为通常数字在列中显示为右对齐。

应该不需要自定义渲染器。相反,您更改表的属性:

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

这些值将被每个默认的列渲染器使用。