Water
Water

Reputation: 3655

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

For some reason, when my TreeTableView is populated with many elements, the icons set on the files will randomly turn completely transparent.

Upon scrolling down, they will refresh.

I've already checked this thread: JavaFX TreeTableView rows gone when scrolling

I assume since I'm on

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)

This version I updated to literally 2-3 days ago after formatting, so I can't see the above version being 2+ years out of date (as the other thread I linked is that old [from 2013]).

A lot of the nodes are buried in other nodes that are not expanded, therefore I'm confused at why clicking on one of the nodes to expand them causes the GUI to chug slowly. When I click on one of the nodes, approximately 15 new elements appear. I don't have an old computer (2.8 Ghz 4 core 8 gig ram), unless such a thing is that taxing?

This is also happening to nodes that are already loaded, so scrolling up and back down will cause them to disappear.

Here is a small example, sometimes it can get ridiculous (I've had up to half of the images on a full screen go blank).

enter image description here

Is this a bug? Or am I doing something wrong? Was the TreeTableView designed to handle this many entries (~50k, but they are nested in each other so only at most 2000-3000 are showing at any given time)?

I have never had this happen when dealing with 100-500 entries, but people who will be using this application will likely be dealing with data that can grow to be much bigger (5000-10000 nested elements which likely will not be showing all at the same time).

The only workaround I can think of is viewing each node by itself, whereby I remove every other node from the table and only display the current one, but this would require a fair amount of reworking which I'd like to avoid if possible.

EDIT: As a note, I use multiple ImageView's for each, so it's not like the problems where someone tries to use one Node in their graph.

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

Updated 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);
    }
}

Controller:

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 file:

<?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>

This is what happens when I pull the scroller down eventually (usually within 3-4 seconds of dragging it up and down):

enter image description here

Furthermore, the files randomly appear to rip instantly to the left when dragging down. INSTRUCTIONS:

Upvotes: 2

Views: 1916

Answers (2)

dmolony
dmolony

Reputation: 1135

The suggestion by @jewelsea works with a single icon, but causes problems when more than one icon is required.

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 is reusing the TreeTableCells, which means that the ImageView needs to be updated each time.

Upvotes: 0

jewelsea
jewelsea

Reputation: 159291

Suggested alternate approach

I suggest that, for your use-case, that you don't set a graphic on each TreeItem. Instead, set a graphic in the cell value factory for your tree cells.

When you set a graphic on each TreeItem, you create 50K graphic nodes. When you set a graphic in the cell factory, you only create a single graphic node for each of the visible cells (e.g. 11 graphic nodes for your sample).

Sample code

The code below does not exhibit the behavior your describe in your question and works fine for me:

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);
    }
}

Even though the code in this answer will probably work for you, I still encourage you to log a report in the JavaFX issue tracker to request a developer to look into the performance of the original code you posted in your question (in case there is something internal which could be done in the platform code to improve or document its scalability with lots of tree item graphics set).

Upvotes: 4

Related Questions