JavaFX 图标在滚动时从 TreeTableView 中随机消失,性能也很慢

JavaFX icons randomly disappear from TreeTableView when scrolling, performance slow as well

由于某些原因,当我的 TreeTableView 填充了很多元素时,文件上设置的图标会随机变成完全透明。

向下滚动时,它们会刷新。

我已经检查过这个线程:JavaFX TreeTableView rows gone when scrolling

我假设因为我在

java version "1.8.0_45"
Java(TM) SE Runtime Environment (build 1.8.0_45-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.45-b02, mixed mode)

我在格式化后 2-3 天前更新到这个版本,所以我看不到上面的版本已经过时 2 年以上(因为我链接的另一个线程是那个旧版本 [从 2013 年开始]) .

很多节点都隐藏在其他未展开的节点中,因此我很困惑为什么单击其中一个节点展开它们会导致 GUI 缓慢启动。当我单击其中一个节点时,会出现大约 15 个新元素。我没有旧电脑(2.8 Ghz 4 核 8 gig ram),除非这样的东西很费力?

这也发生在已经加载的节点上,因此上下滚动会导致它们消失。

这是一个小例子,有时它会变得很荒谬(我在全屏上有多达一半的图像是空白的)。

这是一个错误吗?还是我做错了什么? TreeTableView 是否设计用于处理如此多的条目(~50k,但它们相互嵌套,因此在任何给定时间最多只显示 2000-3000 个条目)?

我在处理 100-500 个条目时从未发生过这种情况,但是使用此应用程序的人可能会处理可以增长到更大的数据(5000-10000 个嵌套元素,这些元素可能 不是同时显示所有内容)。

我能想到的唯一解决方法是单独查看每个节点,由此我从 table 中删除所有其他节点并仅显示当前节点,但这需要大量返工,我如果可能,我想避免。

编辑:请注意,我为每个图像视图使用多个 ImageView,因此这不像有人试图在他们的图中使用一个节点的问题。

============================================= ========================

已更新 MCVE:

package application;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage primaryStage) {
        try {
            FXMLLoader fxmlloader = new FXMLLoader(Main.class.getResource("Test.fxml"));
            AnchorPane root = fxmlloader.load();
            Scene scene = new Scene(root, 400, 400);
            primaryStage.setScene(scene);
            primaryStage.show();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

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

控制器:

package application;

import javafx.fxml.FXML;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.AnchorPane;

public class Controller {

    @FXML
    public AnchorPane rootPane;

    @FXML
    public TreeTableView<String> ttv;

    @FXML
    public TreeTableColumn<String, String> ttc;

    @FXML
    public void initialize() {      
        Image img = new Image("http://i.imgur.com/TEgfhOw.png");
        TreeItem<String> root = new TreeItem<>("Root item");
        TreeItem<String> ti;
        for (int a = 0; a < 50000; a++) {
            ti = new TreeItem<>(Integer.toString(a));
            ti.setGraphic(new ImageView(img));
            root.getChildren().add(ti);
        }
        ttv.setRoot(root);
    }
}

FXML 文件:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.*?>
<?import java.lang.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.layout.AnchorPane?>

<AnchorPane fx:id="rootPane" prefHeight="400.0" prefWidth="400.0" xmlns="http://javafx.com/javafx/8.0.40" xmlns:fx="http://javafx.com/fxml/1" fx:controller="application.Controller">
   <children>
      <TreeTableView fx:id="ttv" prefHeight="300.0" prefWidth="300.0">
        <columns>
          <TreeTableColumn fx:id="ttc" prefWidth="195.0" text="C1" />
        </columns>
      </TreeTableView>
   </children>
</AnchorPane>

这是我最终将滚动条下拉时发生的情况(通常在上下拖动它的 3-4 秒内):

此外,当向下拖动时,文件会随机出现并立即撕裂到左侧。 说明:

建议的替代方法

我建议,对于您的用例,您不要在每个 TreeItem 上设置图形。相反,在树单元格的单元格值工厂中设置一个图形。

当您在每个 TreeItem 上设置一个图形时,您创建了 50K 个图形节点。当您在单元工厂中设置图形时,您只需为每个可见单元创建一个图形节点(例如,您的样本有 11 个图形节点)。

示例代码

下面的代码没有表现出您在问题中描述的行为,对我来说工作正常:

package application;

import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.fxml.FXML;
import javafx.scene.control.*;
import javafx.scene.image.*;
import javafx.scene.layout.AnchorPane;
import javafx.util.Callback;

public class Controller {

    @FXML
    public AnchorPane rootPane;

    @FXML
    public TreeTableView<String> ttv;

    @FXML
    public TreeTableColumn<String, String> ttc;

    @FXML
    public void initialize() {      
        Image img = new Image("http://i.imgur.com/TEgfhOw.png");
        TreeItem<String> root = new TreeItem<>("Root item");
        TreeItem<String> ti;
        for (int a = 0; a < 50000; a++) {
            ti = new TreeItem<>(Integer.toString(a));
//            ti.setGraphic(new ImageView(img));
            root.getChildren().add(ti);
        }
        ttc.setCellValueFactory(param -> 
            new ReadOnlyObjectWrapper<>(param.getValue().getValue())
        );

        ttc.setCellFactory(new Callback<TreeTableColumn<String, String>, TreeTableCell<String, String>>() {
            @Override
            public TreeTableCell<String, String> call(TreeTableColumn<String, String> param) {
                return new TreeTableCell<String, String>() {
                    private ImageView imageView;

                    @Override
                    protected void updateItem(String item, boolean empty) {
                        super.updateItem(item, empty);

                        if (!empty && item != null) {
                            if (imageView == null) {
                                imageView = new ImageView(img);
                            }

                            setText(item);
                            setGraphic(imageView);
                        } else {
                            setText(null);
                            setGraphic(null);
                        }
                    }
                };
            }
        });

        ttv.setRoot(root);
    }
}

即使此答案中的代码可能对您有用,我仍然鼓励您在 JavaFX issue tracker 中记录一份报告,请求开发人员检查您在问题(如果有一些内部的东西可以在平台代码中完成,以提高或记录它的可扩展性,有很多树项目图形集)。

@jewelsea 的建议适用于单个图标,但当需要多个图标时会出现问题。

Image pink = new Image (getClass ().getResourceAsStream ("Letter-A-pink-icon.png"));
Image blue = new Image (getClass ().getResourceAsStream ("Letter-A-blue-icon.png"));

TreeItem<String> root = new TreeItem<> ("0");
for (int i = 1; i <= 5000; i++)
{
  TreeItem<String> ti = new TreeItem<> (Integer.toString (i));
  root.getChildren ().add (ti);
}
ttc.setCellValueFactory (
    param -> new ReadOnlyObjectWrapper<> (param.getValue ().getValue ()));

ttc.setCellFactory (
    new Callback<TreeTableColumn<String, String>, TreeTableCell<String, String>> ()
    {
      @Override
      public TreeTableCell<String, String>
          call (TreeTableColumn<String, String> param)
      {
        return new TreeTableCell<String, String> ()
        {
          ImageView imageView;

          @Override
          protected void updateItem (String item, boolean empty)
          {
            super.updateItem (item, empty);

            if (!empty && item != null)
            {
              if (imageView == null)
                imageView = new ImageView ();

              int val = Integer.parseInt (item);
              imageView.setImage (val % 2 == 0 ? pink : blue);

              setText (item);
              setGraphic (imageView);
            }
            else
            {
              setText (null);
              setGraphic (null);
            }
          }
        };
      }
    });

ttv.setRoot (root);

}

Java是复用了TreeTableCells,也就是说ImageView每次都需要更新