Java FX 2 TreeView Model Reference


Question

I am trying to fetch back the TreeItems valueProperty during an onMouseClick event. I have a TreeView of following type:

@FXML
private TreeView<Pair<URIImpl,String>> myTreeview;

The FXML as follows:

<TreeView fx:id="myTreeview" onMouseClicked="#myTreeview_Clicked" 
        prefHeight="551.0" prefWidth="166.0"
        AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" 
        AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" />

I would like to get back my model (Pair instance) on click.

void myTreeview_Clicked(MouseEvent event) {
    if(event.getTarget() instanceof ...) {
        // Fetch Target
    }
}

The target object in my case is Label text which do not contain the encapsulated model reference, to the best of my knowledge. Could anyone please advise? This seems to be a very fundamental feature. A one-way binding...with some sort of reference. I am not looking to edit the TreeItems.

This thread seems similar to question: Model-Identifier for Node in JavaFX 2 TreeItem

Thanks

1
6
5/23/2017 11:51:21 AM

Accepted Answer

The key is to define a cell factory on the TreeView and in the cell factory add your mouse click event handler to the tree cell. A mouse click event handler so defined will have full access to the model object backing the tree cell and can do what it want with it.

In the example below, when the user clicks cells in the tree, click handlers extract information from the underlying model item related to the clicked cell and render that information into a label below the tree.

treemodel

TreeApplication.java

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.*;
import javafx.stage.*;

public class TreeApplication extends Application {
  @Override public void start(final Stage stage) throws Exception {
    final FXMLLoader loader = new FXMLLoader(
      getClass().getResource("treeInterface.fxml")
    );
    final Parent root = (Parent) loader.load();

    stage.setScene(new Scene(root));
    stage.show();
  }

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

TreeController.java

import javafx.event.EventHandler;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.*;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.util.Callback;
import javafx.util.Pair;

import java.net.URL;
import java.util.ResourceBundle;

public class TreeController implements Initializable {
  @FXML private TreeView<Pair<URIImpl,String>> treeView;
  @FXML private Label clickedPair;

  @Override public void initialize(URL location, ResourceBundle resources) {
    treeView.setCellFactory(new Callback<TreeView<Pair<URIImpl, String>>, TreeCell<Pair<URIImpl, String>>>() {
      @Override public TreeCell<Pair<URIImpl, String>> call(TreeView<Pair<URIImpl, String>> treeView) {
        return new TreeCell<Pair<URIImpl, String>>() {
          final ImageView iconView = new ImageView();
          @Override protected void updateItem(final Pair<URIImpl, String> pair, boolean empty) {
            super.updateItem(pair, empty);

            if (!empty && pair != null) {
              setText(
                pair.getValue()
              );
              setGraphic(
                iconView
              );
              iconView.setImage(pair.getKey().getImage());
            } else {
              setText(null);
              setGraphic(null);
            }

            setOnMouseClicked(new EventHandler<MouseEvent>() {
              @Override
              public void handle(MouseEvent event) {
                clickedPair.setText(
                  "Key: " + pair.getKey() + " Value: " + pair.getValue()
                );
              }
            });
          }
        };
      }
    });

    loadTreeItems(
      createPair("http://www.google.com",    "google.com",    "Google"),
      createPair("http://www.microsoft.com", "microsoft.com", "Microsoft"),
      createPair("http://www.yahoo.com",     "yahoo.com",     "Yahoo")
    );
  }  

  private Pair<URIImpl, String> createPair(String uri, String domain, String name) {
    return new Pair<>(new URIImpl(uri, domain), name);
  }

  private void loadTreeItems(Pair<URIImpl,String>... rootItems) {
    TreeItem<Pair<URIImpl,String>> root = new TreeItem(createPair("N/A", "", "Root Node"));
    root.setExpanded(true);

    for (Pair<URIImpl, String> pair: rootItems) {
      root.getChildren().add(
        new TreeItem<>(
          pair
        )
      );
    }

    treeView.setRoot(root);
  }

}

URIImpl.java

import javafx.scene.image.Image;

class URIImpl {
  private final String uri;
  private final String domain;
  private       Image  image;

  public URIImpl(String uri, String domain) {
    this.uri    = uri;
    this.domain = domain;
  }

  public String getUri() {
    return uri;
  }

  public String getDomain() {
    return domain;
  }

  public Image getImage() {
    if (image == null) {
      image = new Image("https://plus.google.com/_/favicon?domain=" + getDomain());
    }

    return image;
  }

  @Override
  public String toString() {
    return "URIImpl{" + "uri->" + uri + '}';
  }    
}

treeInterface.fxml

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

<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>

<AnchorPane id="AnchorPane" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns:fx="http://javafx.com/fxml" fx:controller="tests.treesample.TreeController">
    <TreeView fx:id="treeView" layoutX="0" layoutY="0" prefHeight="193.0" prefWidth="471.0" />
    <Label    fx:id="clickedPair" layoutX="0" layoutY="200"/>
</AnchorPane>

Update

This worked just fine. However, the icon image seems to be lost once we setup the CallBack event handlers.

Yeah, looks as though if you create your own cell factory, you also need to set the graphic manually within the cell factory. This is because the default tree item cell factory will grab the graphic from the tree item if one has been set on the tree item, whereas this logic won't be present within your custom cell factory unless you code it there.

I updated the code in the sample solution to show how you can set a graphic on a tree cell generated by a custom cell factory. The updated code gets the graphic image from the model backing the tree cell, but it could equally have retrieved it from the graphic attribute of the tree item instead if that were preferred. To get the graphic from the TreeItem, use the following code as suggested by blacks0ul:

TreeItem<Pair<URIImpl, String>> item = getTreeItem();
if (item != null && item.getGraphic() != null) { 
  setGraphic(item.getGraphic()); 
} 
9
5/21/2013 4:03:49 PM

Licensed under: CC-BY-SA with attribution
Not affiliated with: Stack Overflow
Icon