简体   繁体   中英

How to do a selection in cascade of JCombobox choices in a JTable?

I have a JTable with numbers 1,2,3 as 1st column and the number as text in the 2nd column which can be chosen with a JCombobox. For example 1 can be represented as "1", ONE, FIRST or ONCE in the 2nd column. When I make a choice, all the comboboxes of the rows below have to be updated in cascade with the text of the same nature. So if I choose ONCE, the comboboxes of the rows below should be updated to TWICE, THRICE. If I choose FIRST, the comboboxes of the rows below should be updated to SECOND, THIRD. And so on..

At first it looks like it's working but whenever I click somewhere else on the JTable, the combobox is updated with the value of the last row. For example, if I choose ONCE in the 1st row, at first it will update the other rows to TWICE and THRICE. Then if I click on any row, the combobox selection will be updated to THRICE on the 1st row. Next time I click, the 2nd row is updated to THRICE.

What am I doing wrong here?

The combobox cell editor:

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

}

The main class with the frame:

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

I would use a TableModelListener . When the value is changed via the JComboBox editor, I would adjust the TableModel accordingly.

(Notes after the code.)

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);
        }
    }
}
  • Rather than create a new JComboBox each time method getTableCellEditorComponent is called, I initially create all three JComboBox es and return the relevant one.
  • You don't need to override method getCellEditorValue because the superclass method (in class DefaultCellEditor ) will return the correct value.
  • Once the user changes the value (and closes the table cell editor), method tableChanged is invoked. In that method, I get the index of the value that was selected from the JComboBox and then I go through all the rows in the JTable and get the value at that index in the JComboBox for each row and set the JTable value to that value.
  • Because I change the TableModel in method tableChanged , that will cause the method to be called again. In order to prevent the recursive call, I use the adjusting flag.

In the below screen capture, I have selected a value from the JComboBox but I have not yet closed the editor. If I navigate to a different cell in the JTable that will close the editor and then all the displayed data will change. Note that if you navigate to column Number Text in a different row, you may not see the change since that will immediately open the JComboBox editor for the cell that you navigated to.

编辑器打开

After I close the editor, the table looks as in the below screen capture.

编辑关闭

Note that there is a lot of blank space in the above screen captures since the default dimensions of JTable are quite large but the space required to display the data (in this case) is small. One way to make the JTable smaller (in this case) would be to change the preferred size of the JScrollPane .

EDIT

In response to the question you asked in your comment , namely

is it possible to update on the combobox value change

Yes, it is possible. You add an ActionListener to each JComboBox that simply calls method stopCellEditing . Here is the above code, modified to include the ActionListener . The only changes are in class 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);
        }
    }
}

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