简体   繁体   中英

Add JButtons only to certain rows and columns in a JTable

I want to add some JButtons to a JTable, but only to specific cells. So far I've wrote a class that can add Buttons to only one row. I have found a lot of tutorials on how to add Buttons to a whole column, but I can't figure out how to add them only to certain rows.

This is what I want the table to look like:

这就是我想要的样子

The ideal solution would be, if i could just add Buttons based on the values of the tabledata. For example if the data contains an empty String, I'm not showing a Button and if it has a proper value, the String will be the text of the Button.

Source Code

Class ConnectionPanel

public class ConnectionPanel extends JFrame{

public ConnectionPanel(){

    Object[][] licData = {{"License 1", "0.0.0.0", "connect", "disconnect", ""},{"License 2", "123.123.123", "", "", ""},{"License 3", "42.23.4", "connect", "disconnect", "delete"}};

    ConnTableModel licConnModel = new ConnTableModel(licData);

    this.setLayout(new MigLayout("", "[grow]", "[][grow][][][][][][][grow][][][][][]"));
    this.setSize(new Dimension(500, 300));
    JLabel lblLicenses = new JLabel("Licenses");
    this.add(lblLicenses, "cell 0 0,growx");

    JTable licenseTable = new JTable(licConnModel);
    licenseTable.setTableHeader(null);

    new ButtonColumn(licenseTable, 2, 0);
    new ButtonColumn(licenseTable, 3, 0);
    new ButtonColumn(licenseTable, 2, 2);
    new ButtonColumn(licenseTable, 3, 2);
    new ButtonColumn(licenseTable, 4, 2);

    JScrollPane scrollPaneLic = new JScrollPane();
    scrollPaneLic.setViewportView(licenseTable);
    this.add(scrollPaneLic, "cell 0 1 1 6,grow");
}

Inner static Class ConnTableModel

public static class ConnTableModel extends AbstractTableModel {
    Object[][] data;

    public ConnTableModel(Object[][] data){
        this.data = data;
    }

    @Override
    public int getRowCount() {
        return data.length;
    }

    @Override
    public int getColumnCount() {
        return data[0].length;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return data[rowIndex][columnIndex];
    }

    public boolean isCellEditable(int rowIndex, int columnIndex) { 
        if(columnIndex == 2 || columnIndex == 3 || columnIndex == 4) {
            return true;
        } else {
            return false;
        }
    }   
}

Inner Class ButtonColumn

class ButtonColumn extends AbstractCellEditor
implements TableCellRenderer, TableCellEditor, ActionListener
{
    JTable table;
    JButton editButton;
    JButton renderButton;
    String text;
    int showRow;

    public ButtonColumn(JTable table, int column, int showRow) {
        super();
        this.table = table;
        this.showRow = showRow;
        renderButton = new JButton();

        editButton = new JButton();
        editButton.setFocusPainted( false );
        editButton.addActionListener( this );

        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(column).setCellRenderer( this );
        columnModel.getColumn(column).setCellEditor( this );
    }

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

    @Override
    public void actionPerformed(ActionEvent e) {
        fireEditingStopped();

        if(text.equals("connect")){
            System.out.println("conn");
        }else if(text.equals("disconnect")){
            System.out.println("disc");
        }
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row,
            int column) {
        if(row == showRow){
         text = (value == null) ? "" : value.toString();
            editButton.setText( text );
            return editButton;
        }else{
            return null;
        }
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus,
            int row, int column) {
        if (hasFocus) {
            renderButton.setForeground(table.getForeground());
            renderButton.setBackground(UIManager.getColor("Button.background"));
        } else if (selected) {
            renderButton.setForeground(table.getSelectionForeground());
             renderButton.setBackground(table.getSelectionBackground());
        } else {
            renderButton.setForeground(table.getForeground());
            renderButton.setBackground(UIManager.getColor("Button.background"));
        }

        renderButton.setText((value == null) ? "" : value.toString());
        if(row == showRow) {
            return renderButton;
        } else {
            return null;
        }
    }

}

Main Method

    public static void main(String[] args) {
        ConnectionPanel con = new ConnectionPanel();
        con.setVisible(true);
    }
}

