How to Maintain Jtable Cell Rendering After Cell Edit

How to maintain JTable cell rendering after cell edit

When your editor concludes, the table's editingStopped() method collects the new value via getCellEditorValue() and uses it to setValueAt() in the model. The model, in turn, should fireTableCellUpdated(), which will invoke the prescribed renderer. Extending the default should be enough to handle Number formatting. In other cases, it may be convenient to use an instance of your renderer as your editor component; this example shows a typical implementation.

Addendum: Here's a basic example using the default editor and renderer implementations.

Addendum: Thanks to helpful comments from @mKorbel, I've updated the example to select the cell's text for editing, as described in @camickr's article Table Select All Editor.

RenderEditNumber

package overflow;

import java.awt.Component;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.event.MouseEvent;
import java.text.NumberFormat;
import java.util.EventObject;
import javax.swing.DefaultCellEditor;
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.table.DefaultTableCellRenderer;
import javax.swing.table.DefaultTableModel;
import javax.swing.text.JTextComponent;

/** @see http://stackoverflow.com/a/10067560/230513 */
public class RenderEditNumber extends JPanel {

private NumberFormat nf = NumberFormat.getCurrencyInstance();

public RenderEditNumber() {
DefaultTableModel model = new DefaultTableModel(
new String[]{"Amount"}, 0) {

@Override
public Class<?> getColumnClass(int columnIndex) {
return Double.class;
}
};
for (int i = 0; i < 16; i++) {
model.addRow(new Object[]{Double.valueOf(i)});
}
JTable table = new JTable(model) {

@Override // Always selectAll()
public boolean editCellAt(int row, int column, EventObject e) {
boolean result = super.editCellAt(row, column, e);
final Component editor = getEditorComponent();
if (editor == null || !(editor instanceof JTextComponent)) {
return result;
}
if (e instanceof MouseEvent) {
EventQueue.invokeLater(new Runnable() {

@Override
public void run() {
((JTextComponent) editor).selectAll();
}
});
} else {
((JTextComponent) editor).selectAll();
}
return result;
}
};
table.setPreferredScrollableViewportSize(new Dimension(123, 123));
table.setDefaultRenderer(Double.class, new CurrencyRenderer(nf));
table.setDefaultEditor(Double.class, new CurrencyEditor(nf));
this.add(new JScrollPane(table));
}

private static class CurrencyRenderer extends DefaultTableCellRenderer {

private NumberFormat formatter;

public CurrencyRenderer(NumberFormat formatter) {
this.formatter = formatter;
this.setHorizontalAlignment(JLabel.RIGHT);
}

@Override
public void setValue(Object value) {
setText((value == null) ? "" : formatter.format(value));
}
}

private static class CurrencyEditor extends DefaultCellEditor {

private NumberFormat formatter;
private JTextField textField;

public CurrencyEditor(NumberFormat formatter) {
super(new JTextField());
this.formatter = formatter;
this.textField = (JTextField) this.getComponent();
textField.setHorizontalAlignment(JTextField.RIGHT);
textField.setBorder(null);
}

@Override
public Object getCellEditorValue() {
try {
return new Double(textField.getText());
} catch (NumberFormatException e) {
return Double.valueOf(0);
}
}

@Override
public Component getTableCellEditorComponent(JTable table,
Object value, boolean isSelected, int row, int column) {
textField.setText((value == null)
? "" : formatter.format((Double) value));
return textField;
}
}

private void display() {
JFrame f = new JFrame("RenderEditNumber");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}

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

@Override
public void run() {
new RenderEditNumber().display();
}
});
}
}

JTable: How to update cell using custom editor by pop-up input dialog box?

I am The author of this question, and I solved it. I will provide the solution so that others can get help from it.

I was trying to write a custom renderer and a custom editor to use with JTable.
The renderer simply uses JLabel to display data. It is already the standard component for JTable.
The editor is a dialogue box that appears when clicking on the cell that I want to edit.

Here is the solution:

The classes that will remain unchanged are the three classes from Oracle + my custom renderer class

1.TableDialogEditDemo.java.

2.ColorEditor.java

3.ColorRenderer.java

4.CellStringRenderer class provided above in the question body (my class)

