Marc Rasmussen
Marc Rasmussen

Reputation: 20555

Javafx Treeview item action event

I'm trying to create a menu using a treeView. This is the first time I'm using treeView and have been reading up on it on several websites.

I'm having some problems when it comes to action event. What I want to do is basically to fire and event when ever the user clicks a node in the treeview so far I have the following:

        TreeItem<String> rootItem = new TreeItem<String>("Navigation");
    TreeItem<String> statistics = new TreeItem<String>("Statistics");
    TreeItem<String> clan = new TreeItem<String>("Clan page");
    clan.addEventHandler(MouseEvent, new EventHandler<MouseEvent>() {

        @Override
        public void handle(MouseEvent event) {
            // TODO Auto-generated method stub

        }
    });

    rootItem.getChildren().add(statistics);
    rootItem.getChildren().add(clan);

    TreeView<String> tree = new TreeView<String>(rootItem); 

Sadly this doesn't seem to work.

Is there any way I can add a clicklistener or even an actionlistener to the individual items in my treeView without changing the treeItems to type Button ?

Upvotes: 5

Views: 30356

Answers (5)

Michael Sims
Michael Sims

Reputation: 2523

I actually accomplished this in a round-about way, though it is extremely flexible, and even allows you to have TreeItems that engage different classes while still being able to nest them within each other.

It can be rather daunting to look at upon initial inspection, but if you follow it closely, it will make perfect sense.

You start with your class containing the TreeView - we'll call it TreeViewClass.

This example came from a program I'm working on that interacts with GitHub Gists, which explains the various objects that I interact with, in the classes.

public class TreeViewClass {

    public void start() {
        TextArea itemInfo = new TextArea();
        SplitPane splitPane = new SplitPane(getTreeView(), itemInfo);
        splitPane.setDividerPosition(0, .1);
        Stage stage = new Stage();
        stage.setScene(new Scene(splitPane));
        stage.show();
    }

    private Collection<TreeItem<MyTreeItem>> gistObjectTreeItems() {
        Collection<TreeItem<MyTreeItem>> gistTreeItemObjects = FXCollections.observableArrayList();
        List<GistObject> gistObjects = GitHubApi.getGistObjectList();

        for (GistObject gistObject : gistObjects) {
            MyTreeItem myGistObjectTreeItem = new MyTreeItem(gistObject);
            TreeItem<MyTreeItem> objectTreeItem = new TreeItem<>(myGistObjectTreeItem);

            List<GistFile> gistFileList = gistObject.getFileList();
            for(GistFile file : gistFileList) {
                TreeItem<MyTreeItem> fileTreeItem = new TreeItem<>(new MyTreeItem(file));
                objectTreeItem.getChildren().add(fileTreeItem);
            }
            gistTreeItemObjects.add(gistObjectTreeItem);
        }
        return gistTreeItemObjects;
    } 
/**
We are returning a Collection of TreeItems, each one contains an instance
of MyTreeItem using the constructor that defines the instance as a GistObject.
We then add to each of those objects, more TreeItems that contain the same
class MyTreeItem, only we use the constructor that sets the instance as
GistFile, and obviously, each Gist can have many GistFiles.
**/
    
    private TreeView<MyTreeItem> getTreeView() {
        TreeView<MyTreeItem> treeView = new TreeObject(this);
        TreeItem<MyTreeItem> treeRoot = new TreeItem<>(new MyTreeItem());
        treeRoot.getChildren().addAll(gistObjectTreeItems());
        treeView.setRoot(treeRoot);
        treeView.setShowRoot(false);
        return treeView;
    }
/**
getTreeView merely builds the TreeView, then adds to it, the Collection
of MyTreeItems that contain the GistObject and each of those objects
GistFiles. We need a root in the tree, so we add one that also contains
the MyTreeItem class only we use the default constructor, since it is
neither a GistObject or a GistFile, and then we hide it because we
don't need to interact with it. 
**/
}

This is the main TreeView extended class:

import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.input.MouseEvent;

public class TreeObject extends TreeView<MyTreeItem> {

