Question

I am developing a JavaFX 2.2 application using Netbeans 7.2. I am working with a treeview and I extended TreeCell to provide to each TreeItem a context-menu with a MenuItem with "Collpase All" functionality. The max depth level of the treeview is 4. When a user right clicks on a TreeItem of level 2 and clicks to "Collapse All" MenuItem i want to make all the TreeItems of level 3 collapsed (setExpanded(false)). Below you can see the code that I am using. My problem is the memory and CPU cost of this operation. I inserted 250 TreeItems to level 3. The cost of a collapse all operation was ~200MB of memory on each collapseAll click and spends about 2s of time! My developer computer's CPU is an Intel i5 (3.3GHz) and I have 8GB of memory. Is this numbers of hardware cost normal or I am doing something wrong in my code? Am I using a wrong way to collapse them?

This class manages the TreeView. Loads data from database, reloads them, knows the selected TreeItem and expand/collapse the selected children TreeItems.

public final class TargetTree extends SqlConnectionManager {

    private TreeView tree;
    private TreeItem selectedItem;

    private TargetTree() {
        super();
        this.tree = null;
        this.selectedItem = null;
    }

    private TargetTree(TreeView tree) {
        super();
        this.tree = tree;
        this.selectedItem = null;
    }

    public static TargetTree construct(TreeView tree) {
        if (tree == null) {
            return null;
        }

        TargetTree targetTree = new TargetTree(tree);
        targetTree.load();
        return targetTree;
    }

    public void reload() {
        // Clear current tree.
        if (tree.getRoot() != null) {
            for (int i = 0; i < tree.getRoot().getChildren().size(); i++) {
                tree.getRoot().getChildren().clear();
            }
            tree.setRoot(null);
        }
        this.load();
    }

    public void prune() {
        //TODO
    }

    private void load() {
        // New root Item.
        final TreeItem<Object> treeRoot = new TreeItem<>((Object) "Root");
        treeRoot.setExpanded(true);

        // This integers help to find when to build a new department/section/measure.
        int lastDepartmentId = -1;
        int lastSectionId = -1;
        int lastMeasureId = -1;
        int lastTargetId = -1;

        //The temp treeitems.
        TreeItem<Object> departmentTreeItem = null;
        TreeItem<Object> sectionTreeItem = null;
        TreeItem<Object> measureTreeItem = null;
        TreeItem<Object> targetTreeItem = null;

        // Get the new TreeItems from the database.
        super.errorMessage = "";
        try {
            // Establishing connection with db.
            super.openConnection();

            // Query to be executed. Selects everything from the database.
            preparedStmt = connection.prepareStatement(
                    "SELECT.....ORDER BY....;");
            resultSet = preparedStmt.executeQuery();

            while (resultSet.next()) {
                // Department Creation.
                if (lastDepartmentId != resultSet.getInt("departmentId")) {
                    final Department department = Department.initEmpty();
                    department.setId(resultSet.getInt("departmentId"));
                    department.setName(resultSet.getString("departmentName"));

                    // Create the treeitem for this department.
                    departmentTreeItem = new TreeItem<>((Object) department);
                    departmentTreeItem.setExpanded(true);
                    treeRoot.getChildren().add(departmentTreeItem);

                    // Reset the children ids to ensure that they will be recreated.
                    lastDepartmentId = resultSet.getInt("departmentId");
                    lastSectionId = -1;
                    lastMeasureId = -1;
                    lastTargetId = -1;
                }

                // Section Creation.
                if (lastSectionId != resultSet.getInt("sectionId")) {
                    final Section section = Section.initEmpty();
                    section.setId(resultSet.getInt("sectionId"));
                    section.setName(resultSet.getString("sectionName"));

                    // Create the treeitem for this section.
                    sectionTreeItem = new TreeItem<>((Object) section);
                    sectionTreeItem.setExpanded(true);
                    departmentTreeItem.getChildren().add(sectionTreeItem);

                    // Reset the children ids to ensure that they will be recreated.
                    lastSectionId = resultSet.getInt("sectionId");
                    lastMeasureId = -1;
                    lastTargetId = -1;
                }

                // Measure Creation.
                if (lastMeasureId != resultSet.getInt("measureId")) {
                    final Measure measure = Measure.initEmpty();
                    measure.setId(resultSet.getInt("measureId"));
                    measure.setLastname(resultSet.getString("measureLastname"));
                    measure.setFirstname(resultSet.getString("measureFirstName"));

                    // Create the treeitem for this measure.
                    measureTreeItem = new TreeItem<>((Object) measure);
                    measureTreeItem.setExpanded(true);
                    sectionTreeItem.getChildren().add(measureTreeItem );

                    // Reset the children ids to ensure that they will be recreated.
                    lastMeasureId = resultSet.getInt("measureId");
                    lastTargetId = -1;
                }

                // Target Creation.
                if (lastTargetId != resultSet.getInt("targetId")) {
                    final Target target = Target.initEmpty();
                    target.setId(resultSet.getInt("targetId"));
                    target.setText(resultSet.getString("targetText"));

                    // Create the treeitem for this target.
                    targetTreeItem = new TreeItem<>((Object) target);
                    targetTreeItem.setExpanded(false);
                    measureTreeItem.getChildren().add(targetTreeItem);

                    // Reset the children ids to ensure that they will be recreated.
                    lastTargetId = resultSet.getInt("targetId");
                }
            }

            closeAll();
        } catch (SQLException ex) {
            super.errorMessage = ex.getMessage();
        }

        tree.setRoot(treeRoot);
        final TargetTree targetTree = this;
        tree.setCellFactory(new Callback<TreeView<Object>, TreeCell<Object>>() {
            @Override
            public TreeCell<Object> call(TreeView<Object> p) {
                return new TargetTreeCell(targetTree);
            }
        });

        // Select a Tree Item.
        tree.getSelectionModel().selectedItemProperty().addListener(new ChangeListener() {
            @Override
            public void changed(ObservableValue observable, Object oldValue, Object newValue) {
                selectedItem = (TreeItem) newValue;
            }
        });
    }

