简体   繁体   中英

JTable column uneditable after adding JComboBox to a cell

I have a JTable component with 2 columns, for the second column iwant to add a JComboBox for only one cell, following oracle documentation i've created my own cell editor, and added the JComboBox but after that all of the other cells from the column became uneditable. Here is an example:

Before adding the JComboBox:

After adding the JComboBox, i could not edit the other cells

After adding the JComboBox:

My Code:

DefaultTableModel model = new DefaultTableModel(textosTabela, stubColumnNames);
tabela.setModel(model);
tabela.setBorder(new LineBorder(Color.black));
tabela.setGridColor(Color.black);
tabela.setShowGrid(true);
tabela.setPreferredSize(new Dimension(290, 132));
tabela.setRowHeight(22);
tabela.getColumnModel().getColumn(0).setPreferredWidth(160);
tabela.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
TableColumn tm = tabela.getColumnModel().getColumn(0);
tm.setCellRenderer(new ColorColumnRenderer(Color.lightGray));
TableColumn comboCol1 = tabela.getColumnModel().getColumn(1);
JComboBox comboBox = new JComboBox(valoresComboBox);
comboBox.setSelectedIndex(0);
comboCol1.setCellEditor(new ComboBoxEditor(1,3,comboBox));

CellEditor Code:

public class ComboBoxEditor extends DefaultCellEditor {

    private String[] values;
    private String selectedValue;
    private int column = -1;
    private int row = -1;

    public ComboBoxEditor(JComboBox values) {
        super(values);
        // TODO Auto-generated constructor stub
    }

    public ComboBoxEditor(int column, int row, JComboBox values) {
        super(values);
        this.column = column;
        this.row = row;

    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int row, int column) {
        // TODO Auto-generated method stub
        Component c = table.getEditorComponent();
        if(column == this.column && row == this.row) {
            return super.getTableCellEditorComponent(table, value, isSelected, row, column);
        }

        return null;
    }
}

Here is an example that returns a different combobo box by row. If there is no combo box for the row then the default editor is used:

import java.awt.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;

public class TableComboBoxByRow extends JPanel
{
    List<String[]> editorData = new ArrayList<String[]>(3);

    public TableComboBoxByRow()
    {
        setLayout( new BorderLayout() );

        // Create the editorData to be used for each row

        editorData.add( new String[]{ "Red", "Blue", "Green" } );
        editorData.add( new String[]{ "Circle", "Square", "Triangle" } );
        editorData.add( new String[]{ "Apple", "Orange", "Banana" } );

        //  Create the table with default data

        Object[][] data =
        {
            {"Color", "Red"},
            {"Shape", "Square"},
            {"Fruit", "Banana"},
            {"Plain", "Text"}
        };
        String[] columnNames = {"Type","Value"};

        DefaultTableModel model = new DefaultTableModel(data, columnNames);
        JTable table = new JTable(model)
        {
            //  Determine editor to be used by row
            public TableCellEditor getCellEditor(int row, int column)
            {
                int modelColumn = convertColumnIndexToModel( column );

                if (modelColumn == 1 && row < 3)
                {
                    JComboBox<String> comboBox1 = new JComboBox<String>( editorData.get(row));
                    return new DefaultCellEditor( comboBox1 );
                }
                else
                    return super.getCellEditor(row, column);
            }
        };

        JScrollPane scrollPane = new JScrollPane( table );
        add( scrollPane );
//      table.getColumnModel().getColumn(1).setCellRenderer(new ComboBoxRenderer2() );
    }
    private static void createAndShowUI()
    {
        JFrame frame = new JFrame("Table Combo Box by Row");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add( new TableComboBoxByRow() );
        frame.setSize(200, 200);
        frame.setLocationByPlatform( true );
        frame.setVisible( true );
    }

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

The key is overriding the getCellEditor(...) method instead of creating a custom renderer.

The TableModel should dictate which cells are editable or not, by default, DefaultTableModel makes all cell's editable.

In your TableCellEditor , you have...

if (column == this.column && row == this.row) {
    return super.getTableCellEditorComponent(table, value, isSelected, row, column);
}

Which seems to be making the decisions for the editor, which the model should be, for example

DefaultTableModel model = new DefaultTableModel(textosTabela, stubColumnNames) {
    @Override
    public boolean isCellEditable(int row, int column) {
        return row == 3 && column == 1;
    }
};

And the just use the DefaltCellEditor to wrap your JComboBox

JComboBox comboBox = new JComboBox(valoresComboBox);
DefaultCellEditor editor = new DefaultCellEditor(comboBox);

TableColumn comboCol1 = tabela.getColumnModel().getColumn(1);

I might point out that, generally speaking, JTable isn't really good as acting as a property sheet editor, where the value of a cell is different for each row

PropertySheetEditor

Reusability and configurability would be my two major concerns with any type of implementation. It's now really the tables responsibility to make decisions about how things should be rendered or edited, this is why with have renderers and editors. What we need is some way to provide a configurable element into the API which can make the decisions we need and leave the table to do the job it's currently doing.

Since the JTable isn't really designed to handle multiple types of data for a specific column, along with different renderers and editors, we need to provide that functionality.

A Property

Let's start with the key element, the property. A property has three basic elements, a name, value and type.

public interface Property<T> {
    public String getName();
    public T getValue();
    public Class<T> getType();
    public void setValue(T value);
}

I did think about making another interface for mutable properties, but you could just as easily add a readOnly property to this interface if that's required.

And because I'm lazy...

public class DefaultProperty<T> implements Property<T> {

