【问题标题】:How to do a selection in cascade of JCombobox choices in a JTable?如何在 JTable 中的级联 JCombobox 选项中进行选择?
【发布时间】:2021-04-27 06:48:20
【问题描述】:

我有一个 JTable,其中数字 1、2、3 作为第一列,数字作为第二列中的文本,可以使用 JCombobox 进行选择。例如,1 可以在第 2 列中表示为“1”、ONE、FIRST 或 ONCE。当我做出选择时,下面行的所有组合框都必须使用相同性质的文本进行级联更新。因此,如果我选择 ONCE,则下面各行的组合框应更新为 TWICE、THRICE。如果我选择 FIRST,下面各行的组合框应更新为 SECOND、THIRD。等等……

起初它看起来可以正常工作,但每当我单击 JTable 上的其他位置时,组合框都会更新为最后一行的值。例如,如果我在第一行选择 ONCE,首先它会将其他行更新为 TWICE 和 THRICE。然后,如果我单击任何行,组合框选择将在第一行更新为 THRICE。下次单击时,第二行将更新为 THRICE。

我在这里做错了什么?

组合框单元格编辑器:

public class NumberChoiceEditor extends DefaultCellEditor {

    private static final long serialVersionUID = 1L;

    private String numberAsText;

    private static final String[][] NUMBERS = { { "1", "ONE", "FIRST", "ONCE" }, { "2", "TWO", "SECOND", "TWICE" }, { "3", "THREE", "THIRD", "THRICE" } };

    public NumberChoiceEditor() {
        super(new JComboBox<>());
    }

    @Override
    public Object getCellEditorValue() {
        return numberAsText;
    }

    @Override
    public Component getTableCellEditorComponent(final JTable table, final Object value, final boolean isSelected, final int row, final int column) {
        if (value instanceof String) {
            numberAsText = (String) value;
        }
        JComboBox<String> numberTextChoice = new JComboBox<>(NUMBERS[row]);
        numberTextChoice.setSelectedItem(numberAsText);
        numberTextChoice.addActionListener(e -> {
            numberAsText = (String) numberTextChoice.getSelectedItem();
            int nextRow = row + 1;
            if (nextRow < NUMBERS.length) {
                String numberText = (String) table.getValueAt(nextRow, 1);
                JComboBox<String> nextRowChoices = (JComboBox<String>) getTableCellEditorComponent(table, numberText, isSelected, nextRow, column);
                nextRowChoices.setSelectedIndex(numberTextChoice.getSelectedIndex());
                table.setValueAt(nextRowChoices.getSelectedItem(), nextRow, 1);
            }
        });
        return numberTextChoice;
    }

}

带框架的主类:

public class NumberTable {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            JTable table = new JTable(new Object[][] { { 1, "1" }, { 2, "2" }, { 3, "3" } }, new Object[] { "Number", "Number Text" });
            table.getColumn("Number Text").setCellEditor(new NumberChoiceEditor());
            table.setRowHeight(25);
            JFrame frame = new JFrame("Number Table");
            frame.add(new JScrollPane(table));
            frame.setLocationRelativeTo(null);
            frame.setSize(600, 200);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setVisible(true);
        });
    }
}

【问题讨论】:

  • 你为什么有int nextRow = row + 1;
  • 因为我想到达下一行来更新选定的组合框项目。例如,如果我在第 1 行选择“ONE”,则必须在第 2 行自动选择“TWO”,在第 3 行必须自动选择“THREE”。

标签: java swing


【解决方案1】:

我会使用TableModelListener。当通过JComboBox 编辑器更改值时,我会相应地调整TableModel

(代码后的注释。)

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;

import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

public class NumTable implements Runnable, TableModelListener {
    private boolean  adjusting;
    private JFrame  frame;
    private JTable  table;

    @Override
    public void run() {
        createGui();
    }

