The scenario i am trying to achieve is,
TableCell
in a TableRow
gets updated , the row color will be changed to red and after 3 seconds the color should be automatically reverted to original. below is MCVE,
Main Class
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.stage.Stage;
public class TestProjectWin10 extends Application {
private final ObservableList<Element> data = FXCollections.observableArrayList();
public final Runnable changeValues = () -> {
int i = 0;
while (i <= 100000) {
if (Thread.currentThread().isInterrupted()) {
break;
}
data.get(0).setOccurence(System.currentTimeMillis());
data.get(0).count();
i = i + 1;
}
};
private final ExecutorService executor = Executors.newSingleThreadExecutor(runnable -> {
Thread t = new Thread(runnable);
t.setDaemon(true);
return t;
});
@Override
public void start(Stage primaryStage) {
TableView<Element> table = new TableView<>();
table.getStylesheets().add(this.getClass().getResource("tableColor.css").toExternalForm());
table.setEditable(true);
TableColumn<Element, String> nameCol = new TableColumn<>("Name");
nameCol.setPrefWidth(200);
nameCol.setCellValueFactory(cell -> cell.getValue().nameProperty());
nameCol.setCellFactory((TableColumn<Element, String> param) -> new ColorCounterTableCellRenderer(table));
table.getColumns().add(nameCol);
this.data.add(new Element());
table.setItems(this.data);
this.executor.submit(this.changeValues);
Scene scene = new Scene(table, 600, 600);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
The Element Class:
import java.util.concurrent.atomic.AtomicReference;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Element {
int x = 0;
private final StringProperty nameProperty = new SimpleStringProperty("");
private final AtomicReference<String> name = new AtomicReference<>();
private final DoubleProperty occurence = new SimpleDoubleProperty();
public void count() {
x = x + 1;
if (name.getAndSet(Integer.toString(x)) == null) {
Platform.runLater(() -> nameProperty.set(name.getAndSet(null)));
}
}
public void setOccurence(double value) {
occurence.set(value);
}
public String getName() {
return nameProperty().get();
}
public void setName(String name) {
nameProperty().set(name);
}
public StringProperty nameProperty() {
return nameProperty;
}
double getOccurrenceTime() {
return occurence.get();
}
}
CellFactory code:
import java.util.Timer;
import java.util.TimerTask;
import javafx.application.Platform;
import javafx.geometry.Pos;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableRow;
import javafx.scene.control.TableView;
public class ColorCounterTableCellRenderer extends TableCell<Element, String> {
private final static long MAX_MARKED_TIME = 3000;
private final static long UPDATE_INTERVAL = 1000;
private static Timer t = null;
private final String highlightedStyle = "highlightedRow";
private final TableView tv;
public ColorCounterTableCellRenderer(TableView tv) {
this.tv = tv;
createTimer();
setAlignment(Pos.CENTER_RIGHT);
}
private void createTimer() {
if (t == null) {
t = new Timer("Hightlight", true);
t.schedule(new TimerTask() {
@Override
public void run() {
final long currentTime = System.currentTimeMillis();
TableRow tr = getTableRow();
if (tr.getItem() != null) {
if (currentTime - ((Element) tr.getItem()).getOccurrenceTime() > MAX_MARKED_TIME) {
Platform.runLater(() -> {
tr.getStyleClass().remove(highlightedStyle);
});
}
}
}
}, 0, UPDATE_INTERVAL);
}
}
@Override
protected void updateItem(String item, boolean empty) {
if (empty || getTableRow() == null || getTableRow().getItem() == null) {
setText(null);
return;
}
long currentTime = System.currentTimeMillis();
TableRow<Element> row = getTableRow();
Element elementRow = row.getItem();
double occurrenceTime = elementRow.getOccurrenceTime();
if (currentTime - occurrenceTime < MAX_MARKED_TIME) {
if (!row.getStyleClass().contains(highlightedStyle)) {
row.getStyleClass().add(highlightedStyle);
}
}
super.updateItem(item, empty);
setText(item + "");
}
}
and the css file tableColor.css
.highlightedRow {
-fx-background-color: rgba(255,0,0, 0.25);
-fx-background-insets: 0, 1, 2;
-fx-background: -fx-accent;
-fx-text-fill: -fx-selection-bar-text;
}
What is the issue..?
I check whether difference between the current time and update happened time is less than 3 seconds - row color to red (in ColorCounterTableCellRenderer
- updateItem
method)
in a separate timer (ColorCounterTableCellRenderer
) , i try check whether difference between current time and update happened time is more than 3 seconds - Removing red color.
But in the timer (createTimer
- method) code : the tr.getItem()
is always null
and hence not removing red color.
Is this the correct way to achieve what i want? Why tr.getItem()
returns null
.
To test : I ran the code and waited for executor
code to end and checked whether red color is removed after 3 seconds.
Any updates to the UI, even if it's triggered through listeners, needs to be done from the application thread. (You can overcome this issue by doing the updates using Platform.runLater
.)
Furthermore you cannot rely on the same cell keeping the same cell for the complete time it's supposed to be shown as marked.
To overcome this issue you need to store the info about the marked cells either in the item itself or in some observable external data structure.
The following example stores the times of the last update in a ObservableMap
and uses a AnimationTimer
to clear expired entries from the map. Furthermore it uses TableRow
s to update a pseudoclass based on the contents of the map.
private static class Item {
private final IntegerProperty value = new SimpleIntegerProperty();
}
private final ObservableMap<Item, Long> markTimes = FXCollections.observableHashMap();
private AnimationTimer updater;
private void updateValue(Item item, int newValue) {
int oldValue = item.value.get();
if (newValue != oldValue) {
item.value.set(newValue);
// update time of item being marked
markTimes.put(item, System.nanoTime());
// timer for removal of entry
updater.start();
}
}
@Override
public void start(Stage primaryStage) {
Item item = new Item(); // the item that is updated
TableView<Item> table = new TableView<>();
table.getItems().add(item);
// some additional items to make sure scrolling effects can be tested
IntStream.range(0, 100).mapToObj(i -> new Item()).forEach(table.getItems()::add);
TableColumn<Item, Number> column = new TableColumn<>();
column.getStyleClass().add("mark-column");
column.setCellValueFactory(cd -> cd.getValue().value);
table.getColumns().add(column);
final PseudoClass marked = PseudoClass.getPseudoClass("marked");
table.setRowFactory(tv -> new TableRow<Item>() {
final InvalidationListener reference = o -> {
pseudoClassStateChanged(marked, !isEmpty() && markTimes.containsKey(getItem()));
};
final WeakInvalidationListener listener = new WeakInvalidationListener(reference);
@Override
protected void updateItem(Item item, boolean empty) {
boolean wasEmpty = isEmpty();
super.updateItem(item, empty);
if (empty != wasEmpty) {
if (empty) {
markTimes.removeListener(listener);
} else {
markTimes.addListener(listener);
}
}
reference.invalidated(null);
}
});
Scene scene = new Scene(table);
scene.getStylesheets().add("style.css");
primaryStage.setScene(scene);
primaryStage.show();
updater = new AnimationTimer() {
@Override
public void handle(long now) {
for (Iterator<Map.Entry<Item, Long>> iter = markTimes.entrySet().iterator(); iter.hasNext();) {
Map.Entry<Item, Long> entry = iter.next();
if (now - entry.getValue() > 2_000_000_000L) { // remove after 1 sec
iter.remove();
}
}
// pause updates, if there are no entries left
if (markTimes.isEmpty()) {
stop();
}
}
};
final Random random = new Random();
Thread t = new Thread(() -> {
while (true) {
try {
Thread.sleep(4000);
} catch (InterruptedException ex) {
continue;
}
Platform.runLater(() -> {
updateValue(item, random.nextInt(4));
});
}
});
t.setDaemon(true);
t.start();
}
.table-row-cell:marked .table-cell.mark-column {
-fx-background-color: red;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With