When I click on a table cell, and then click on a second table cell, a number of mouse and focus events occur that I do not understand. For example, clicking of cell (1, 0) and then on cell (2, 1) and then the Done button to display the sequence of events causes the following events:
1) Mouse Pressed on cell (1,0)
2) Focus Gained on cell (1,0)
3) Mouse Pressed on cell (1,0) - why(?)
4) Mouse Pressed on cell (2,1)
5) Focus Lost on cell (1,0) - why(?)
6) Focus Lost on cell (2,1)
7) Focus Gained on cell (1,0) - why(?)
8) Focus Gained on cell (2,1) - why(?)
9) Focus Lost on cell (1,0) - why(?)
10) Focus lost on cell (2,1) - why(?)
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.*;
import static javax.swing.SwingConstants.CENTER;
import static javax.swing.SwingConstants.LEFT;
import javax.swing.SwingUtilities;
import javax.swing.table.*;
public class TestFocus {
public ArrayList<String> mylog;
public int number = 0;
public TestFocus() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = createPanel();
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public JPanel createPanel() {
mylog = new ArrayList<>();
JPanel panel = new JPanel();
TestTableModel tm = new TestTableModel();
JLabel title = new JLabel("Test Table");
JTable table = new JTable(tm);
TableColumnModel tcm = table.getColumnModel();
TestTableCellEditor editor = new TestTableCellEditor();
TestTableCellRenderer renderer = new TestTableCellRenderer();
for (int i = 0; i < tm.getColumnCount(); i++) {
TableColumn column = tcm.getColumn(i);
column.setCellEditor(editor);
column.setCellRenderer(renderer);
}
JScrollPane jsp = new JScrollPane(table);
JButton btn = new JButton("Done");
btn.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
for (String s : mylog) {
System.out.println(s);
}
}
});
panel.setLayout(new BoxLayout(panel, BoxLayout.PAGE_AXIS));
panel.add(jsp);
panel.add(btn);
return panel;
}
class TestTableModel extends AbstractTableModel {
private String[] columnNames = { "Firstname", "Lastname", "Age" };
private Object[][] data = {
{ "John", "Smith", 29},
{ "Mary", "Thomas", 63},
{ "Peter", "Jones", 48} };
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
public Class getColumnClass(int col) {
return getValueAt(0, col).getClass();
}
public String getColumnClassName(int col) {
if (col == 2) {
return "Integer";
} else {
return "String";
}
}
public boolean isCellEditable(int row, int col) {
return true;
}
public void setValueAt(Object value, int row, int col) {
data[row][col] = value;
fireTableCellUpdated(row, col);
}
}
public class TestTableCellEditor extends AbstractCellEditor
implements TableCellEditor {
JComponent component = new JTextField();
#Override
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {
((JTextField)component).addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent me) {
mylog.add(++number + ") Mouse pressed: " +
value.toString() + ": r/c ("+row+"/"+column+")");
}
});
((JTextField)component).addFocusListener(new FocusListener() {
#Override
public void focusGained(FocusEvent fe) {
mylog.add(++number + ") Focus gained: " +
value.toString() + ": r/c ("+row+"/"+column+")");
}
#Override
public void focusLost(FocusEvent fe) {
mylog.add(++number + ") FocusLost: " +
value.toString() + ": r/c ("+row+"/"+column+")");
}
});
if (value != null) {
((JTextField)component).setText(value.toString());
} else {
((JTextField)component).setText("");
}
return (JTextField)component;
}
#Override
public Object getCellEditorValue() {
return ((JTextField)component).getText();
}
}
public class TestTableCellRenderer extends JLabel implements
TableCellRenderer {
public TestTableCellRenderer() {
this.setOpaque(true);
}
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, final int row, int column) {
DefaultTableCellRenderer renderer = new DefaultTableCellRenderer();
Component c = renderer.getTableCellRendererComponent(table, value,
isSelected, hasFocus, row, column);
if (hasFocus) {
c.setBackground(Color.yellow);
}
TestTableModel tm = (TestTableModel)table.getModel();
int col = table.convertColumnIndexToModel(column);
String colname = tm.getColumnName(col);
String type = tm.getColumnClassName(col);
if (type.equals("Integer") || type.equals("Int")) {
((JLabel)c).setHorizontalAlignment(CENTER);
} else { // add padding
((JLabel)c).setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 10));
((JLabel)c).setHorizontalAlignment(LEFT);
}
if (type.equals("String")) {
String text = ((JLabel)c).getText();
((JLabel)c).setToolTipText(text);
}
return c;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new TestFocus();
}
});
}
}
getTableCellEditorComponent method will be called very time the JTable gets rendered - a lot of times. Inside it you textfield.addMouseListener(). That means the listener will be added a lot of times. That's why you get many events instead of one (all these listeners are notified). In order to solve it, add the listeners only one time. You can add the listeners in constructor of this class.
For example:
public class TestTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private JTextField component;
public TestTableCellEditor() {
component = new JTextField();
component.addMouseListener(mouseListener);
component.addFocusListener(focusListener)
}
#Override
public Object getCellEditorValue() {
return component.getText();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
component.setText(value == null ? "" : String.valueOf(value));
return component;
}
}
Related
I have a JTable with JButton like this.
first pic
if I click the "+" button,the table is like this.
second pic
then, if I click the "-" button on the second line, it's ok.
now the table has only one row which was added by the "+" button.
so the question is that, I clicked the "-" button on the row,it throws an exception below:
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 1 >= 1
at java.util.Vector.elementAt(Vector.java:477)
at javax.swing.table.DefaultTableModel.setValueAt(DefaultTableModel.java:664)
what happened?
this is my code:
public class TableDeleteButtonEditor extends AbstractCellEditor implements TableCellEditor{
/**
*
*/
private static final long serialVersionUID = 1L;
JButton button;
String label;
boolean isPushed;
Vector<Vector<String>> vPartsTypeData;
DefaultTableModel dtm;
JTable partsTypeValueTable;
public TableDeleteButtonEditor(Vector<Vector<String>> vPartsTypeData, DefaultTableModel dtm, JTable partsTypeValueTable) {
// TODO Auto-generated constructor stub
//this.setClickCountToStart(1);
this.vPartsTypeData = vPartsTypeData;
this.dtm = dtm;
this.partsTypeValueTable = partsTypeValueTable;
this.vPartsTypeData = vPartsTypeData;
button = new JButton();
int selectedRow = partsTypeValueTable.getSelectedRow();
System.out.println("selectedRow:"+selectedRow);
System.out.println("Count:"+vPartsTypeData.size());
button.addActionListener(new deleteButtonListener());
}
public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected,int row, int column) {
if (isSelected) {
button.setFont(new Font("Arial",Font.PLAIN,30));
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
} else {
button.setFont(new Font("Arial",Font.PLAIN,30));
button.setForeground(table.getForeground());
button.setBackground(table.getBackground());
}
label = (value == null) ? "" : value.toString();
button.setText(label);
isPushed = true;
return button;
}
public Object getCellEditorValue() {
if (isPushed) {
}
isPushed = false;
return new String(label);
}
public boolean stopCellEditing() {
isPushed = false;
return super.stopCellEditing();
}
public class deleteButtonListener implements ActionListener
{
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
System.out.println("-----");
int selectedRow = partsTypeValueTable.getSelectedRow();
//System.out.println("selectedRow:"+selectedRow);
//System.out.println("Count:"+vPartsTypeData.size());
dtm.removeRow(selectedRow-1);
//vPartsTypeData.remove(partsTypeValueTable.getSelectedRow());
System.out.println("tableCount:"+partsTypeValueTable.getRowCount());
//dtm.fireTableChanged(null);
partsTypeValueTable.setModel(dtm);
SwingUtilities.invokeLater(new Runnable() {
public void run() {
partsTypeValueTable.repaint();
partsTypeValueTable.validate();
partsTypeValueTable.updateUI();
dtm.fireTableDataChanged();
}
});
}
}
}
So, a little bit of a look at the stack trace can lead us to a better understanding of what's going on...
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 1 >= 1
at java.util.Vector.elementAt(Vector.java:477)
at javax.swing.table.DefaultTableModel.setValueAt(DefaultTableModel.java:664)
at javax.swing.JTable.setValueAt(JTable.java:2741)
at javax.swing.JTable.editingStopped(JTable.java:4723)
at javax.swing.AbstractCellEditor.fireEditingStopped(AbstractCellEditor.java:141)
at javax.swing.AbstractCellEditor.stopCellEditing(AbstractCellEditor.java:85)
So, basically stopCellEditing is triggering a call to setValueAt, passing in the editing row, column and the result of getCellEditorValue, but, since this occurs AFTER the ActionListener has removed the row from the TableModel, it breaks, as the row, which setValueAt is trying to update simply no longer exists (or worse is a different row altogether).
It's not the responsibility of the TableCellEditor to modify the TableModel, instead, it should be reporting back to the TableModel a state value which it can use to make decisions about what it should do.
A simplified version of the editor might look something like...
public class TableDeleteButtonEditor extends AbstractCellEditor implements TableCellEditor {
/**
*
*/
private static final long serialVersionUID = 1L;
JButton button;
boolean isPushed;
public TableDeleteButtonEditor() {
button = new JButton();
button.addActionListener(new DeleteButtonListener());
}
public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, int row, int column) {
if (isSelected) {
button.setFont(new Font("Arial", Font.PLAIN, 30));
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
} else {
button.setFont(new Font("Arial", Font.PLAIN, 30));
button.setForeground(table.getForeground());
button.setBackground(table.getBackground());
}
button.setText((value == null) ? "" : value.toString());
isPushed = false;
return button;
}
public Object getCellEditorValue() {
return isPushed;
}
public class DeleteButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
isPushed = true;
stopCellEditing();
}
}
}
The core functionality of the editor simply revolves around the state of the isPushed value.
The TableModel now needs to inspect this value when setValueAt is called and respond to it
#Override
public void setValueAt(Object aValue, int row, int column) {
if (column == 0 && (aValue instanceof Boolean)) {
boolean pushed = (boolean) aValue;
if (pushed) {
removeRow(row);
}
}
}
And viola, row is now deleted and everybody is happy.
Runnable example...
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableColumn;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
DefaultTableModel model = new DefaultTableModel(new String[]{"A"}, 0) {
#Override
public void setValueAt(Object aValue, int row, int column) {
if (column == 0 && (aValue instanceof Boolean)) {
boolean pushed = (boolean) aValue;
if (pushed) {
removeRow(row);
}
}
}
};
model.addRow(new Object[]{"-"});
model.addRow(new Object[]{"-"});
JTable table = new JTable(model);
TableColumn column = table.getColumnModel().getColumn(0);
column.setCellEditor(new TableDeleteButtonEditor());
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(table));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TableDeleteButtonEditor extends AbstractCellEditor implements TableCellEditor {
/**
*
*/
private static final long serialVersionUID = 1L;
JButton button;
boolean isPushed;
JTable partsTypeValueTable;
public TableDeleteButtonEditor() {
button = new JButton();
button.addActionListener(new DeleteButtonListener());
}
public Component getTableCellEditorComponent(final JTable table, Object value, boolean isSelected, int row, int column) {
partsTypeValueTable = table;
if (isSelected) {
button.setFont(new Font("Arial", Font.PLAIN, 30));
button.setForeground(table.getSelectionForeground());
button.setBackground(table.getSelectionBackground());
} else {
button.setFont(new Font("Arial", Font.PLAIN, 30));
button.setForeground(table.getForeground());
button.setBackground(table.getBackground());
}
button.setText((value == null) ? "" : value.toString());
isPushed = false;
return button;
}
public Object getCellEditorValue() {
return isPushed;
}
public class DeleteButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
isPushed = true;
stopCellEditing();
}
}
}
}
As I said, I don't like this approach, it's a personally thing, but as a user, I find it frustrating and much prefer something more like this for example.
However, you might like to also have a look at Table Button Column for another approach
I tried to find here for a long time answer for my question but without the exact result i expected.
I have JTable which every time i am changing values in entire column (only in one column every time).
I want to listen to a table changes and when data changes in the column, the color in the column will be changed too and all other columns will be in the default color.
This is the code for the table listener:
Class CustomCellRenderer extends DefaultTableCellRenderer {
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component rendererComp = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
table.getModel().addTableModelListener(new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if(***here i want to know which column changed or something like that***){
rendererComp.setBackground(Color.CYAN);
}
}
});
return rendererComp ;
}
}
and this is the code for the table creation:
private void createTable() {
tablePanel.setLayout(new FlowLayout(FlowLayout.LEFT));
DefaultTableModel tableModel = new DefaultTableModel(){
#Override
public boolean isCellEditable(int row, int column) {
//all cells false
return false;
}
};
contentTable = new JTable(tableModel);
contentTable.setGridColor(Color.LIGHT_GRAY);
for(int i=0; i<columnSize; i++) {
tableModel.addColumn("0");
}
for(int i=0; i<rawSize; i++) {
tableModel.addRow(new Object[] { "" });
}
for(int i=0; i<rawSize; i++) {
for(int j=0; j<tableModel.getRowCount(); j++) {
tableModel.setValueAt("0", j, i);
}
}
for(int i=0; i<ramSize; i++) {
contentTable.getColumnModel().getColumn(i).setCellRenderer(new CustomCellRenderer());
}
JScrollPane scrollPane = new JScrollPane(contentTable);
scrollPane.setPreferredSize(new Dimension(400, 150));
tablePanel.add(scrollPane);
}
Store the desired state in the TableModel; let the TableCellRenderer use the state to condition the view accordingly. In the example below, as soon as setValueAt() updates any cell, edited is marked true. The render, which is applied to column zero, changes the display accordingly. Note how clearEdited() invokes fireTableDataChanged() to force the table to render all cells when called in the Clear handler.
Addendum: The update below shows one approach to handling multiple columns independently. The CustomModel now contains a Map<Integer, Boolean> to store the edited state for each column to which the CustomRenderer is applied. As an aside, the CustomRenderer now invokes convertColumnIndexToModel() and sets the selection color correctly.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
/**
* #see http://stackoverflow.com/a/37439731/230513
*/
public class Test {
private void display() {
JFrame f = new JFrame("Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
CustomModel model = new CustomModel();
model.setColumnIdentifiers(new String[]{"A", "B"});
for (int i = 0; i < 16; i++) {
model.addRow(new String[]{"A:" + i, "B:" + i});
}
JTable table = new JTable(model) {
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(100, getRowHeight() * getRowCount() / 2);
}
};
table.getColumnModel().getColumn(0).setCellRenderer(new CustomRenderer(model));
table.getColumnModel().getColumn(1).setCellRenderer(new CustomRenderer(model));
f.add(new JScrollPane(table));
JPanel p = new JPanel();
p.add(new JButton(new UpdateAction("Update A", model, 0)));
p.add(new JButton(new UpdateAction("Update B", model, 1)));
p.add(new JButton(new AbstractAction("Clear") {
#Override
public void actionPerformed(ActionEvent e) {
model.clearEdited(0);
model.clearEdited(1);
}
}));
f.add(p, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
private static class CustomModel extends DefaultTableModel {
private final Map<Integer, Boolean> edited = new HashMap<>();
public boolean isEdited(int column) {
return edited.get(column) != null && edited.get(column);
}
public void clearEdited(int column) {
edited.put(column, false);
fireTableDataChanged();
}
#Override
public boolean isCellEditable(int row, int column) {
return false;
}
#Override
public void setValueAt(Object aValue, int row, int column) {
super.setValueAt(aValue, row, column);
edited.put(column, true);
}
}
private static class CustomRenderer extends DefaultTableCellRenderer {
private final CustomModel model;
public CustomRenderer(CustomModel model) {
this.model = model;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int col) {
Component c = super.getTableCellRendererComponent(
table, value, isSelected, hasFocus, row, col);
if (model.isEdited(table.convertColumnIndexToModel(col))) {
c.setBackground(Color.cyan);
} else if (isSelected) {
c.setBackground(table.getSelectionBackground());
} else {
c.setBackground(table.getBackground());
}
return c;
}
}
private static class UpdateAction extends AbstractAction {
private final CustomModel model;
private final int column;
public UpdateAction(String name, CustomModel model, int column) {
super(name);
this.model = model;
this.column = column;
}
#Override
public void actionPerformed(ActionEvent e) {
for (int i = 0; i < model.getRowCount(); i++) {
model.setValueAt(model.getValueAt(i, column), i, column);
}
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Test()::display);
}
}
Imagine I'm building an IRC client with Java and I'd like rich text in the chat view to show IRC colors and colored nicks. I'd like to build this with a JTable. I can do that, but the text is then not selectable. Making the table editable doesn't make sense.
I've also investigated:
TextArea - no rich text formatting
JEditPane - can't append, only replace which is bad performance wise
JList - can't select text
So I got a table working I just need the text to be selectable without making it editable. I'd also would only like the text contents, and none of the HTML to be copied into the clipboard upon copying the text selection.
I have tried various iterations of setRowSelectionAllowed(), setColumnSelectionEnabled() and setCellSelectionEnabled() and setSelectionMode the table model returns false for isCellEditable(). Nothing has made the text selectable.
EDIT: as per answer 1 I was wrong about text editor panes so I'm trying those solutions.
I don't know why you don't want to use a JTextPane or JEditorPane. You insert text by its document. Examples here --> How to use Editor Panes and Text Panes.
But for your purpose you can for example do something like this. I override changeSelection to selectAll text when is clicking, the cells are editable but its cellEditors are not editable.
public class JTableTest {
private final DefaultCellEditor cellEditor;
private final JTextField textfield;
private JPanel panel;
private MyTableModel tableModel = new MyTableModel();
private JTable table = new JTable() {
#Override
public TableCellEditor getCellEditor(int row, int column) {
return JTableTest.this.cellEditor;
}
#Override
public void changeSelection(
final int row, final int column, final boolean toggle, final boolean extend) {
super.changeSelection(row, column, toggle, extend);
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
if ((getCellEditor(row, column) != null && !editCellAt(row, column))) {
JTextField textfield=(JTextField)JTableTest.this.cellEditor.getComponent();
textfield.selectAll();
}
}
});
}
};
public JTableTest() {
JScrollPane scroll = new JScrollPane(table);
table.setModel(tableModel);
panel = new JPanel(new BorderLayout());
panel.add(scroll, BorderLayout.CENTER);
textfield = new JTextField();
textfield.setEditable(Boolean.FALSE);
textfield.setBorder(null);
cellEditor = new DefaultCellEditor(textfield);
tableModel.insertValue(new ItemRow("nonEditable", "Editable"));
}
private class ItemRow {
private String column1;
private String column2;
public ItemRow(String column1, String column2) {
this.column1 = column1;
this.column2 = column2;
}
public String getColumn1() {
return column1;
}
public void setColumn1(String column1) {
this.column1 = column1;
}
public String getColumn2() {
return column2;
}
public void setColumn2(String column2) {
this.column2 = column2;
}
}
private class MyTableModel extends AbstractTableModel {
public static final int COLUMN1_INDEX = 0;
public static final int COLUMN2_INDEX = 1;
private final List<ItemRow> data = new ArrayList<>();
private final String[] columnsNames = {
"Column1",
"Column2",};
private final Class<?>[] columnsTypes = {
String.class,
String.class
};
public MyTableModel() {
super();
}
#Override
public Object getValueAt(int inRow, int inCol) {
ItemRow row = data.get(inRow);
Object outReturn = null;
switch (inCol) {
case COLUMN1_INDEX:
outReturn = row.getColumn1();
break;
case COLUMN2_INDEX:
outReturn = row.getColumn2();
break;
default:
throw new RuntimeException("invalid column");
}
return outReturn;
}
#Override
public void setValueAt(Object inValue, int inRow, int inCol) {
System.out.println("Gets called ");
if (inRow < 0 || inCol < 0 || inRow >= data.size()) {
return;
}
ItemRow row = data.get(inRow);
switch (inCol) {
case COLUMN1_INDEX:
row.setColumn1(inValue.toString());
break;
case COLUMN2_INDEX:
row.setColumn2(inValue.toString());
break;
}
fireTableCellUpdated(inRow, inCol);
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return columnsTypes.length;
}
#Override
public String getColumnName(int inCol) {
return this.columnsNames[inCol];
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return this.columnsTypes[columnIndex];
}
/**
*
* #param row
*/
public void insertValue(ItemRow row) {
data.add(row);
fireTableRowsInserted(data.size() - 1, data.size() - 1);
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return true;
}
}
private static void createAndShowGUI(final Container container, final String title) {
//Create and set up the window.
JFrame frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationByPlatform(Boolean.TRUE);
frame.add(container);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI(new JTableTest().panel, "Test");
}
});
}
}
I accomplished this by enabling the editing and then making the component responsible for the edition ignore any changes. For this I created a TableCellEditor and intercepted the key types to the JTextField, the component used for editing.
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableCellEditor;
public class TableCellSelectionTest
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run()
{
new TableCellSelectionTest().initUI();
}
});
}
public void initUI()
{
JFrame frame = new JFrame();
int N = 5;
int M = 3;
Object[][] data = new Object[N][M];
for (int i = 0; i < N; ++i)
{
for (int j = 0; j < M; ++j)
{
data[i][j] = "This is the cell (" + i + ", " + j +")";
}
}
String[] columnNames = { "Column 1", "Column 2", "Column 3" };
DefaultTableModel model = new DefaultTableModel(data, columnNames);
final MyTableCellEditor editor = new MyTableCellEditor();
JTable table = new JTable(model) {
#Override
public TableCellEditor getCellEditor(int row, int column)
{
return editor;
}
};
frame.add(new JScrollPane(table), BorderLayout.CENTER);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
class MyTableCellEditor extends AbstractCellEditor implements
TableCellEditor
{
Object _value;
#Override
public Object getCellEditorValue()
{
return _value;
}
#Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column)
{
_value = value;
JTextField textField = new JTextField(_value.toString());
textField.addKeyListener(new KeyAdapter()
{
public void keyTyped(KeyEvent e) {
e.consume(); //ignores the key
}
#Override
public void keyPressed(KeyEvent e)
{
e.consume();
}});
textField.setEditable(false); //this is functionally irrelevent, makes slight visual changes
return textField;
}
}
}
I tried both the answers here... but one problem at least is that you can tell when you've entered the "editing" mode.
This might be of interest... uses a combination of Editor magic and cheeky rendering to make it look like no editing is going on: editor's click-count-to-start is set to 1, and the component (JTextPane) delivered by the editor's method does setEditable( false ).
If this tickles your fancy, you might be interested at looking at my implementation of a JTable which adjusts (perfectly, harnessing the JTextPane's powerful wrapping power) the row height to the text, for individual rows, including when you change the columns: How to wrap lines in a jtable cell?
public class SelectableNonEditableTableTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame main_frame = new JFrame();
main_frame.setPreferredSize(new Dimension(1200, 300));
main_frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ArrayList<String> nonsense = new ArrayList<String>(
Arrays.asList(
"Lorem ipsum dolor sit amet, sed dolore vivendum ut",
"pri an soleat causae doctus.",
"Alienum abhorreant mea ea",
"cum malorum diceret ei. Pri oratio invidunt consequat ne.",
"Ius tritani detraxit scribentur et",
"has detraxit legendos intellegat at",
"quo oporteat constituam ex"));
JTable example_table = new JTable(10, 4);
example_table.setRowHeight( example_table.getRowHeight() * 2 );
DefaultCellEditor cell_editor = new SelectableNonEditableCellEditor(
new JTextField());
cell_editor.setClickCountToStart(1);
example_table.setDefaultEditor(Object.class, cell_editor);
TableCellRenderer renderer = new SelectableNonEditableTableRenderer();
example_table.setDefaultRenderer(Object.class, renderer);
for (int i = 0; i < 10; i++) {
example_table.setValueAt(nonsense.get(i % nonsense.size()),
i, i % 4);
}
main_frame.getContentPane().add(new JScrollPane(example_table));
main_frame.pack();
main_frame.setVisible(true);
}
});
}
}
class SelectableNonEditableCellEditor extends DefaultCellEditor {
public SelectableNonEditableCellEditor(JTextField textField) {
super(textField);
}
public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int col) {
Component comp = super.getTableCellEditorComponent(table, value,
isSelected, row, col);
if (value instanceof java.lang.String) {
DefaultStyledDocument sty_doc = new DefaultStyledDocument();
try {
sty_doc.insertString(0, (String) value, null);
} catch (BadLocationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
JTextPane jtp_comp = new JTextPane(sty_doc);
jtp_comp.setEditable(false);
return jtp_comp;
}
return comp;
}
}
class SelectableNonEditableTableRenderer extends JTextPane implements
TableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value,
boolean isSelected, boolean hasFocus, int row, int column) {
if (value instanceof DefaultStyledDocument) {
setDocument((DefaultStyledDocument) value);
} else {
setText((String) value);
}
return this;
}
}
Maybe you can implement your own TableCellRenderer that extends JTextField in your table.
I have here a JTable with two(2) columns. The right column is an editable one while the other is not.
So, what my problem is that whenever the user changed the value of a cell, that specific cell will changed its cell color.
I wanna do this because I want to let the user know that he/she made some changes in the table.
I found this somewhere and it somehow solved my problem but 1 thing that didn't come up with my expectation is that after changing the value and clicked another cell, the color changes back to its original color. I want to let it stay until it is saved.
#Override
public Component prepareEditor(TableCellEditor editor, int data, int columns) {
Component c = super.prepareEditor(editor, data, columns);
c.setBackground(Color.RED);
return c;
}
Is it possible? If yes, please show some example.
UPDATE:
String[] columnname = {"Student Name", "Grade"};
Object[][] data = {};
gradetable = new JTable(data, columnname){
private Object[][] rowData;
public boolean isCellEditable(int data, int columns){
return columns == 1;
}
public Component prepareRenderer(TableCellRenderer r, int data, int columns){
final Component c = super.prepareRenderer(r, data, columns);
if (data % 2 == 0){
c.setBackground(Color.LIGHT_GRAY);
}
else{
c.setBackground(Color.WHITE);
}
if (isCellSelected(data, columns)){
c.setBackground(Color.ORANGE);
}
return c;
}
#Override
public Component prepareEditor(TableCellEditor editor, int data, int columns) {
Component c = super.prepareEditor(editor, data, columns);
c.setBackground(Color.RED);
return c;
}
};
gradetable.setModel(new DefaultTableModel(data, columnname));
gradetable.setPreferredScrollableViewportSize(new Dimension (350, 130));
gradetable.setFillsViewportHeight(true);
gradetable.getTableHeader().setReorderingAllowed(false);
gradetable.setGridColor(new Color(128,128,128,128));
JScrollPane jsp = new JScrollPane(gradetable);
panel3.add(jsp);
Tables use a TableCellRenderer to paint values on the screen. The editors and renderers don't actually have anything to do with each other (from a painting point of view).
So once the editor has been dismissed (accepted or cancelled), the cell is repainted using the assigned TableCellRenderer
You need to supply, in your table model, some way to determine which rows have been updated and change the state of the renderer to match.
FYI- The DefaultTableCellRenderer uses a JLabel as it's base renderer, so it is transparent by default; you will need to make it opaque to make it render properly.
Check out Using custom renderers for more details
Update with example
This is nothing more then a proof of concept. It will not meet your absolute requirements and you should take a serious look at the tutorial linked above.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;
import javax.swing.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumnModel;
import javax.swing.table.TableModel;
public class TableEdit {
public static void main(String[] args) {
new TableEdit();
}
public TableEdit() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
setLayout(new BorderLayout());
JTable table = new JTable(new MyTableModel());
table.setSurrendersFocusOnKeystroke(true);
TableColumnModel model = table.getColumnModel();
model.getColumn(1).setCellRenderer(new MyTableCellRenderer());
add(new JScrollPane(table));
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
}
public class MyData {
private String key;
private String value;
private boolean changed;
public MyData(String key, String value) {
this.key = key;
this.value = value;
this.changed = false;
}
public String getKey() {
return key;
}
public String getValue() {
return value;
}
public void setValue(String newValue) {
if (value == null ? newValue != null : !value.equals(newValue)) {
value = newValue;
changed = true;
}
}
public boolean hasChanged() {
return changed;
}
}
public class MyTableModel extends AbstractTableModel {
private List<MyData> data;
public MyTableModel() {
data = new ArrayList<>(25);
for (int index = 0; index < 5; index++) {
data.add(new MyData("A" + (index + 1), "B" + (index + 1)));
}
}
#Override
public int getRowCount() {
return data.size();
}
#Override
public int getColumnCount() {
return 2;
}
#Override
public Object getValueAt(int rowIndex, int columnIndex) {
MyData myData = data.get(rowIndex);
Object value = null;
switch (columnIndex) {
case 0:
value = myData.getKey();
break;
case 1:
value = myData.getValue();
break;
}
return value;
}
#Override
public Class<?> getColumnClass(int columnIndex) {
return String.class;
}
#Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
return columnIndex == 1;
}
public boolean hasChanged(int rowIndex) {
MyData myData = data.get(rowIndex);
return myData.hasChanged();
}
#Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
MyData myData = data.get(rowIndex);
myData.setValue(aValue == null ? null : aValue.toString());
}
}
public class MyTableCellRenderer extends DefaultTableCellRenderer {
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setOpaque(isSelected);
TableModel model = table.getModel();
if (model instanceof MyTableModel) {
MyTableModel myModel = (MyTableModel) model;
if (myModel.hasChanged(row)) {
if (!isSelected) {
setBackground(Color.RED);
setOpaque(true);
}
}
}
return this;
}
}
}
I have set a cell editor for one of the columns to be a JSpinner and it works, but when the data in the data model changes the editor i enabled before is still enabled nad it shows the value form the old row (it does not exist or it is in a different place in the changed data).
How can i make the editor for the selected cell disappear saving the entered value when the data changes?
this one could be good code for your SSCCE
import java.awt.*;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.table.*;
public class SpinnerColumn extends AbstractCellEditor implements TableCellEditor, TableCellRenderer {
private static final long serialVersionUID = 1L;
private JSpinner editSpinner, renderSpinner;
private JTable table;
private String[] list;
private Border originalBorder;
public SpinnerColumn(JTable table, int column) {
editSpinner = new JSpinner();
renderSpinner = new JSpinner();
originalBorder = editSpinner.getBorder();
editSpinner.setBorder(new LineBorder(Color.BLUE));
this.table = table;
table.getColumnModel().getColumn(column).setCellEditor(this);
}
public SpinnerColumn(String[] list, JTable table, int column) {
editSpinner = new JSpinner();
editSpinner.setModel(new SpinnerListModel(list));
renderSpinner = new JSpinner();
originalBorder = editSpinner.getBorder();
editSpinner.setBorder(new LineBorder(Color.BLUE));
this.list = list;
this.table = table;
table.getColumnModel().getColumn(column).setCellEditor(this);
}
#Override
public Object getCellEditorValue() {
return editSpinner.getValue();
}
#Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
if (list != null) {
editSpinner.setValue(list[0]);
} else {
editSpinner.setValue(0);
}
if (value != null) {
editSpinner.setValue(value);
}
return editSpinner;
}
#Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected,
boolean hasFocus, int row, int column) {
if (hasFocus) {
renderSpinner.setBorder(new LineBorder(Color.BLUE));
} else {
renderSpinner.setBorder(originalBorder);
}// *** here's where we set the spinner's value
if (value == null) {
renderSpinner.setValue(0);
} else {
int intValue = ((Integer) value).intValue();
renderSpinner.setValue(intValue);
}
return renderSpinner;
}
#Override
public boolean isCellEditable(EventObject evt) {
return true;
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("SpinnerColumn");
JPanel panel = new JPanel(new GridLayout(1, 1));
JTable table = new JTable(5, 1);
SpinnerColumn spinnerColumn = new SpinnerColumn(table, 0);
table.setDefaultRenderer(Object.class, spinnerColumn);
panel.add(table);
frame.setContentPane(panel);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
}