Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get JavaFX TreeView to behave consistently upon node expansion?

Tags:

javafx

I have a JavaFX TreeView with an invisible root and a handful of 'folder' TreeItems that have many 'file' TreeItems as children. The 'folder' TreeItems typically fit inside the TreeView without there being any scrollbars.

invisible-root/
    folder/
    folder/
    folder/
        file
        file
        file
        ...
        file

Sometimes, when I expand a 'folder' TreeItem, the scrollbars appear but the scroll position remains the same. (This is what I want!) However, sometimes, expanding a TreeItem causes the scrollbars appear and the TableView scrolls to the last child of the expanded TreeItem!

This is very unexpected and surprising, especially since I have difficulty predicting which of the two behaviors I will see: (1) stay put, or (2) scroll to last item. Personally, I think behavior (1) is less surprising and preferable.

Any thoughts on how to deal with this?

I see this behavior on Java8u31.

like image 243
davidrmcharles Avatar asked Apr 01 '15 23:04

davidrmcharles


1 Answers

The problem is in VirtualFlow. In layoutChildren() there is this section:

 if (lastCellCount != cellCount) {
            // The cell count has changed. We want to keep the viewport
            // stable if possible. If position was 0 or 1, we want to keep
            // the position in the same place. If the new cell count is >=
            // the currentIndex, then we will adjust the position to be 1.
            // Otherwise, our goal is to leave the index of the cell at the
            // top consistent, with the same translation etc.
            if (position == 0 || position == 1) {
                // Update the item count
//                setItemCount(cellCount);
            } else if (currentIndex >= cellCount) {
                setPosition(1.0f);
//                setItemCount(cellCount);
            } else if (firstCell != null) {
                double firstCellOffset = getCellPosition(firstCell);
                int firstCellIndex = getCellIndex(firstCell);
//                setItemCount(cellCount);
                adjustPositionToIndex(firstCellIndex);
                double viewportTopToCellTop = -computeOffsetForCell(firstCellIndex);
                adjustByPixelAmount(viewportTopToCellTop - firstCellOffset);
            }

The problem arises if position is 1.0 (== scrolled to bottom), because in that case there is no recalculation. A workaround would be to override the TreeViewSkin to provide your own VirtualFlow and fix the behavior there.

The code below is meant to illustrate the problem, it's not a real solution, just a starting point if you really want to fix it:

import com.sun.javafx.scene.control.skin.TreeViewSkin;
import com.sun.javafx.scene.control.skin.VirtualFlow;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.IndexedCell;
import javafx.scene.control.Skin;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;

public class TreeViewScrollBehaviour extends Application {

    @Override
    public void start(Stage primaryStage) {

        TreeView treeView = new TreeView() {
            @Override
            protected Skin createDefaultSkin() {
                return new TTreeViewSkin(this); //To change body of generated methods, choose Tools | Templates.
            }

        };
        TreeItem<String> treeItem = new TreeItem<String>("Root");
        for (int i = 0; i < 20; i++) {
            TreeItem<String> treeItem1 = new TreeItem<>("second layer " + i);
            treeItem.getChildren().add(treeItem1);
            for (int j = 0; j < 20; j++) {
                treeItem1.getChildren().add(new TreeItem<>("Third Layer " + j));

            }

        }

        treeView.setRoot(treeItem);
        StackPane root = new StackPane();
        root.getChildren().addAll(treeView);
        Scene scene = new Scene(root, 300, 250);
        primaryStage.setTitle("Hello World!");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        launch(args);
    }

    class TTreeViewSkin<T extends IndexedCell> extends TreeViewSkin<T> {

        public TTreeViewSkin(TreeView treeView) {
            super(treeView);
        }

        @Override
        protected VirtualFlow createVirtualFlow() {
            return new TVirtualFlow<T>(); //To change body of generated methods, choose Tools | Templates.
        }

    }

    class TVirtualFlow<T extends IndexedCell> extends VirtualFlow<T> {

        @Override
        public double getPosition() {
            double position = super.getPosition();
            if (position == 1.0d) {
                return 0.99999999999;
            }
            return super.getPosition(); //To change body of generated methods, choose Tools | Templates.
        }

        @Override
        public void setPosition(double newPosition) {
            if (newPosition == 1.0d) {
                newPosition = 0.99999999999;
            }
            super.setPosition(newPosition); //To change body of generated methods, choose Tools | Templates.
        }

    }
}
like image 87
monacotoni Avatar answered Sep 20 '22 12:09

monacotoni