    @Override
    public void tableChanged(TableModelEvent event) {
        if (!adjusting) {
            adjusting = true;
            int col = event.getColumn();
            if (col == 1) {
                NumChoiceEd editor = (NumChoiceEd) table.getColumnModel().getColumn(1).getCellEditor();
                int row = event.getFirstRow();
                JComboBox<String> combo = editor.getCombo(row);
                if (combo != null) {
                    int ndx = combo.getSelectedIndex();
                    if (ndx >= 0) {
                        int rows = table.getRowCount();
                        for (int i = 0; i < rows; i++) {
                            combo = editor.getCombo(i);
                            String val = combo.getModel().getElementAt(ndx);
                            table.setValueAt(val, i, col);
                        }
                    }
                }
            }
            adjusting = false;
        }
    }

    private void createGui() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(createTable(), BorderLayout.CENTER);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JScrollPane createTable() {
        Object[][] data = new Object[][]{{1, "1"}, {2, "2"}, {3, "3"}};
        Object[] columnNames = new Object[]{"Number", "Number Text"};
        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        model.addTableModelListener(this);
        table = new JTable(model);
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        TableColumnModel tcm = table.getColumnModel();
        TableColumn column = tcm.getColumn(1);
        column.setCellEditor(new NumChoiceEd());
        JScrollPane scrollPane = new JScrollPane(table);
        return scrollPane;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new NumTable());
    }
}

class NumChoiceEd extends DefaultCellEditor {
    private static final long serialVersionUID = 1L;
    private JComboBox<String>  oneCombo;
    private JComboBox<String>  twoCombo;
    private JComboBox<String>  threeCombo;

    public NumChoiceEd() {
        super(new JComboBox<>());
        oneCombo = new JComboBox<>(new String[]{"1", "ONE", "FIRST", "ONCE"});
        twoCombo = new JComboBox<>(new String[]{"2", "TWO", "SECOND", "TWICE"});
        threeCombo = new JComboBox<>(new String[]{"3", "THREE", "THIRD", "THRICE"});
    }

    public JComboBox<String> getCombo(int row) {
        switch (row) {
            case 0:
                return oneCombo;
            case 1:
                return twoCombo;
            case 2:
                return threeCombo;
            default:
                return null;
        }
    }

    @Override
    public Component getTableCellEditorComponent(final JTable table,
                                                 final Object value,
                                                 final boolean isSelected,
                                                 final int row,
                                                 final int column) {
        if (column == 1) {
            switch (row) {
                case 0:
                    return oneCombo;
                case 1:
                    return twoCombo;
                case 2:
                    return threeCombo;
                default:
                    return super.getTableCellEditorComponent(table, value, isSelected, row, column);
            }
        }
        else {
            return super.getTableCellEditorComponent(table, value, isSelected, row, column);
        }
    }
}
  • 我不是在每次调用 getTableCellEditorComponent 方法时都创建一个新的 JComboBox,而是首先创建所有三个 JComboBoxes 并返回相关的一个。
  • 您不需要重写方法 getCellEditorValue,因为超类方法(在类 DefaultCellEditor 中)将返回正确的值。
  • 一旦用户更改值(并关闭表格单元格编辑器),就会调用方法tableChanged。在该方法中,我获取从JComboBox 中选择的值的索引,然后遍历JTable 中的所有行,并在JComboBox 中为每一行获取该索引处的值并设置JTable 值到那个值。
  • 因为我更改了方法TableModel 中的tableChanged,这将导致该方法再次被调用。为了防止递归调用,我使用了adjusting 标志。

在下面的屏幕截图中,我从JComboBox 中选择了一个值,但我还没有关闭编辑器。如果我导航到 JTable 中的另一个单元格,它将关闭编辑器,然后所有显示的数据都会改变。请注意,如果您导航到不同行中的列 Number Text,您可能看不到更改,因为这将立即打开您导航到的单元格的 JComboBox 编辑器。

关闭编辑器后,表格如下图所示。

请注意,上面的屏幕截图中有很多空白区域,因为JTable 的默认尺寸非常大,但显示数据所需的空间(在这种情况下)很小。使JTable 更小(在这种情况下)的一种方法是更改​​JScrollPane 的首选大小。

编辑