    public void collapseChildren() {
        Thread thread = new Thread(new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < selectedItem.getChildren().size(); i++) {
                            TreeItem<Object> current = (TreeItem<Object>) selectedItem.getChildren().get(i);
                            if (!current.isLeaf()) {
                                current.setExpanded(false);
                            }
                            current = null;
                        }
                        selectedItem.setExpanded(false);
                        System.gc();
                    }
                });
                return null;
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

    public void expandChildren() {
        Thread thread = new Thread(new Task<Void>() {
            @Override
            protected Void call() throws Exception {
                Platform.runLater(new Runnable() {
                    @Override
                    public void run() {
                        for (int i = 0; i < selectedItem.getChildren().size(); i++) {
                            TreeItem<Object> current = (TreeItem<Object>) selectedItem.getChildren().get(i);
                            if (!current.isLeaf()) {
                                current.setExpanded(true);
                            }
                            current = null;
                        }
                        selectedItem.setExpanded(true);
                        System.gc();
                    }
                });
                return null;
            }
        });
        thread.setDaemon(true);
        thread.start();
    }
}

Below is the custom TreeCell class.

public class TargetTreeCell extends TreeCell<Object> {

    private TargetTree targetTree;

    public TargetTreeCell(TargetTree targetTree) {
        super();
        this.targetTree = targetTree;
    }

    @Override
    public void updateItem(Object item, boolean empty) {
        super.updateItem(item, empty);

        if (item != null) {
            if (item instanceof Target) {
                initTarget(item);
            } else if (item instanceof Measure) {
                initMeasure(item);
            } else if (item instanceof Section) {
                initSection(item);
            } else if (item instanceof Department) {
                initDepartment(item);
            } else if (item instanceof String) {
                initRoot(item);
            }
        }
    }

