简体   繁体   English

将JComboBox添加到单元格后,JTable列不可编辑

[英]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. 我有一个包含2列的JTable组件,对于第二列,我想仅为一个单元格添加一个JComboBox,在oracle文档之后,我创建了自己的单元格编辑器,并添加了JComboBox,但是此后该列中的所有其他单元格变得无法编辑。 Here is an example: 这是一个例子:

Before adding the JComboBox: 在添加JComboBox之前:

After adding the JComboBox, i could not edit the other cells 添加JComboBox之后,我无法编辑其他单元格

After adding the JComboBox: 添加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: CellEditor代码:

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. 关键是重写getCellEditor(...)方法,而不是创建自定义渲染器。

The TableModel should dictate which cells are editable or not, by default, DefaultTableModel makes all cell's editable. TableModel应该规定哪些单元格是可编辑的,默认情况下, DefaultTableModel使所有单元格都可编辑。

In your TableCellEditor , you have... TableCellEditor ,您可以...

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 只需使用DefaltCellEditor包装您的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 我可能会指出,一般来说, JTable不能真正用作属性表编辑器,因为每行的单元格值都不同

PropertySheetEditor 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. 我们需要某种方式在API中提供一个可配置的元素,该元素可以做出我们需要的决定,并离开表格来完成当前正在做的工作。

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. 由于JTable并非真正为处理特定列的多种类型的数据以及不同的呈现器和编辑器而设计,因此我们需要提供该功能。

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. 我确实考虑过要为可变属性创建另一个interface ,但是如果需要,您也可以轻松地向该接口添加readOnly属性。

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 ... 现在,我们需要可以管理我们的Property和与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 ;) 创建动态模型并不需要花费太多时间,您可以在其中添加/删除Property ,这超出了此答案的范围;)

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. 现在,此实现只处理TableCellEditor ,实际上,您可能还应该处理渲染器。

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... 最后,我们需要某种方式来显示它,这是一个定制的JTable (显然),它使用PropertySheetDelegatePropertySheetModel来显示已管理的数据...

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. 但是,在JTableGenericEditor使用getColumnClass()方法将输入到文本字段中的String转换为正确的Class,以便可以将值保存到TableModel。

Your TableModel is using the default getColumnClass() implementation, so all values are considered Objects and are simply stored as a String. 您的TableModel使用默认的getColumnClass()实现,因此所有值都被视为Objects,并简单地存储为String。

You can verify this by clicking on the "Class of Selected Row" before and after editing the Integer or Double values. 您可以通过在编辑Integer或Double值之前和之后单击“所选行的类”来进行验证。 (The ComboBoxEditor and the BooleanEditor doesn't have a problem because they implement their own getCellEditorValue() method to return the proper value.) (ComboBoxEditor和BooleanEditor没问题,因为它们实现了自己的getCellEditorValue()方法以返回正确的值。)

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. 我认为您需要重写JTablegetColumnClass()方法,因为它可以访问正在编辑的行,而TableModel没有访问权限。

Here is the code I added to the PropertySheet class: 这是我添加到PropertySheet类的代码:

@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 :) 虽然我确定您会找到一种更好的方法来实现它:)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM