使用 tableview 的正确方法是什么?
What is right way to work with tableview?
一段时间以来,我一直试图让我的 tableview 像一种由后台线程更新的电子表格一样工作,当单元格更新时,它会高亮显示几秒钟(更改样式)然后继续回到原来的风格。
我已经知道,我不能直接在 table 单元格中存储和设置样式,我需要某种支持 class 来保存这些数据。但是 tableview 及其 "reusing" 个单元格(对不同数据使用相同的单元格)的行为真的很奇怪。当所有单元格都适合屏幕时,它对我来说完美无瑕,但是一旦我放置了大约 100 个单元格并且它变得可滚动,它就开始出现错误,有时样式(或设置的图形)消失并且在滚动后出现,如果我禁用一些顶部视图单元格,滚动后的其他一些单元格也会被禁用,依此类推。有什么正确的方法吗?
我需要的基本上是
Background data thread ---updates--> tableview
Another thread --after few seconds removes style--> tableview
正如我现在所拥有的,我有一个模型 class,它包含数据、样式和对 table 单元格的引用(我禁用了排序,所以它应该没问题)和背景线程更新模型 class 中的数据,并且该模型 class 更改引用单元格的样式并在 "style remover" 线程中注册自己,然后 while 删除样式。
我认为发布我的实际代码不会有用,因为一旦我发现单元格正在被重用,我的代码就会变得太复杂并且有点不可读,所以我想以正确的方式完全重做。
性能对我来说不是那么重要,不会超过 100 个单元格,但是 table 视图中的突出显示和按钮必须完美无缺。
这就是我的应用程序现在的样子 - 了解我需要什么。
编辑:这是 link 我的 相关的。
合作者:
- 在数据方面,一个(视图)模型有一个 recentlyChanged 属性,只要值改变就会更新
- 在视图方面,一个自定义单元格监听 recentlyChanged 属性 并根据需要更新其样式
棘手的部分是在重新使用或不使用时清理单元格状态:总是(希望!)调用的方法是 cell.updateIndex(int newIndex)
,所以这就是 un-/register听众。
下面是一个可运行的(虽然很粗糙;)示例
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import de.swingempire.fx.util.FXUtils;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TableCoreRecentlyChanged extends Application {
public static class RecentChanged extends TableCell<Dummy, String> {
private ChangeListener<Boolean> recentListener = (src, ov, nv) -> updateRecentStyle(nv);
private Dummy lastDummy;
/*
* Just to see any effect.
*/
protected void updateRecentStyle(boolean highlight) {
if (highlight) {
setStyle("-fx-background-color: #99ff99");
} else {
setStyle("-fx-background-color: #009900");
}
}
@Override
public void updateIndex(int index) {
if (lastDummy != null) {
lastDummy.recentlyChangedProperty().removeListener(recentListener);
lastDummy = null;
}
updateRecentStyle(false);
super.updateIndex(index);
if (getTableRow() != null && getTableRow().getItem() != null) {
lastDummy = getTableRow().getItem();
updateRecentStyle(lastDummy.recentlyChangedProperty().get());
lastDummy.recentlyChangedProperty().addListener(recentListener);
}
}
@Override
protected void updateItem(String item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else {
super.setText(item);
super.setGraphic(null);
}
}
}
private Parent getContent() {
TableView<Dummy> table = new TableView<>(createData(50));
table.setEditable(true);
TableColumn<Dummy, String> column = new TableColumn<>("Value");
column.setCellValueFactory(c -> c.getValue().valueProperty());
column.setCellFactory(e -> new RecentChanged());
column.setMinWidth(200);
table.getColumns().addAll(column);
int editIndex = 20;
Button changeValue = new Button("Edit");
changeValue.setOnAction(e -> {
Dummy dummy = table.getItems().get(editIndex);
dummy.setValue(dummy.getValue()+"x");
});
HBox buttons = new HBox(10, changeValue);
BorderPane content = new BorderPane(table);
content.setBottom(buttons);
return content;
}
private ObservableList<Dummy> createData(int size) {
return FXCollections.observableArrayList(
Stream.generate(Dummy::new)
.limit(size)
.collect(Collectors.toList()));
}
private static class Dummy {
private static int count;
ReadOnlyBooleanWrapper recentlyChanged = new ReadOnlyBooleanWrapper() {
Timeline recentTimer;
@Override
protected void invalidated() {
if (get()) {
if (recentTimer == null) {
recentTimer = new Timeline(new KeyFrame(
Duration.millis(2500),
ae -> set(false)));
}
recentTimer.playFromStart();
} else {
if (recentTimer != null) recentTimer.stop();
}
}
};
StringProperty value = new SimpleStringProperty(this, "value", "initial " + count++) {
@Override
protected void invalidated() {
recentlyChanged.set(true);
}
};
public StringProperty valueProperty() {return value;}
public String getValue() {return valueProperty().get(); }
public void setValue(String text) {valueProperty().set(text); }
public ReadOnlyBooleanProperty recentlyChangedProperty() { return recentlyChanged.getReadOnlyProperty(); }
public String toString() {return "[dummy: " + getValue() + "]";}
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
// primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableCoreRecentlyChanged.class.getName());
}
一段时间以来,我一直试图让我的 tableview 像一种由后台线程更新的电子表格一样工作,当单元格更新时,它会高亮显示几秒钟(更改样式)然后继续回到原来的风格。 我已经知道,我不能直接在 table 单元格中存储和设置样式,我需要某种支持 class 来保存这些数据。但是 tableview 及其 "reusing" 个单元格(对不同数据使用相同的单元格)的行为真的很奇怪。当所有单元格都适合屏幕时,它对我来说完美无瑕,但是一旦我放置了大约 100 个单元格并且它变得可滚动,它就开始出现错误,有时样式(或设置的图形)消失并且在滚动后出现,如果我禁用一些顶部视图单元格,滚动后的其他一些单元格也会被禁用,依此类推。有什么正确的方法吗?
我需要的基本上是
Background data thread ---updates--> tableview
Another thread --after few seconds removes style--> tableview
正如我现在所拥有的,我有一个模型 class,它包含数据、样式和对 table 单元格的引用(我禁用了排序,所以它应该没问题)和背景线程更新模型 class 中的数据,并且该模型 class 更改引用单元格的样式并在 "style remover" 线程中注册自己,然后 while 删除样式。
我认为发布我的实际代码不会有用,因为一旦我发现单元格正在被重用,我的代码就会变得太复杂并且有点不可读,所以我想以正确的方式完全重做。
性能对我来说不是那么重要,不会超过 100 个单元格,但是 table 视图中的突出显示和按钮必须完美无缺。
这就是我的应用程序现在的样子 - 了解我需要什么。
编辑:这是 link 我的
合作者:
- 在数据方面,一个(视图)模型有一个 recentlyChanged 属性,只要值改变就会更新
- 在视图方面,一个自定义单元格监听 recentlyChanged 属性 并根据需要更新其样式
棘手的部分是在重新使用或不使用时清理单元格状态:总是(希望!)调用的方法是 cell.updateIndex(int newIndex)
,所以这就是 un-/register听众。
下面是一个可运行的(虽然很粗糙;)示例
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import de.swingempire.fx.util.FXUtils;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.beans.property.ReadOnlyBooleanProperty;
import javafx.beans.property.ReadOnlyBooleanWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Duration;
public class TableCoreRecentlyChanged extends Application {
public static class RecentChanged extends TableCell<Dummy, String> {
private ChangeListener<Boolean> recentListener = (src, ov, nv) -> updateRecentStyle(nv);
private Dummy lastDummy;
/*
* Just to see any effect.
*/
protected void updateRecentStyle(boolean highlight) {
if (highlight) {
setStyle("-fx-background-color: #99ff99");
} else {
setStyle("-fx-background-color: #009900");
}
}
@Override
public void updateIndex(int index) {
if (lastDummy != null) {
lastDummy.recentlyChangedProperty().removeListener(recentListener);
lastDummy = null;
}
updateRecentStyle(false);
super.updateIndex(index);
if (getTableRow() != null && getTableRow().getItem() != null) {
lastDummy = getTableRow().getItem();
updateRecentStyle(lastDummy.recentlyChangedProperty().get());
lastDummy.recentlyChangedProperty().addListener(recentListener);
}
}
@Override
protected void updateItem(String item, boolean empty) {
if (item == getItem()) return;
super.updateItem(item, empty);
if (item == null) {
super.setText(null);
super.setGraphic(null);
} else {
super.setText(item);
super.setGraphic(null);
}
}
}
private Parent getContent() {
TableView<Dummy> table = new TableView<>(createData(50));
table.setEditable(true);
TableColumn<Dummy, String> column = new TableColumn<>("Value");
column.setCellValueFactory(c -> c.getValue().valueProperty());
column.setCellFactory(e -> new RecentChanged());
column.setMinWidth(200);
table.getColumns().addAll(column);
int editIndex = 20;
Button changeValue = new Button("Edit");
changeValue.setOnAction(e -> {
Dummy dummy = table.getItems().get(editIndex);
dummy.setValue(dummy.getValue()+"x");
});
HBox buttons = new HBox(10, changeValue);
BorderPane content = new BorderPane(table);
content.setBottom(buttons);
return content;
}
private ObservableList<Dummy> createData(int size) {
return FXCollections.observableArrayList(
Stream.generate(Dummy::new)
.limit(size)
.collect(Collectors.toList()));
}
private static class Dummy {
private static int count;
ReadOnlyBooleanWrapper recentlyChanged = new ReadOnlyBooleanWrapper() {
Timeline recentTimer;
@Override
protected void invalidated() {
if (get()) {
if (recentTimer == null) {
recentTimer = new Timeline(new KeyFrame(
Duration.millis(2500),
ae -> set(false)));
}
recentTimer.playFromStart();
} else {
if (recentTimer != null) recentTimer.stop();
}
}
};
StringProperty value = new SimpleStringProperty(this, "value", "initial " + count++) {
@Override
protected void invalidated() {
recentlyChanged.set(true);
}
};
public StringProperty valueProperty() {return value;}
public String getValue() {return valueProperty().get(); }
public void setValue(String text) {valueProperty().set(text); }
public ReadOnlyBooleanProperty recentlyChangedProperty() { return recentlyChanged.getReadOnlyProperty(); }
public String toString() {return "[dummy: " + getValue() + "]";}
}
@Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setScene(new Scene(getContent()));
// primaryStage.setTitle(FXUtils.version());
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(TableCoreRecentlyChanged.class.getName());
}