5

I have a TreeTableView where every node has an icon. Everything works perfectly when I expand the tree, but when I collapse the tree, the icons of the no longer visible items are left behind.

The rows and the text are removed, but the icons remain "free-floating". In the screenshot you can see the TreeTableView twice, once expanded with the correct text, and once collapsed with only the remaining icons.

Screenshot with correct expanded TreeTableView and broken collapsed one

The screenshot above was created from the following minimal example (you just need to add your own icon.png):

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableView;
import javafx.stage.Stage;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;

public class TreeTableViewSample extends Application {
    private static final Image icon = new Image(TreeTableViewSample.class.getResourceAsStream("icon.png"));

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

    @Override
    public void start(Stage stage) {
        stage.setTitle("Tree Table View Samples");
        final Scene scene = new Scene(new Group(), 200, 400);
        Group sceneRoot = (Group)scene.getRoot();  

        //Creating tree items
        final TreeItem<String> childNode1 = new TreeItem<>("Child Node 1", new ImageView(icon));
        final TreeItem<String> childNode2 = new TreeItem<>("Child Node 2", new ImageView(icon));
        final TreeItem<String> childNode3 = new TreeItem<>("Child Node 3", new ImageView(icon));

        //Creating the root element
        final TreeItem<String> root = new TreeItem<>("Root node", new ImageView(icon));
        root.setExpanded(true);   

        //Adding tree items to the root
        root.getChildren().setAll(childNode1, childNode2, childNode3);        

        //Creating a column
        TreeTableColumn<String,String> column = new TreeTableColumn<>("Column");
        column.setPrefWidth(150);   

        //Defining cell content
        column.setCellValueFactory((CellDataFeatures<String, String> p) -> 
            new ReadOnlyStringWrapper(p.getValue().getValue()));  

        //Creating a tree table view
        final TreeTableView<String> treeTableView = new TreeTableView<>(root);
        treeTableView.getColumns().add(column);
        treeTableView.setPrefWidth(152);
        treeTableView.setShowRoot(true);             
        sceneRoot.getChildren().add(treeTableView);
        stage.setScene(scene);
        stage.show();
    }     
}

I have tried this using OpenJDK and Oracle Java 8u92 on Linux and using Oracle Java 8u92 on Windows.

Sarek
  • 250
  • 4
  • 9

1 Answers1

9

That's a bug. It works fine with my old 1.8.0_71 but fails as you describe with 1.8.0_92.

IMHO, defining a graphic in the TreeItem is nonsense anyway: the TreeItem is part of the model for the tree and should contain data only, not details of how the data are presented. The graphic should be a property of the cell and should be defined in a cell factory. The following workaround works as expected:

import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableColumn.CellDataFeatures;
import javafx.scene.control.TreeTableView;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class TreeTableViewSample extends Application {
    private static final Image icon = new Rectangle(12, 12, Color.CORNFLOWERBLUE).snapshot(null, null);

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

    @Override
    public void start(Stage stage) {
        stage.setTitle("Tree Table View Samples");
        final Scene scene = new Scene(new Group(), 200, 400);
        Group sceneRoot = (Group)scene.getRoot();  

        //Creating tree items
//        final TreeItem<String> childNode1 = new TreeItem<>("Child Node 1", new ImageView(icon));
//        final TreeItem<String> childNode2 = new TreeItem<>("Child Node 2", new ImageView(icon));
//        final TreeItem<String> childNode3 = new TreeItem<>("Child Node 3", new ImageView(icon));

        final TreeItem<String> childNode1 = new TreeItem<>("Child Node 1");
        final TreeItem<String> childNode2 = new TreeItem<>("Child Node 2");
        final TreeItem<String> childNode3 = new TreeItem<>("Child Node 3");

        //Creating the root element
//        final TreeItem<String> root = new TreeItem<>("Root node", new ImageView(icon));
        final TreeItem<String> root = new TreeItem<>("Root node");
        root.setExpanded(true);   

        //Adding tree items to the root
        root.getChildren().setAll(childNode1, childNode2, childNode3);        

        //Creating a column
        TreeTableColumn<String,String> column = new TreeTableColumn<>("Column");
        column.setPrefWidth(150);   

        //Defining cell content
        column.setCellValueFactory((CellDataFeatures<String, String> p) -> 
            new ReadOnlyStringWrapper(p.getValue().getValue()));  


        // cell factory to display graphic:
        column.setCellFactory(ttc -> new TreeTableCell<String, String>() {

            private final ImageView graphic = new ImageView(icon);

            @Override
            protected void updateItem(String item, boolean empty) {
                super.updateItem(item, empty);
                setText(empty ? null : item);
                setGraphic(empty ? null : graphic);
            }
        });

        //Creating a tree table view
        final TreeTableView<String> treeTableView = new TreeTableView<>(root);
        treeTableView.getColumns().add(column);
        treeTableView.setPrefWidth(152);
        treeTableView.setShowRoot(true);             
        sceneRoot.getChildren().add(treeTableView);
        stage.setScene(scene);
        stage.show();
    }     
}
James_D
  • 201,275
  • 16
  • 291
  • 322
  • "nonsense" is a bit strong, IMO :-) There _are_ contexts where an image is part of the model .. – kleopatra Apr 29 '16 at 08:49
  • @kleopatra An image, yes; but an `ImageView`? Setting a `graphic` on the `TreeItem` has pretty bad side-effects; e.g. you can't use the same `TreeItem` in multiple `TreeView`s (because the graphic can't have two parents). Having multiple views of the same model is the main purpose of MVC (and similar) designs, no? – James_D Apr 29 '16 at 18:31
  • ahh .. pleading guilty of not fully reading ;-) agreed, nodes in the model are a no-no-never – kleopatra Apr 29 '16 at 19:39
  • Thank you, this works perfectly. In my defense: I tried various things to get it to work correctly. When posting this question I decided to use the icon approach given in the JavaFX examples... I will file a bug as I think what is described in the documentation should at least work, even if it is not the ideal solution. – Sarek May 02 '16 at 07:36
  • If you place the icon in a cell, then the icon will move with this cell for example when the user changes its position. If you place the icon in the tree item the icon will always be displayed next to the arrow. – christoph.keimel Jun 17 '16 at 12:55
  • @christoph.keimel Sorry: not sure I understand that. What do you mean by "when the user changes its position"? – James_D Jun 17 '16 at 12:57
  • You can drag drop the columns of the tree table thereby changing the position of the wohle column (and the cells in the column). If you do this, the icon will stay in the column that was moved. – christoph.keimel Jun 17 '16 at 12:59
  • Ah, ok, I see what you mean. But the proper place to define a graphic that were fixed independent of the order of the columns would be in the `TreeTableRow`, though I admit I have never tried it. – James_D Jun 17 '16 at 13:04