【问题标题】:Lazy-loading FXProperties延迟加载 FXProperties
【发布时间】:2018-08-08 18:43:47
【问题描述】:

这是here的后续报道。

我正在实现一个表格,它将数据异步加载到表格单元格中。问题是,表格单元格有时不能正确更新。有时它会以某种方式“挂起”并永远显示“Loading..”。只有当我在表格中滚动一点时,实际值才会更新。

要重现,运行应用程序并在表格中快速向下滚动。有些单元格不会显示“延迟加载”值,而是显示占位符字符串。

延迟加载属性如下所示:

public abstract class LazyLoadingStringProperty extends SimpleStringProperty {

public static final String DEFAULT_LOADING_STRING = "Loading..";
private static final ExecutorService exe = Executors.newCachedThreadPool();
private String loadingString = DEFAULT_LOADING_STRING;

private boolean loaded = false;

public LazyLoadingStringProperty() {

}

public boolean isLoaded() {
    return loaded;
}

public void setLoaded(final boolean loaded) {
    this.loaded = loaded;
}

public String getLoadingString() {
    return loadingString;
}

public void setLoadingString(final String loadingString) {
    this.loadingString = loadingString;
}

@Override
public String getValue() {
    if (!loaded) {
        Platform.runLater(() -> startLoadingService());
        return loadingString;
    }
    return super.getValue();
}

protected void startLoadingService() {

    final Service<String> s = new Service<String>() {

        @Override
        protected Task<String> createTask() {
            return LazyLoadingStringProperty.this.createTask();
        }
    };

    s.setExecutor(exe);

    s.setOnFailed(e -> {
        setLoaded(true);
        setValue(s.getException().getLocalizedMessage());

    });

    s.setOnSucceeded(e -> {
        setLoaded(true);
        setValue(s.getValue());

    });
    s.start();
    // System.err.println("Started");
}

protected abstract Task<String> createTask();

}

应用程序如下所示:

public class ExampleTable extends Application {

    private static final int NUM_ELEMENTS = 500;

    private final TableView<ExampleBean> table = new TableView<>();

    private final ObservableList<ExampleBean> data = FXCollections.observableArrayList();

    public static void main(final String[] args) {
        launch(args);
    }

    @Override
    public void start(final Stage stage) {
        final Scene scene = new Scene(new Group());
        stage.setTitle("Table View Sample");
        stage.setWidth(300);
        stage.setHeight(500);

        final TableColumn<ExampleBean, String> c1 = new TableColumn<>("A");
        c1.setCellValueFactory(new PropertyValueFactory<ExampleBean, String>("p1"));
        final TableColumn<ExampleBean, String> c2 = new TableColumn<>("B");
        c2.setCellValueFactory(new PropertyValueFactory<ExampleBean, String>("p2"));
        final TableColumn<ExampleBean, String> c3 = new TableColumn<>("C");
        c3.setCellValueFactory(new PropertyValueFactory<ExampleBean, String>("p3"));

        c1.setPrefWidth(100);
        c2.setPrefWidth(100);
        c3.setPrefWidth(100);


        for (int i = 0; i < NUM_ELEMENTS; i++) {
            data.add(new ExampleBean());
        }

        final ScrollPane sp = new ScrollPane();
        sp.setContent(table);
        sp.setMaxHeight(Double.POSITIVE_INFINITY);
        sp.setMaxWidth(Double.POSITIVE_INFINITY);
        sp.setFitToHeight(true);
        sp.setFitToWidth(true);

        table.setItems(data);
        // table.setMaxHeight(Double.POSITIVE_INFINITY);
        // table.setMaxWidth(Double.POSITIVE_INFINITY);
        table.getColumns().addAll(c1, c2, c3);

        final VBox vbox = new VBox();
        vbox.setSpacing(5);
        VBox.setVgrow(sp, Priority.ALWAYS);
        vbox.getChildren().addAll(sp);

        scene.setRoot(vbox);


        stage.setScene(scene);
        stage.show();
    }
}

完整的可运行代码可以在here找到。

【问题讨论】:

  • 我认为缺少的是您需要在onSucceeded()onFailed() 方法中调用fireValueChangedEvent(),以便在有新值可用时通知听众。
  • 这是/应该在setValue()方法中完成,不是吗?我会试一试的。
  • 但是当加载任务完成时setValue()不会被调用...
  • 哦,但是你调用它之前你调用setLoaded()...所以听众会收到通知,但他们仍然得到“尚未加载”的价值
  • 嗯。这项服务似乎完全没有必要;您真正需要的只是一项任务(只使用一次服务是没有意义的:它的全部目的是它是可重用的)。我添加了fireValueChangeEvent() 调用并删除了该服务(仅使用一个任务),然后它运行良好。不过这让我很困扰:我不知道为什么该服务会阻止它正常工作。

标签: java javafx tableview


【解决方案1】:

看起来像是 JavaFX 服务中的一个错误 ...

由于没有保存对 Service 实例的引用,因此它们被垃圾回收。
有时他们中的一些人在他们的任务完成之前就被垃圾收集了。
在这种情况下,不会调用服务的 onFailedonSucceeded 属性的侦听器。

简单的解决方法是使用LazyLoadingStringProperty 对象保存服务引用:

...
private ArrayList<Service<String>> services = new ArrayList<>();

protected void startLoadingService() {

    final Service<String> s = new Service<String>() {
        @Override
        protected Task<String> createTask() {
            return LazyLoadingStringProperty.this.createTask();
        }
    };
    services.add(s);
    ...

但是没有必要在并行加载多个任务的情况下加载一个值。 因此,每个价值一项服务绰绰有余:

    ...

    private Service<String> s;

    protected void startLoadingService() {

        if (s != null) return; // started already

        s = new Service<String>() {
            ...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-06
    • 2021-07-13
    • 2010-11-26
    • 2011-08-26
    • 2018-04-28
    • 2017-07-08
    相关资源
    最近更新 更多