Question

When I create new Buttons with "new ButtonColumn(myTable, column, row)" and I do this for more than one row, it shows only the last Button I create in the table for each column. I can't really figure out why it behaves like that. I guess there is something wrong with the "ButtonColumn"-class? And is there a way to either create Buttons only for certain rows, or fe create them from the TableModel directly?

you have done this in completely wrong way. anyway, the root cause for the problem you mentioned is: when you create ButtonColumn instance, it will be the cell renderer and the editor for that column. when you execute,

 new ButtonColumn(licenseTable, 2, 2);
 new ButtonColumn(licenseTable, 2, 3);

now column 2 cell renderer is ButtonColumn(licenseTable, 2, 3) , it will return null for any row other than 3. so you will see the button only in row 3 for column 2.

Other problems:

  1. don't use the same instance for editor and the renderer, it may cause some painting problems

  2. cell renderer is a designed to provide a component based on the row,column,object. so your renderer can evaluate those values and it can then decide whether it should return a button or a empty label.

here is a mcve reduced to the very basics:

public class ButtonTableTest {

    public static void main(String[] args) {
        final Random random = new Random();
        DefaultTableModel tableModel = new DefaultTableModel(20, 7) {
            @Override
            public Class<?> getColumnClass(int arg0) {
              // provide the default renderer and editor of String for empty cells
                return String.class; 
            }

            @Override
            public boolean isCellEditable(int row, int column) {
               // do not request the editor for empty cells
                return !"".equals(getValueAt(row, column));
            }

            @Override
            public Object getValueAt(int row, int column) {
                // some random table content
                if (null == super.getValueAt(row, column)) {
                    int nextInt = random.nextInt(10);
                    if (nextInt > 5)
                        super.setValueAt(String.format("cell %dx%d", row, column), row, column);
                    else
                        super.setValueAt("", row, column);
                }
                return super.getValueAt(row, column);
            }

            @Override
            public void setValueAt(Object arg0, int arg1, int arg2) {
                // prevent update to NULL
            }

        };

        JTable jTable = new JTable(tableModel);
        jTable.setPreferredSize(new Dimension(800, 350));
        final JButton jButton = new JButton();

        jTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer() {
            @Override
            public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
                    boolean hasFocus, int row, int column) {
                Component tableCellRendererComponent = super.getTableCellRendererComponent(table, value, isSelected,
                        hasFocus, row, column);
                if ("".equals(value)) {
                    // default renderer for empty cells
                    return tableCellRendererComponent;
                } else {
                    jButton.setAction(createSameActionForEditorAndRenderer(table, value));
                    return jButton;
                }
            }
        });
        jTable.setDefaultEditor(String.class, new DefaultCellEditor(new JCheckBox()) { // JCheckBox is closest to a button...

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

        });
        JOptionPane.showMessageDialog(null, jTable);
    }

    private static AbstractAction createSameActionForEditorAndRenderer(JTable table, Object value) {
        return new AbstractAction((String) value) {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                SwingUtilities.invokeLater(() -> {
                    JOptionPane.showMessageDialog(table, String.format("clicked on %s",value));
                });
                table.getCellEditor().stopCellEditing();
                table.repaint();
            }
        };
    }
}

When I click into a cell, it often shows the wrong value. Sometimes its the value of another cell from the same row or from a cell that has been previously clicked. I can't figure out why it does that?

This is because I use the same JButton instance for the Renderer as well as the Editor.

