Get Off My Lawn
Get Off My Lawn

Reputation: 36311

JavaFx automatically add listeners to dynamically added items

In jquery, there is the on feature which will automatically bind events to items that are added at a later time (such as an ajax call), it looks something like this:

$(document).on("click", ".my-class", function(){
    // Do my event task here
});

Now with that every time an item with the class my-class is added to the dom that event is automatically attached.

Is there anything like that in Java or JavaFx? I would like to keep my events in my controller, and have other methods in what I like to call Helper Classes with the code that I am working on I am not sure how I can achieve that.

So I have this in my controller:

Tree tree = new Tree(currentMysqlConn);
tree.addDatabases(treeItem);

That then creates TreeItem's then that creates child items, and those child items have events when clicked. Here is an example one:

TreeItem<String> tableNode = new TreeItem<>("Tables", tableIcon);
tableNode.expandedProperty().addListener(new ChangeListener(){

    @Override
    public void changed(ObservableValue observable, Object oldValue, Object newValue){
        if((boolean) newValue){
            TableActions ta = new TableActions(tableNode);
            ta.getTables(conn);
        }
    }
});

The issue is, that I have that in my Helper Class, so is there anything I can do to move that into my controller? Because that is inside a loop.

Upvotes: 0

Views: 5993

Answers (2)

eckig
eckig

Reputation: 11134

If you have access to the root node of your TreeView this is quite simply done:

Have a look at the TreeItem and the TreeModificationEvent docs. You can add a listener to the root node for the childrenModificationEvent. Any time a node is added or removed below your root node you will get the TreeModificationEvent where you can retrieve the added and deleted nodes.

Upvotes: 0

James_D
James_D

Reputation: 209358

You can register a listener with any observable list (e.g. the list you get from TreeItem.getChildren(), or Parent.getChildren()), and recursively have that listener register the same listener with any children added to the list.

The following example updates a label displaying the total number of nodes in a tree view. To do this, we need to know when child nodes are added to any node, so we need listeners associated with the dynamically added nodes. Your use case might be a bit different, but if you understand how this works you should be able to implement what you need. Note that while I don't have any other classes here, if the contents of the UI are changed from anywhere, the controller is notified appropriately via the listeners.

CountingTreeViewController.java:

import java.util.concurrent.atomic.AtomicInteger;

import javafx.beans.binding.Bindings;
import javafx.beans.binding.IntegerBinding;
import javafx.collections.ListChangeListener;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;


public class CountingTreeViewController {
    @FXML
    private TextField itemTextField ;
    @FXML
    private TreeView<String> tree ;
    @FXML
    private Label countLabel ;

    // binding that contains the total count of all nodes
    // the computeValue() method recursively counts the number of children
    // of each node
    // This binding needs to be invalidated if the list of child nodes of any
    // tree node changes...
    private final IntegerBinding count = new IntegerBinding() {

        @Override
        protected int computeValue() {
            // Note the AtomicInteger here is just used as a mutable wrapper.
            // We don't care about the atomicity since it's only updated from 
            // one thread
            return countNodes(tree.getRoot(), new AtomicInteger());
        }

        private int countNodes(TreeItem<?> node, AtomicInteger count) {
            count.incrementAndGet();
            for (TreeItem<?> child : node.getChildren()) {
                countNodes(child, count);
            }
            return count.intValue();
        }
    };

    // A ListChangeListener that will be associated with any tree node. It does two things:
    // First, it invalidates the count binding above if the number of child nodes changes.
    // Second, it adds the same listener to any new child nodes, and removes it from any nodes
    // that are removed from the tree.
    private final ListChangeListener<TreeItem<String>> childrenChanged 
        = new ListChangeListener<TreeItem<String>>() {

            @Override
            public void onChanged(
                    javafx.collections.ListChangeListener.Change<? extends TreeItem<String>> change) {
                while (change.next()) {
                    if (change.wasAdded() || change.wasRemoved()) {
                        count.invalidate();
                    }
                    if (change.wasAdded()) {
                        for (TreeItem<String> item : change.getAddedSubList()) {
                            item.getChildren().addListener(childrenChanged);
                        }
                    } else if (change.wasRemoved()) {
                        for (TreeItem<String> item : change.getRemoved()) {
                            item.getChildren().removeListener(childrenChanged);
                        }
                    }
                }
            }

    };

    public void initialize(){ 
        tree.getRoot().getChildren().addListener(childrenChanged);
        countLabel.textProperty().bind(Bindings.format("Count: %s", count));
    }


    // add a new child node to the selected node (or the root if nothing's selected)
    @FXML
    private void addItem() {
        TreeItem<String> item = tree.getSelectionModel().getSelectedItem();
        if (item == null) {
            item = tree.getRoot();
        }
        TreeItem<String> newItem = new TreeItem<>(itemTextField.getText());
        newItem.setExpanded(true);
        item.getChildren().add(newItem);
        itemTextField.setText("");
        itemTextField.requestFocus();
    }
}

CountingTree.fxml:

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

<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.control.TreeView?>
<?import javafx.scene.control.TreeItem?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.geometry.Insets?>

<BorderPane xmlns:fx="http://javafx.com/fxml/1" fx:controller="CountingTreeViewController">
    <center>
        <TreeView fx:id="tree">
            <root>
                <TreeItem value="Root" expanded="true" />
            </root>
        </TreeView>
    </center>
    <right>
        <HBox alignment="CENTER" spacing="5">
            <padding>
                <Insets top="10" right="10" left="10" bottom="10"/>
            </padding>
            <TextField fx:id="itemTextField" onAction="#addItem"/>
            <Button text="Add" onAction="#addItem"/>
        </HBox>
    </right>
    <bottom>
        <Label text="Count: 1" fx:id="countLabel"/>
    </bottom>
</BorderPane>

CountingTree.java:

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

public class CountingTree extends Application {

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(getClass().getResource("CountingTree.fxml"));
        primaryStage.setScene(new Scene(loader.load(), 800, 600));
        primaryStage.show();
    }

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

Upvotes: 4

Related Questions