针对您在comment 中提出的问题,即

是否可以更新组合框值的变化

是的,这是可能的。您将一个ActionListener 添加到每个JComboBox,它只调用方法stopCellEditing。这是上面的代码,修改为包含ActionListener。唯一的变化是在 NumChoiceEd 类中。

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.event.ActionListener;

import javax.swing.DefaultCellEditor;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableColumnModel;

public class NumTable implements Runnable, TableModelListener {
    private boolean  adjusting;
    private JFrame  frame;
    private JTable  table;

    @Override
    public void run() {
        createGui();
    }

    @Override
    public void tableChanged(TableModelEvent event) {
        if (!adjusting) {
            adjusting = true;
            int col = event.getColumn();
            if (col == 1) {
                NumChoiceEd editor = (NumChoiceEd) table.getColumnModel().getColumn(1).getCellEditor();
                int row = event.getFirstRow();
                JComboBox<String> combo = editor.getCombo(row);
                if (combo != null) {
                    int ndx = combo.getSelectedIndex();
                    if (ndx >= 0) {
                        int rows = table.getRowCount();
                        for (int i = 0; i < rows; i++) {
                            combo = editor.getCombo(i);
                            String val = combo.getModel().getElementAt(ndx);
                            table.setValueAt(val, i, col);
                        }
                    }
                }
            }
            adjusting = false;
        }
    }

    private void createGui() {
        frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(createTable(), BorderLayout.CENTER);
        frame.pack();
        frame.setLocationByPlatform(true);
        frame.setVisible(true);
    }

    private JScrollPane createTable() {
        Object[][] data = new Object[][]{{1, "1"}, {2, "2"}, {3, "3"}};
        Object[] columnNames = new Object[]{"Number", "Number Text"};
        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        model.addTableModelListener(this);
        table = new JTable(model);
        table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
        TableColumnModel tcm = table.getColumnModel();
        TableColumn column = tcm.getColumn(1);
        column.setCellEditor(new NumChoiceEd());
        JScrollPane scrollPane = new JScrollPane(table);
        return scrollPane;
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new NumTable());
    }
}

class NumChoiceEd extends DefaultCellEditor {
    private static final long serialVersionUID = 1L;
    private JComboBox<String>  oneCombo;
    private JComboBox<String>  twoCombo;
    private JComboBox<String>  threeCombo;

    public NumChoiceEd() {
        super(new JComboBox<>());
        ActionListener al = event -> NumChoiceEd.this.stopCellEditing(); // ADDED THIS LINE
        oneCombo = new JComboBox<>(new String[]{"1", "ONE", "FIRST", "ONCE"});
        oneCombo.addActionListener(al); // ADDED THIS LINE
        twoCombo = new JComboBox<>(new String[]{"2", "TWO", "SECOND", "TWICE"});
        twoCombo.addActionListener(al); // ADDED THIS LINE
        threeCombo = new JComboBox<>(new String[]{"3", "THREE", "THIRD", "THRICE"});
        threeCombo.addActionListener(al); // ADDED THIS LINE
    }

    public JComboBox<String> getCombo(int row) {
        switch (row) {
            case 0:
                return oneCombo;
            case 1:
                return twoCombo;
            case 2:
                return threeCombo;
            default:
                return null;
        }
    }

    @Override
    public Component getTableCellEditorComponent(final JTable table,
                                                 final Object value,
                                                 final boolean isSelected,
                                                 final int row,
                                                 final int column) {
        if (column == 1) {
            switch (row) {
                case 0:
                    return oneCombo;
                case 1:
                    return twoCombo;
                case 2:
                    return threeCombo;
                default:
                    return super.getTableCellEditorComponent(table, value, isSelected, row, column);
            }
        }
        else {
            return super.getTableCellEditorComponent(table, value, isSelected, row, column);
        }
    }
}

【讨论】:

  • 所以通过单击其他地方关闭组合框编辑器是触发更新的原因。但是是否可以更新组合框值更改,即无需单击其他位置来触发刷新?
猜你喜欢
  • 2017-01-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多