    private final String name;
    private T value;
    private final Class<T> type;

    public DefaultProperty(String name, T value, Class<T> type) {
        this.name = name;
        this.value = value;
        this.type = type;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public T getValue() {
        return value;
    }

    @Override
    public Class<T> getType() {
        return type;
    }

    @Override
    public void setValue(T value) {
        this.value = value;
    }

}

The Model

Now, we need model which can manage our Property 's and the contract with the JTable ...

public interface PropertySheetModel extends TableModel {        
    public Property getPropertyAt(int row);
}

And because I'm lazy...

public class DefaultPropertySheeModel extends AbstractTableModel implements PropertySheetModel {

    private List<Property> properties;

    public DefaultPropertySheeModel(List<Property> properties) {
        this.properties = new ArrayList<>(properties);
    }

    @Override
    public int getRowCount() {
        return getProperties().size();
    }

    @Override
    public int getColumnCount() {
        return 2; // Key/value
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Property p = getPropertyAt(rowIndex);
        Object value = null;
        switch (columnIndex) {
            case 0:
                value = p.getName();
                break;
            case 1:
                value = p.getValue();
                break;
        }
        return value;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        Property p = getPropertyAt(rowIndex);
        p.setValue(aValue);
        fireTableCellUpdated(rowIndex, columnIndex);
    }

    @Override
    public Property getPropertyAt(int row) {
        return getProperties().get(row);
    }

    protected List<Property> getProperties() {
        return properties;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return columnIndex != 0;
    }

}

It wouldn't take much to make a dynamic model, where you could add/remove Property 's, it's just beyond the scope of this answer ;)

A Delegate

Okay, now we need something to start making more complex decisions for us. This is where the delegate comes in, we can use it to answer some of the questions that we don't really want to answer ourselves

public interface PropertySheetDelegate {

    public TableCellEditor getCellEditorFor(Property property);

    public void setCellEditorFor(Property property, TableCellEditor editor);
    public void setCellEditorFor(Class type, TableCellEditor editor);

    public TableCellRenderer getCellRendererFor(Property property);
    public void setCellRendererFor(Property property, TableCellRenderer editor);
    public void setCellRendererFor(Class type, TableCellRenderer editor);

}

And because I'm lazy...

public class DefaultPropertySheetDelegate implements PropertySheetDelegate {

    private Map<Property, TableCellEditor> propertyEditors;
    private Map<Class, TableCellEditor> typeEditors;