This is the change to fix it:

        //final JButton jButton = new JButton();

        jTable.setDefaultRenderer(String.class, new DefaultTableCellRenderer() {
           private final JButton jButton = new JButton();
        // rest of renderer

        jTable.setDefaultEditor(String.class, new DefaultCellEditor(new JCheckBox()) { // JCheckBox is closest to a button...
           private final JButton jButton = new JButton();
        // rest of editor

When you call

TableColumnModel columnModel = table.getColumnModel();
columnModel.getColumn(column).setCellRenderer(this);
columnModel.getColumn(column).setCellEditor(this);

in the constructor of ButtonColumn you set the CellRenderer and CellEditor for the column. The last call to this one wins and overwrites old settings for CellRenderer and CellEditor for that column.

The problem is not a five minute issue. I would create a container which can hold ButtonColumn s and can then decide, which ButtonColumn to render/edit, but it is quite tricky. A quick try to just solve the rendering can be found below:

New Inner Class RowColumn

A class for combining row and column to associate it in a Map .

static class RowColumn {
    final int row;
    final int column;

    RowColumn(int theColumn, int theRow) {
        this.column = theColumn;
        this.row = theRow;
    }

    public int getRow() {
        return row;
    }

    public int getColumn() {
        return column;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + column;
        result = prime * result + row;
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        RowColumn other = (RowColumn) obj;
        if (column != other.column)
            return false;
        if (row != other.row)
            return false;
        return true;
    }


}

New Inner Class ButtonColumnContainer

Container to hold a number of ButtonColumn s.

static class ButtonColumnContainer implements TableCellRenderer {
    Map<RowColumn, ButtonColumn> mapIntToColumn = new HashMap<>();

    ButtonColumn createButtonColumn(JTable table, int column, int row) {

        RowColumn rc = new RowColumn(column, row);
        ButtonColumn buttonColumn = new ButtonColumn(column, row);
        mapIntToColumn.put(rc, buttonColumn);

        TableColumnModel columnModel = table.getColumnModel();
        columnModel.getColumn(column).setCellRenderer(this);
        // columnModel.getColumn(column).setCellEditor(this);

        return buttonColumn;
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean hasFocus,
            int row, int column) {
        ButtonColumn buttonColumn = getButtonColumn(column, row);

        if (buttonColumn != null) {
            JButton renderButton = buttonColumn.getRenderButton();
            if (hasFocus) {
                renderButton.setForeground(table.getForeground());
                renderButton.setBackground(UIManager.getColor("Button.background"));
            } else if (selected) {
                renderButton.setForeground(table.getSelectionForeground());
                renderButton.setBackground(table.getSelectionBackground());
            } else {
                renderButton.setForeground(table.getForeground());
                renderButton.setBackground(UIManager.getColor("Button.background"));
            }

            renderButton.setText((value == null) ? "" : value.toString());
            return renderButton;
        } else {
            return null;
        }
    }

    public Component getTableCellEditorComponent(JTable table, Object value, boolean selected, int row,
            int column) {
        ButtonColumn buttonColumn = getButtonColumn(column, row);

        if (buttonColumn != null) {
            JButton editButton = buttonColumn.getEditButton();
            String text = (value == null) ? "" : value.toString();
            editButton.setText(text);
            return editButton;
        } else {
            return null;
        }

    }

    private ButtonColumn getButtonColumn(int column, int row) {
        RowColumn rowColumn = new RowColumn(column, row);
        return mapIntToColumn.get(rowColumn);
    }

}

Adapted Inner class ButtonColumn

The original class has been added a getter and setter for the JButton s editButton and renderButton

static class ButtonColumn extends AbstractCellEditor implements ActionListener {

    final JButton editButton;
    final JButton renderButton;
    String text;
    int showRow;

    public ButtonColumn(int column, int showRow) {
        super();
        this.showRow = showRow;
        renderButton = new JButton();

        editButton = new JButton();
        editButton.setFocusPainted(false);
        editButton.addActionListener(this);

    }

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

    @Override
    public void actionPerformed(ActionEvent e) {
        fireEditingStopped();

        if (text.equals("connect")) {
            System.out.println("conn");
        } else if (text.equals("disconnect")) {
            System.out.println("disc");
        }
    }

    public JButton getEditButton() {
        return editButton;
    }

    public JButton getRenderButton() {
        return renderButton;
    }



}

Instead of the initial calls to the constructor of ButtonColumn

ButtonColumnContainer container = new ButtonColumnContainer();
container.createButtonColumn(licenseTable, 2, 0);
container.createButtonColumn(licenseTable, 3, 0);
container.createButtonColumn(licenseTable, 2, 2);
container.createButtonColumn(licenseTable, 3, 2);
container.createButtonColumn(licenseTable, 4, 2);

Note that this code hasn't got the capability to edit anything, but it should solve at least the rendering problem.

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