JComboBox as Jtable CellEditor with Overriden stop

2019-03-01 02:25发布

问题:

I have a custom JTable with a custom TableModel using a JComboBox as a cell editor. The ComboBox also has a custom ComboBoxModel The ComboBox model holds multiple fields that will be used to update the data behind the JTable and afterwards update a database.

The following is a simple example to show the problem I am encountering. Steps to reproduce:

  1. Click on an cell
  2. Select a value from the ComboBox drop-down list
  3. Click on a different cell
  4. Click back on the first selected cell

The second cell will get the value from the first one.

Why is this happening? Why does the ComboBox model change before stopCellEditing exists?

import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class TestComboCellEditor {

    public static void main(String[] args) {

        TestComboCellEditor test = new TestComboCellEditor();
        test.go();
    }

    public void go() {

        //create the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // create and add a tabbed pane to the frame
        JTabbedPane tabbedPane = new JTabbedPane();
        frame.getContentPane().add(tabbedPane);
        //create a table and add it to a scroll pane in a new tab
        final JTable table = new JTable(new DefaultTableModel(new Object[]{"A", "B"}, 5));
        JScrollPane scrollPane = new JScrollPane(table);
        tabbedPane.addTab("test", scrollPane);

        // create a simple JComboBox and set is as table cell editor on column A
        Object[] comboElements = {"aaaaa1", "aaaaaa2", "b"};
        final JComboBox comboBox = new JComboBox(comboElements);
        comboBox.setEditable(true);
        table.getColumn("A").setCellEditor(new DefaultCellEditor(comboBox) {
            @Override
            public boolean stopCellEditing() {
                if (comboBox.isEditable()) {
                    DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
                    String selectedItem = (String) comboModel.getSelectedItem();
                    int selectedIndex = comboModel.getIndexOf(selectedItem);
                    if (!(selectedIndex == -1)) {
                        // the selected item exists as an Option inside the ComboBox
                        DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
                        int selectedRow = table.getSelectedRow();
                        int selectedColumn = table.getSelectedColumn();
                        tableModel.setValueAt(selectedItem, selectedRow, selectedColumn);
                    } else if (selectedItem != null) {
                        // missing code - adding new info to a custom JComboBox model and to alter info inside a custom table model
                    }
                }
                return super.stopCellEditing();
            }
        });

        // pack and show frame
        frame.pack();
        frame.setVisible(true);

    }
}

回答1:

Here is an approach that keeps all the code in the editor:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

public class TestComboCellEditor {

    public static void main(String[] args) {

        TestComboCellEditor test = new TestComboCellEditor();
        test.go();
    }

    public void go() {

        //create the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // create and add a tabbed pane to the frame
        JTabbedPane tabbedPane = new JTabbedPane();
        frame.getContentPane().add(tabbedPane);
        //create a table and add it to a scroll pane in a new tab
        final JTable table = new JTable(new DefaultTableModel(new Object[]{"A", "B"}, 5));
        JScrollPane scrollPane = new JScrollPane(table);
        tabbedPane.addTab("test", scrollPane);

        // create a simple JComboBox and set is as table cell editor on column A
        Object[] comboElements = {"aaaaa1", "aaaaaa2", "b"};
        final JComboBox comboBox = new JComboBox(comboElements);
        comboBox.setEditable(true);
        table.getColumn("A").setCellEditor(new DefaultCellEditor(comboBox)
        {
            private Object originalValue;

            @Override
            public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column)
            {
                originalValue = value;
                return super.getTableCellEditorComponent(table, value, isSelected, row, column);
            }

            @Override
            public boolean stopCellEditing()
            {
                JComboBox comboBox = (JComboBox)getComponent();
                DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
                Object editingValue = getCellEditorValue();

                //  Needed because your TableModel is empty

                if (editingValue == null)
                    return super.stopCellEditing();

                int selectedIndex = comboModel.getIndexOf(editingValue);

                //  Selecting item from model

                if (! (selectedIndex == -1))
                    return super.stopCellEditing();

                //  Confirm addition of new value

                int result = JOptionPane.showConfirmDialog(
                    comboBox.getParent(),
                    "Add (" + editingValue + ") to table?",
                    "Update Model",
                    JOptionPane.YES_NO_OPTION);

                if (result == JOptionPane.YES_OPTION)
                {
                    comboBox.addItem(editingValue);
                    return super.stopCellEditing();
                }
                else
                {
                    comboBox.removeItem(editingValue);
                     comboBox.setSelectedItem(originalValue);
                    return false;
                }
            }
        });

        // pack and show frame
        frame.pack();
        frame.setVisible(true);

    }
}


回答2:

Ok, I've done some changes and I think I got something working. If it is not the best practice and you got a better implementation, please post an answer.

Edit: Do not follow the example below! Following kleopatra's comments it is a wrong implementation. I'm leaving it here so you know how not to do it.

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;

public class TestComboCellEditor {

    public static void main(String[] args) {

        TestComboCellEditor test = new TestComboCellEditor();
        test.go();
    }

    public void go() {

        //create the frame
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        // create and add a tabbed pane to the frame
        JTabbedPane tabbedPane = new JTabbedPane();
        frame.getContentPane().add(tabbedPane);
        //create a table and add it to a scroll pane in a new tab
        final JTable table = new JTable(new DefaultTableModel(new Object[]{"A", "B"}, 5));
        JScrollPane scrollPane = new JScrollPane(table);
        tabbedPane.addTab("test", scrollPane);

        // create a simple JComboBox and set is as table cell editor on column A
        Object[] comboElements = {"aaaaa1", "aaaaaa2", "b"};
        final JComboBox comboBox = new JComboBox(comboElements);
        comboBox.setEditable(true);
        table.getColumn("A").setCellEditor(new DefaultCellEditor(comboBox) {
            @Override
            public boolean stopCellEditing() {
                if (comboBox.isEditable()) {
                    DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
                    String selectedItem = (String) comboModel.getSelectedItem();
                    int selectedIndex = comboModel.getIndexOf(selectedItem);
                    if (!(selectedIndex == -1)) {
                        comboBox.actionPerformed(new ActionEvent(this, selectedIndex, "blabla"));
                    } else if (selectedItem != null) {
                        // missing code - adding new info to a custom JComboBox model and to alter info inside a custom table model
                    }
                }
                return super.stopCellEditing();
            }
        });

        comboBox.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // the selected item exists as an Option inside the ComboBox
                if (e.getActionCommand().equals("blabla")) {
                    DefaultComboBoxModel comboModel = (DefaultComboBoxModel) comboBox.getModel();
                    String selectedItem = (String) comboModel.getSelectedItem();
                    DefaultTableModel tableModel = (DefaultTableModel) table.getModel();
                    int selectedRow = table.getSelectedRow();
                    int selectedColumn = table.getSelectedColumn();
                    tableModel.setValueAt(selectedItem, selectedRow, selectedColumn);
                }
            }
        });

        // pack and show frame
        frame.pack();
        frame.setVisible(true);

    }
}