    private Map<Property, TableCellRenderer> propertyRenderers;
    private Map<Class, TableCellRenderer> typeRenderers;

    public DefaultPropertySheetDelegate() {
        propertyEditors = new HashMap<>(25);
        typeEditors = new HashMap<>(25);
        propertyRenderers = new HashMap<>(25);
        typeRenderers = new HashMap<>(25);

        JTextField field = new JTextField();
        field.setBorder(null);

        DefaultCellEditor editor = new DefaultCellEditor(field);
        editor.setClickCountToStart(1);
        setCellEditorFor(String.class, editor);
    }

    @Override
    public TableCellEditor getCellEditorFor(Property property) {
        TableCellEditor editor = propertyEditors.get(property);
        if (editor == null) {
            editor = typeEditors.get(property.getType());
        }
        return editor;
    }

    @Override
    public void setCellEditorFor(Property property, TableCellEditor editor) {
        propertyEditors.put(property, editor);
    }

    @Override
    public void setCellEditorFor(Class type, TableCellEditor editor) {
        typeEditors.put(type, editor);
    }

    @Override
    public void setCellRendererFor(Class type, TableCellRenderer renderer) {
        typeRenderers.put(type, renderer);
    }

    @Override
    public void setCellRendererFor(Property property, TableCellRenderer renderer) {
        propertyRenderers.put(property, renderer);
    }

    @Override
    public TableCellRenderer getCellRendererFor(Property property) {
        TableCellRenderer renderer = propertyRenderers.get(property);
        if (renderer == null) {
            renderer = typeRenderers.get(property.getType());
        }
        return renderer;
    }

}

Now, this implementation simply deals with the TableCellEditor , realistically, you should probably also have it deal with the renderers as well.

The View...

And finally, we need some way to show it, this is a customised JTable (obviously), which uses the PropertySheetDelegate and PropertySheetModel to present the data been managed...

public class PropertySheet extends JTable {

    private PropertySheetDelegate propertySheetDelegate;

    public PropertySheet(PropertySheetDelegate controller, PropertySheetModel model) {
        super(model);
        setDelegate(controller);
    }

    public PropertySheet() {
        super();
    }

    @Override
    public void setModel(TableModel dataModel) {
        if (dataModel instanceof PropertySheetModel || dataModel == null) {
            super.setModel(dataModel);
        } else {
            throw new UnsupportedOperationException("Unsupported PropertySheetModel " + dataModel.getClass().getName());
        }
    }

    public PropertySheetDelegate getPropertySheetDelegate() {
        return propertySheetDelegate;
    }

    public void setDelegate(PropertySheetDelegate value) {
        if (propertySheetDelegate != value) {
            PropertySheetDelegate old = propertySheetDelegate;
            this.propertySheetDelegate = value;
            firePropertyChange("propertySheetController", old, value);
        }
    }

    @Override
    public TableCellEditor getCellEditor(int row, int column) {
        TableCellEditor editor = null;
        // Assumes that only the values can be modified
        if (column == 1) {
            PropertySheetDelegate delegate = getPropertySheetDelegate();
            if (delegate != null) {
                PropertySheetModel model = (PropertySheetModel) getModel();
                Property property = model.getPropertyAt(row);
                editor = delegate.getCellEditorFor(property);

                System.out.println("Editor for " + property + " = " + editor);

                if (editor == null) {
                    editor = getDefaultEditor(property.getType());
                    System.out.println("Default Editor for " + property.getType() + " = " + editor);
                }
            } else {
                editor = super.getCellEditor(row, column);
            }
        }
        return editor;
    }

    @Override
    public TableCellRenderer getCellRenderer(int row, int column) {
        TableCellRenderer renderer = null;
        if (column == 1) {
            PropertySheetDelegate delegate = getPropertySheetDelegate();
            if (delegate != null) {
                PropertySheetModel model = (PropertySheetModel) getModel();
                Property property = model.getPropertyAt(row);
                renderer = delegate.getCellRendererFor(property);
                if (renderer == null) {
                    renderer = getDefaultRenderer(property.getType());
                }
            } else {
                renderer = super.getCellRenderer(row, column);
            }
        } else {
            renderer = super.getCellRenderer(row, column);
        }
        return renderer;
    }

}

The plumbing

And finally, some what to make it work...

List<Property> properties = new ArrayList<>(25);
properties.add(new DefaultProperty<>("Name", null, String.class));
properties.add(new DefaultProperty<>("Description", null, String.class));
properties.add(new DefaultProperty<>("Quanity", null, Integer.class));
properties.add(new DefaultProperty<>("Warrenty", null, Integer.class));
properties.add(new DefaultProperty<>("Returns", null, Boolean.class));

// This is our custom editor
DefaultCellEditor editor = new DefaultCellEditor(new JComboBox(new Integer[]{1, 2, 3, 4, 5}));
PropertySheetDelegate controller = new DefaultPropertySheetDelegate();
controller.setCellEditorFor(properties.get(2), editor);

PropertySheetModel model = new DefaultPropertySheeModel(properties);
PropertySheet propertySheet = new PropertySheet(controller, model);

setLayout(new BorderLayout());
add(new JScrollPane(propertySheet));

Using your new updates makes it easier to use the default renderers and editors.

However, in JTable , the GenericEditor uses the getColumnClass() method to convert the String entered into the text field into the proper Class so the value can be saved to the TableModel.

Your TableModel is using the default getColumnClass() implementation, so all values are considered Objects and are simply stored as a String.

You can verify this by clicking on the "Class of Selected Row" before and after editing the Integer or Double values. (The ComboBoxEditor and the BooleanEditor doesn't have a problem because they implement their own getCellEditorValue() method to return the proper value.)

import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.ArrayList;
import javax.swing.*;

public class PropertyTest extends JPanel
{
    public PropertyTest()
    {
        List<Property> properties = new ArrayList<>(25);
        properties.add(new DefaultProperty<>("String", "String", String.class));
        properties.add(new DefaultProperty<>("Integer Combo", new Integer(2), Integer.class));
        properties.add(new DefaultProperty<>("Boolean", Boolean.TRUE, Boolean.class));
        properties.add(new DefaultProperty<>("Integer", new Integer(1), Integer.class));
        properties.add(new DefaultProperty<>("Double", new Double(1.25), Double.class));

        // This is our custom editor
        DefaultCellEditor editor = new DefaultCellEditor(new JComboBox(new Integer[]{1, 2, 3, 4, 5}));
        PropertySheetDelegate delegate = new DefaultPropertySheetDelegate();
        delegate.setCellEditorFor(properties.get(1), editor);


        PropertySheetModel model = new DefaultPropertySheetModel(properties);
        final PropertySheet propertySheet = new PropertySheet(delegate, model);

        setLayout(new BorderLayout());
        add(new JScrollPane(propertySheet));

        JButton button = new JButton("Class of Selected Row");
        add(button, BorderLayout.SOUTH);
        button.addActionListener( new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                int row = propertySheet.getSelectedRow();
                System.out.println(propertySheet.getValueAt(row, 1).getClass() );
            }
        });
    }

    private static void createAndShowGUI()
    {
        JFrame frame = new JFrame("PropertyTest");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new PropertyTest());
        frame.setLocationByPlatform( true );
        frame.setSize(400, 400);
        frame.setVisible( true );
    }

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

I think you need to override the getColumnClass() method of the JTable because it has access to the row being edited and the TableModel doesn't.

Here is the code I added to the PropertySheet class:

@Override
public Class getColumnClass(int column)
{
    int row = getSelectedRow();

    if (row == -1) return super.getColumnClass( column );

    PropertySheetDelegate controller = getPropertySheetDelegate();

    if (controller == null || column == 0)
    {
        return super.getColumnClass( column );
    }
    else
    {
        PropertySheetModel model = (PropertySheetModel) getModel();
        Property property = model.getPropertyAt(row);
        return property.getType();
    }
}

Although I'm sure you will find a better way to implement it :)

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