    public TreeObject() {
        super();
        addEventHandler(MouseEvent.MOUSE_CLICKED, e->{
            if(e.getClickCount() == 1) {
                selected = getSelectionModel().getSelectedItem();
                if (selected != null) {
                    gistFile = selected.getValue().getFile();
                    gist     = selected.getValue().getGist();
                    if (gistFile != null) {
                        GitHubApi.setSelected(gistFile);
                    }
                    else if (gist != null) {
                        GitHubApi.setSelected(gist);
                    }
                }
            }
        });
    }

    TreeItem<MyTreeItem> selected;
    GistFile             gistFile;
    GistObject           gist;
}
    
/**
This is the class that gives us the ability to add all manner of events that we might
want to look for. In this case, I am looking for MOUSE_CLICKED events and notice I can
even check to see how many mouse clicks the user enacted on the TreeItems. When the user
clicks on the TreeItem, the selected object returns the MyTreeItem instance that
is assigned to whichever TreeItem they click on, I can then test to see which way 
MyTreeItem has been configured (GistObject or GistFile) and act accordingly.
**/

Finally, we have the MyTreeItem class:

public class MyTreeItem {

    private enum TYPE {
        GIST, FILE
    }

    public MyTreeItem(GistObject gist) {
        this.gist = gist;
        this.type = TYPE.GIST;
    }

    public MyTreeItem(GistFile file) {
        this.file = file;
        this.type = TYPE.FILE;

    }

    public MyTreeItem(){}

    private TYPE type;
    private GistObject gist;
    private GistFile   file;

    public boolean isGist() { return type == TYPE.GIST; }

    public boolean isFile() { return type == TYPE.FILE; }

    public GistObject getGist() { return gist; }

    public GistFile getFile() { return file; }

    @Override
    public String toString() {
        if(this.type == TYPE.FILE) return StringUtils.truncate(file.toString(),25);
        else if(this.type == TYPE.GIST) return StringUtils.truncate(gist.toString(),25);
        return "";
    }
}
/**
This should be fairly self-explanatory. This is the MyTreeItem class that can be instantiated
as either a GistObject or a GistFile. Note the toString returns different Strings depending
on how the class was instantiated. The toString() will automatically populate the label
on the TreeItem object that the class is assigned to.
**/

Upvotes: -1

Younes Meridji
Younes Meridji

Reputation: 309

treeView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<TreeItem<String>>() {

                @Override
                public void changed(ObservableValue<? extends TreeItem<String>> observable,
                        TreeItem<String> oldValue, TreeItem<String> newValue) {
                    // newValue represents the selected itemTree
                }

            });

Upvotes: 2

Salah Eddine Taouririt
Salah Eddine Taouririt

Reputation: 26405

According to the JavaFX 2.2 documentation :

" ..a TreeItem is not a Node, and therefore no visual events will be fired on the TreeItem, To get these events, it is necessary to add relevant observers to the TreeCell instances (via a custom cell factory)."

I think this example on using TreeView will be somehow useful.

Upvotes: 6

user1628407
user1628407

Reputation: 139

I couldn't find method getPickResult in mouse event, so maybe next is preferable then answer from Alex:

1) add listener to tree view

treeView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> handle(newValue));

2) handle clicks, it's not need distinguish clicks on empty space and nodes

private void updateSelectedItem(Object newValue) {      
    System.out.println(newValue);
}

Upvotes: 10

Alexandre
Alexandre

Reputation: 531

This may be solved by implementing CellFactory, but I think the easiest way is like this:

1) Create and add an event handler to the TreeView:

EventHandler<MouseEvent> mouseEventHandle = (MouseEvent event) -> {
    handleMouseClicked(event);
};

treeView.addEventHandler(MouseEvent.MOUSE_CLICKED, mouseEventHandle); 

2) Handle only clicks on the nodes (and not on empy space os the TreeView):

private void handleMouseClicked(MouseEvent event) {
    Node node = event.getPickResult().getIntersectedNode();
    // Accept clicks only on node cells, and not on empty spaces of the TreeView
    if (node instanceof Text || (node instanceof TreeCell && ((TreeCell) node).getText() != null)) {
        String name = (String) ((TreeItem)treeView.getSelectionModel().getSelectedItem()).getValue();
        System.out.println("Node click: " + name);
    }
}

Upvotes: 16

Related Questions