First off, you must setup your context as appropriate for Swing: the TableModel must have enough knowledge/control about itself to fully comply to its notification contract. That is it must fire row-/cellUpdated or rowsInserted whenever such a change happens.
Then the basic approach to highlight changes (for a certain time) in the JTable is to
- implement a custom renderer that decorates cells which are in some storage
- configure the table with the custom renderer
- listen to changes of the model
- add the changeEvents (or a custom object with its relevant properties) to the storage that the renderer knows about
- use timers to remove the change markers after some time
SwingX simplifies (biased me :-) the rendering part by providing Highlighters and HighlightPredicates: the former do custom visual decorations when the latter decides they should be turned on. The above approach would be adjusted to
- configure the table with highlighters for visual decoration
- listen to changes in the model
- add the changed cell to a custom HighlightPredicate and configure the Highlighter with it
- use timers to remove the change markers after some time
Below is some code, the management of the timers/predicates factored into a class called ChangeDecorator: it keeps one Highlighter for decorating updated cells and one for decorating inserted rows (Note: this is an example, obviously the logic must be extended to cover updated rows :) It's fed by a modelListener with changes and updates the predicates as needed.
JXTable table = new JXTable(model);
final ChangeDecorator controller = new ChangeDecorator();
table.addHighlighter(controller.getChangeHighlighter());
TableModelListener l = new TableModelListener() {
@Override
public void tableChanged(TableModelEvent e) {
if (TableUtilities.isUpdate(e)) {
Change change = new Change(e.getFirstRow(), e.getColumn());
controller.addChange(change);
} else if (TableUtilities.isInsert(e)) {
Change change = new Change(e.getFirstRow());
controller.addChange(change);
}
}
};
model.addTableModelListener(l);
/**
* Manages the Highlighters for inserted rows/updated cells.
*/
public static class ChangeDecorator {
private List<Change> changes;
private AbstractHighlighter update;
private AbstractHighlighter insert;
private Highlighter compound;
public ChangeDecorator() {
changes = new ArrayList<>();
}
public Highlighter getChangeHighlighter() {
if (compound == null) {
update = new ColorHighlighter(new ChangePredicate(changes, true),
Color.YELLOW, null);
insert = new ColorHighlighter(new ChangePredicate(changes, false),
Color.GREEN, null);
compound = new CompoundHighlighter(update, insert);
}
return compound;
}
public void addChange(Change change) {
startTimer(change, change.isCell ? update : insert);
}
private void startTimer(final Change change, final AbstractHighlighter hl) {
changes.add(change);
hl.setHighlightPredicate(new ChangePredicate(changes, change.isCell));
ActionListener l = new ActionListener() {
boolean done;
@Override
public void actionPerformed(ActionEvent e) {
if (!done) {
done = true;
return;
}
((Timer) e.getSource()).stop();
changes.remove(change);
hl.setHighlightPredicate(new ChangePredicate(changes, change.isCell));
}
};
Timer timer = new Timer(2000, l);
timer.setInitialDelay(100);
timer.start();
}
}
/**
* A predicate enables highlighting a cell if it
* contains a change for that cell.
*/
public static class ChangePredicate implements HighlightPredicate {
private List<Change> changes;
private boolean matchCell;
public ChangePredicate(List<Change> changes, boolean matchCell) {
this.changes = new ArrayList(changes);
this.matchCell = matchCell;
}
@Override
public boolean isHighlighted(Component renderer,
ComponentAdapter adapter) {
return changes.contains(createChange(adapter));
}
private Change createChange(ComponentAdapter adapter) {
int modelRow = adapter.convertRowIndexToModel(adapter.row);
if (matchCell) {
int modelColumn =
adapter.convertColumnIndexToModel(adapter.column);;
return new Change(modelRow, modelColumn);
}
return new Change(modelRow);
}
}
/**
* A crude class encapsulating a cell change.
*
*/
public static class Change {
int row;
int column;
boolean isCell;
public Change(int row) {
this(row, -1, false);
}
public Change(int row, int col) {
this(row, col, true);
}
private Change(int row, int col, boolean update) {
this.row = row;
this.column = col;
this.isCell = update;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Change)) return false;
Change other = (Change) obj;
return row == other.row && column == other.column && isCell == other.isCell;
}
}