Reputation: 67
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.
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");
}
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;
}
}
}
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;
}
}
}
public static void main(String[] args) {
ConnectionPanel con = new ConnectionPanel();
con.setVisible(true);
}
}
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 f.e. create them from the TableModel directly?
Upvotes: 0
Views: 1385
Reputation: 15634
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
Upvotes: 1
Reputation: 1679
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:
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;
}
}
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);
}
}
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.
Upvotes: 1
Reputation: 4183
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:
don't use the same instance for editor and the renderer, it may cause some painting problems
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.
Upvotes: 2