JavaFX表行更新 [英] JavaFX table row update
问题描述
我要实现的方案是
- 每当更新
TableRow
中特定的TableCell
时,行颜色将更改为红色,并且3秒钟后应自动将其恢复为原始颜色.
- Whenever a particular
TableCell
in aTableRow
gets updated , the row color will be changed to red and after 3 seconds the color should be automatically reverted to original.
以下是 MCVE ,
主班
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);
}
}
元素类:
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代码:
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 + "");
}
}
和CSS文件tableColor.css
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;
}
出什么问题了?.
-
我检查当前时间与更新发生时间之间的差异是否小于3秒-行颜色变为红色(在
ColorCounterTableCellRenderer
-updateItem
方法中)
在单独的计时器(ColorCounterTableCellRenderer
)中,我尝试检查当前时间与更新发生时间之间的时差是否超过3秒-消除红色.
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.
但是在计时器(createTimer
-方法)代码中:tr.getItem()
始终为null
,因此不能消除红色.
But in the timer (createTimer
- method) code : the tr.getItem()
is always null
and hence not removing red color.
这是实现我想要的正确方法吗?为什么tr.getItem()
返回null
.
Is this the correct way to achieve what i want? Why tr.getItem()
returns null
.
要测试:我运行了代码,等待executor
代码结束,并检查了3秒后是否去除了红色.
To test : I ran the code and waited for executor
code to end and checked whether red color is removed after 3 seconds.
推荐答案
对UI的任何更新,即使它是通过侦听器触发的,都需要在应用程序线程中完成. (您可以通过使用Platform.runLater
进行更新来解决此问题.)
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.
以下示例将上次更新的时间存储在ObservableMap
中,并使用AnimationTimer
从地图中清除过期的条目.此外,它使用TableRow
s根据地图的内容更新伪类.
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();
}
style.css
.table-row-cell:marked .table-cell.mark-column {
-fx-background-color: red;
}
这篇关于JavaFX表行更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!