    ///<editor-fold defaultstate="collapsed" desc="Tree Item Initialization">
    private void initRoot(Object item) {
        // Create Menu Items.
        MenuItem expandAllMenuItems = new MenuItem("Expand All");
        MenuItem collapseAllMenuItems = new MenuItem("Collapse All");

        // Event Haddlers for each Menu Items.
        expandAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
            }
        });
        collapseAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.collapseChildren();
            }
        });

        // Create Menu and add Menu Items.
        ContextMenu contextMenu = new ContextMenu();
        contextMenu.getItems().addAll(expandAllMenuItems, collapseAllMenuItems);

        //Init Root Tree Item.
        String root = (String) item;
        setText(root);
        setContextMenu(contextMenu);
    }

    private void initDepartment(Object item) {
        // Create Menu Items.
        MenuItem expandAllMenuItems = new MenuItem("Expand All");
        MenuItem collapseAllMenuItems = new MenuItem("Collapse All");

        // Event Haddlers for each Menu Items.
        expandAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.expandChildren();
            }
        });
        collapseAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.collapseChildren();
            }
        });

        // Create Menu and add Menu Items.
        ContextMenu contextMenu = new ContextMenu();
        contextMenu.getItems().addAll(expandAllMenuItems, collapseAllMenuItems);

        //Init Department Tree Item.
        Department department = (Department) item;
        setText(department.getName());
        setContextMenu(contextMenu);
    }

    private void initSection(Object item) {
        // Create Menu Items.
        MenuItem expandAllMenuItems = new MenuItem("Expand All");
        MenuItem collapseAllMenuItems = new MenuItem("Collapse All");

        // Event Haddlers for each Menu Items.
        expandAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.expandChildren();
            }
        });
        collapseAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.collapseChildren();
            }
        });

        // Create Menu and add Menu Items.
        ContextMenu contextMenu = new ContextMenu();
        contextMenu.getItems().addAll(expandAllMenuItems, collapseAllMenuItems);

        //Init Section Tree Item.
        Section section = (Section) item;
        setText(section.getName());
        setContextMenu(contextMenu);
    }

    private void initMeasure(Object item) {
        // Create Menu Items.
        MenuItem expandAllMenuItems = new MenuItem("Expand");
        MenuItem collapseAllMenuItems = new MenuItem("Collapse");

        // Event Haddlers for each Menu Items.
        expandAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.expandChildren();
            }
        });
        collapseAllMenuItems.setOnAction(new EventHandler() {
            @Override
            public void handle(Event event) {
                targetTree.collapseChildren();
            }
        });

        // Create Menu and add Menu Items.
        ContextMenu contextMenu = new ContextMenu();
        contextMenu.getItems().addAll(expandAllMenuItems, collapseAllMenuItems);

        //Init Section Tree Item.
        Measure measure = (Measure) item;
        setText(measure.getLastname() + " " + measure.getFirstname());
        setContextMenu(contextMenu);
    }

    private void initTarget(Object item) {
        //Init Section Tree Item.
        Target target = (Target) item;
        setText(target.getText());
    }
    ///</editor-fold>
}

If I have a copy-paste error please forgive me..I don't have problem with compiling. The code is running without errors. My problem is on the methods expandChildren() and collapseChildren() of the first class. In a previous version I didn't used threads and I used recursion to make all the children TreeItems (and their children TreeItems..) to collapse but the memory cost was more.

Was it helpful?

Solution

I found the answer to my problem! I will explain it with an example. I initialize a TreeView with 100 TreeItems and the result is a tree structure with 3 levels. On the screen the tree was displaying only 45 of them. To view the others i had to scroll up/down or to expand the collapsed TreeItems. On each case, the method updateItem is called to construct the new TreeItems that will appear to the visible on screen tree and therefore they all was appearing in the screen.

When i collapse an expanded TreeItem then the updateItem method will run. This was the reason of the memory and cpu cost! I had to collapse ~ 200 TreeItems that was all, and their parent expanded.

I solved my problem with a very simple way. Just before i started to collapse everything, i collapsed the parent TreeItem. Thus, i first collapsed the parent and then all the children. When the children was collapsed one by one from the source code (setExpanded(false)), the updateItem method was NOT running because their parent and therefore the children TreeItems was not existed in the screen.

On this way i saved a lot of memory and cpu time that i was spend like a dummy.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top