The class that will be updated is the custom editor class (I changed its name from "CellStringEditor" to "DialogStringEditor"

import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.AbstractCellEditor;
import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JTable;
import javax.swing.table.TableCellEditor;

public class DialogStringEditor extends AbstractCellEditor
implements TableCellEditor,
ActionListener
{

String newInput;
String oldValue;
JButton button;
static final String EDIT = "edit";

public DialogStringEditor()
{
button = new JButton();
button.setBackground(Color.WHITE);
button.setActionCommand(EDIT);
button.addActionListener(this);
button.setBorderPainted(false);
}

@Override
public void actionPerformed(ActionEvent e)
{
if (EDIT.equals(e.getActionCommand()))
{
newInput = JOptionPane.showInputDialog("Edit", oldValue);
if (newInput == null)
{
newInput = oldValue;
}
fireEditingStopped();
}
}

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

@Override
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int row,
int column)
{
newInput = (String) value;
oldValue = (String) value;
return button;
}
}

This will work ok

You can also make a little update for the renderer class "CellStringRenderer" to control how are selction and unselection colors of the cell appear.

update this method:

    @Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
{
String stringValue = (String) value;
this.setText(stringValue);

if (isSelected)
{
this.setBackground(table.getSelectionBackground());
this.setForeground(table.getSelectionForeground());
} else
{
this.setBackground(table.getBackground());
this.setForeground(table.getForeground());
}

return this;
}
}

Regards.

JTable custom cell renderer focus problem

So your problem is, that you are still editing the cell. So you have to stop the editing and then the cell will be changed.

At your button you can get the cell who is being edited with

TableCellEditor cellEditor = table.getCellEditor();
then you can stop the editing with

if(cellEditor!=null){

cellEditor.stopCellEditing();

}

and then you can save the value

Overriding JTable's DefaultTableCellRenderer to center all the cells in a JTable

For applying the same visual decoration to all rendering components (if that's what you really want, be careful, it might have an usability penalty!) you can override the JTable's preparedRenderer method:

@Override
public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
Component comp = super.prepareRenderer(...);
if (comp instanceof JLabel) {
((JLabel) comp).setHorizontalAlignment(...);
}
return comp;
}

BTW: this approach is violating the rule to not subclass JSomething for application needs. You might consider using SwingX which formally supports visually decorating rendering components. So instead of subclassing, you would register a Highlighter with the table:

JXTable table = ...
table.addHighlighter(new AlignmentHighlighter(CENTER), HighlightPredicate.ALWAYS);

How to bypass calling the JTable renderer

One issue is probably this:

Component c = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
// ...
Color curColor = c.getBackground();

super.getTableCellRendererComponent will call the getTableCellRendererComponent method of DefaultTableCellRenderer. DefaultTableCellRenderer will never set the background of the renderer component to yellow. (Well, in theory, it could, if Swing were using a look-and-feel that reads user desktop preferences, and the user had set those preferences so that button backgrounds are yellow.)

But it doesn’t matter, in this case, since it’s best for a cell renderer to use state information rather than existing appearance.

Components can be painted (and thus their cell renderers called) for many reasons, including something as simple as the user moving the mouse over the component. There is no reliable way to know what exactly will cause it. All you can do is be prepared for the renderer to be called at any time.

You should store the matching rows in a private field, independent of table appearance. Something like:

public class ColorChange extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1;

private final Collection<Integer> matchingRows;

public ColorChange(Collection<Integer> matchingRows) {
this.matchingRows = matchingRows;
}

@Override
public Component getTableCellRendererComponent(JTable table,
Object value, boolean isSelected, boolean hasFocus,
int row, int column) {

Component c = super.getTableCellRendererComponent(table,
value, isSelected, hasFocus, row, column);

if (column != 7) {
this.setHorizontalAlignment(SwingConstants.CENTER);
} else {
this.setHorizontalAlignment(SwingConstants.LEFT);
}

if (!isSelected) {
if (matchingRows.contains(row)) {
c.setBackground(Color.YELLOW);
} else {
c.setBackground(null);
}
}

return c;
}
}

Notice that if a row is selected, you should let the JTable’s selection color remain in effect.

To make use of the above renderer, you would maintain the matching rows by listening for table model changes:

private final Collection<Integer> matchingRows = new HashSet<>();

// ...

table.setDefaultRenderer(Object.class, new ColorChange(matchingRows));

table.getModel().addTableModelListener(event -> {
int type = event.getType();
int column = event.getColumn();
TableModel model = (TableModel) event.getSource();
int firstRow = event.getFirstRow();
int lastRow = event.getLastRow();

if (firstRow == TableModelEvent.HEADER_ROW) {
table.repaint();
return;
}

if (type == TableModelEvent.UPDATE) {
if ((column == 4 || column == 5) && firstRow == lastRow) {
int editedRow = firstRow;
Object edMainC = model.getValueAt(editedRow, 4);
Object edSubC = model.getValueAt(editedRow, 5);

matchingRows.clear();
int count = model.getRowCount();
for (int row = 0; row < count; row++) {
if (row != editedRow) {
Object mainC = model.getValueAt(row, 4);
Object subC = model.getValueAt(row, 5);
if (Objects.equals(mainC, edMainC) ||
Objects.equals(subC, edSubC)) {

matchingRows.add(row);
}
}
}
}
} else if (type == TableModelEvent.INSERT) {
int start = Math.min(firstRow, lastRow);
int count = Math.abs(lastRow - firstRow) + 1;

List<Integer> newRows = new ArrayList<>(matchingRows);
newRows.replaceAll(row -> row < start ? row : row + count);

matchingRows.clear();
matchingRows.addAll(newRows);
} else if (type == TableModelEvent.DELETE) {
int start = Math.min(firstRow, lastRow);
int end = Math.max(firstRow, lastRow);
int count = end - start + 1;

List<Integer> newRows = new ArrayList<>(matchingRows);
newRows.removeIf(row -> row >= start && row <= end);
newRows.replaceAll(row -> row <= end ? row : row - count);

matchingRows.clear();
matchingRows.addAll(newRows);
}

table.repaint();
});

The repaint() is needed since every row needs to be (potentially) redrawn, not just the row(s) affected by the TableModelEvent, after an edit is committed.

Change JTable cell colours without clicking

The last arguments to the method are row and column. To get the required result, the renderer must take those values into account.

Dynamically change JTable cell value at focus lost

getCurrencyEquivalent() is a method that returns a formatted version of a given String in a different String format.

Don't use a FocusListener to try to change the format of the data when it is edited.

Instead, you should be using a custom renderer to format the data. Check out Table Format Renderers for an easy example of how you can do this.

Custom cells on a JTable returning wrong values (and not displaying correctly)

Given your code is highly customized and hard to debug without all the pieces put together, I'd start from the scratch appealing to the basic concepts and saying that these requirements can be satisfied working with the TableModel:

  • Verify input of number columns: this can be delegated to the right editor if getColumnClass(...) in our table model returns the appropriate class. No custom editor needed!
  • Update amt column on qty or price columns update: this also can be done working with the table model, just need to override setValueAt(...) correctly.
  • Display price and amt columns with currency format: this can be done providing a custom renderer (not editor).

Since there's no info about your table model I'll illustrate the points above using DefaultTableModel as follows:

String[] header = new String[] { "qty", "price", "amt" };
DefaultTableModel model = new DefaultTableModel(header, 1) {

@Override
public Class<?> getColumnClass(int columnIndex) {
switch (columnIndex) {
case 0: return Integer.class;
case 1:
case 2: return Double.class;
}
throw new ArrayIndexOutOfBoundsException(columnIndex);
}

@Override
public boolean isCellEditable(int row, int column) {
return column < 2;
}

@Override
public void setValueAt(Object aValue, int row, int column) {
super.setValueAt(aValue, row, column);
if (column < 2) {
Integer qty = (Integer)getValueAt(row, 0);
Double price = (Double)getValueAt(row, 1);
Double amt = (qty != null && price != null)
? (Double)(qty * price)
: null;
super.setValueAt(amt, row, 2);
}
}
};

Some notes about the example:

  1. In this example I've only set the three columns that actually matter: qty, price and amt.
  2. Only qty and price columns are editable while amt is not editable (it's calculated when the other columns are updated).
  3. Given the getColumnClass() implementation, the default editor won't allow invalid inputs for none of the number columns: whether column class is Integer it will allow only integer values. The same applies for Double class.
  4. If either qty or price column is modified then setValueAt(...) is invoked and amt column is also updated accordingly.

Finally to apply the currency format when the cell is rendered (not being edited) we need to provide a custom renderer. For example:

class CurrencyRenderer extends DefaultTableCellRenderer {

private final NumberFormat currencyFormat;

public CurrencyRenderer() {
currencyFormat = NumberFormat.getCurrencyInstance();
}

@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
Component renderer = super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
if (value instanceof Number) {
setText(currencyFormat.format((Number)value));
}
return renderer;
}
}

Now, there are different ways to provide a renderer, all explained here: Concepts: Editors and Renderers



Related Topics



Leave a reply



Submit