Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8152424

TreeView scrollTo not working properly when using rows with different heights

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Incomplete
    • Icon: P4 P4
    • None
    • 8u66
    • javafx
    • x86
    • other

      FULL PRODUCT VERSION :
      java version "1.8.0_66"
      Java(TM) SE Runtime Environment (build 1.8.0_66-b17)
      Java HotSpot(TM) 64-Bit Server VM (build 25.66-b17, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Darwin xxx.fritz.box 15.3.0 Darwin Kernel Version 15.3.0: Thu Dec 10 18:40:58 PST 2015; root:xnu-3248.30.4~1/RELEASE_X86_64 x86_64

      A DESCRIPTION OF THE PROBLEM :
      Using the TreeView with a Layout/Hierarchy like the following:

      TreeView
      - A0 (preferedHeight = 40)
      -- A1 (preferedHeight = 30)
      --- A2 (preferedHeight = 20)
      -- B1 (preferedHeight = 30)
      --- B2 (preferedHeight = 20)
      -- C1 (preferedHeight = 30)
      --- C2 (preferedHeight = 20)
      ... (repeat x-times)

      it always happens when you try to scrollTo(index) all of the 0-Level Nodes (assuming you have a larger number ~20) that one of those scrollTo(index) calls does not produces a result where the row ends up at the top. This error does not occur when all rows have the same height.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      - Create a TreeView with the described hierarchy and layout.
      - Try to scrollTo(index) to every index of every top level item.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Every row is positioned at the top. Except for those at the bottom.
      ACTUAL -
      At least one of those scrollTo(index) will not result in a row being positioned at the top (bottom rows excluded)

      ERROR MESSAGES/STACK TRACES THAT OCCUR :
      no error, visual bug.

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      package de.t2med.aps.client.praxis.statistik.karteikontrolle;

      import javafx.application.Application;
      import javafx.collections.FXCollections;
      import javafx.collections.ListChangeListener;
      import javafx.scene.Scene;
      import javafx.scene.control.Label;
      import javafx.scene.control.ListCell;
      import javafx.scene.control.ListView;
      import javafx.scene.control.SelectionMode;
      import javafx.scene.control.TreeCell;
      import javafx.scene.control.TreeItem;
      import javafx.scene.control.TreeView;
      import javafx.scene.layout.BorderPane;
      import javafx.stage.Stage;
      import javafx.util.Callback;
      import org.apache.commons.lang3.RandomStringUtils;
      import org.apache.commons.lang3.RandomUtils;

      import java.util.ArrayList;
      import java.util.List;
      import java.util.Locale;

      public class TreeViewExample extends Application {

      public static final int MIN_HEIGHT = 1024;
      public static final int MIN_WIDTH = 1280;
      private final BorderPane rootLayout = new BorderPane();
      private List<TreeViewData> data;
      private TreeView<TreeViewRowItem> treeView;
      private TreeItem<TreeViewRowItem> root;

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

      @Override
      public void start(final Stage primaryStage) throws Exception {
      Locale.setDefault(Locale.GERMANY);
      initData();
      initTreeView();
      initSelectionView();
      final Scene scene = new Scene(rootLayout);

      rootLayout.setPrefWidth(MIN_WIDTH);
      rootLayout.setPrefHeight(MIN_HEIGHT);

      primaryStage.setScene(scene);
      primaryStage.setMinWidth(MIN_WIDTH);
      primaryStage.setMinHeight(MIN_HEIGHT);
      primaryStage.centerOnScreen();
      primaryStage.show();
      }

      private void initData() {
      data = new ArrayList<>();
      for (int i = 0; i < 25; i++) {
      data.add(new TreeViewData(String.format("%s (%s)", RandomStringUtils.randomAlphanumeric(10), String.valueOf(i))));
      }
      }

      private void initSelectionView() {
      final ListView<TreeViewData> listView = new ListView<>();
      listView.setCellFactory(param -> new ListCell<TreeViewData>() {
      @Override
      protected void updateItem(final TreeViewData item, final boolean empty) {
      super.updateItem(item, empty);
      if (null == item) {
      setGraphic(null);
      } else {
      setGraphic(new Label(item.title));
      }
      }
      });
      listView.setItems(FXCollections.observableArrayList(data));
      listView.getSelectionModel().setSelectionMode(SelectionMode.SINGLE);
      listView.getSelectionModel().getSelectedItems().addListener((ListChangeListener<TreeViewData>) c -> scrollTo(c.getList().get(0)));
      rootLayout.setRight(listView);
      }

      private void initTreeView() {
      treeView = new TreeView<>();
      treeView.setCellFactory(new Callback<TreeView<TreeViewRowItem>, TreeCell<TreeViewRowItem>>() {
      @Override
      public TreeCell<TreeViewRowItem> call(final TreeView<TreeViewRowItem> param) {
      return new TreeCell<TreeViewRowItem>() {
      @Override
      protected void updateItem(final TreeViewRowItem item, final boolean empty) {
      super.updateItem(item, empty);
      if (null == item) {
      setGraphic(null);
      } else {
      final Label value = new Label(item.getText());
      switch (item.level) {
      case FIRST_LEVEL:
      value.setPrefHeight(40.0);
      break;
      case SECOND_LEVEL:
      value.setPrefHeight(30.0);
      break;
      case THIRD_LEVEL_B:
      case THIRD_LEVEL_C:
      case THIRD_LEVEL_A:
      value.setPrefHeight(20.0);
      break;
      }
      setGraphic(value);
      }
      }
      };
      }
      });
      root = new TreeItem<>();
      root.setGraphic(new Label("Root"));
      root.setExpanded(true);
      treeView.setRoot(root);
      treeView.setShowRoot(false);
      data.forEach(treeViewData -> {
      final TreeItem<TreeViewRowItem> treeItem = new TreeItem<>(new TreeViewRowItem(TreeViewDataType.FIRST_LEVEL, treeViewData, 0));
      treeItem.setExpanded(true);
      root.getChildren().add(treeItem);
      addChildren(treeViewData.childrenA, treeViewData, treeItem, true, TreeViewDataType.THIRD_LEVEL_A);
      addChildren(treeViewData.childrenB, treeViewData, treeItem, true, TreeViewDataType.THIRD_LEVEL_B);
      addChildren(treeViewData.childrenC, treeViewData, treeItem, false, TreeViewDataType.THIRD_LEVEL_C);
      });
      rootLayout.setCenter(treeView);
      }

      private void addChildren(final List<String> list, final TreeViewData treeViewData, final TreeItem<TreeViewRowItem> treeItem, final boolean expanded,
      final TreeViewDataType thirdLevel) {
      final TreeItem<TreeViewRowItem> headerItem = new TreeItem<>(new TreeViewRowItem(TreeViewDataType.SECOND_LEVEL, treeViewData, 0));

      treeItem.getChildren().add(headerItem);
      headerItem.setExpanded(expanded);
      for (int i = 0; i < list.size(); i++) {
      final TreeItem<TreeViewRowItem> child = new TreeItem<>(new TreeViewRowItem(thirdLevel, treeViewData, i));
      headerItem.getChildren().add(child);
      }
      }

      private void scrollTo(final TreeViewData treeViewData) {
      for (final TreeItem<TreeViewRowItem> item : root.getChildren()) {
      if (null != item.getValue() && treeViewData.equals(item.getValue().data)) {
      final int row = treeView.getRow(item);
      treeView.scrollTo(row);
      treeView.getSelectionModel().select(row);
      treeView.requestFocus();
      }
      }

      }

      private class TreeViewData {

      private final String title;

      private final List<String> childrenA = new ArrayList<>();
      private final List<String> childrenB = new ArrayList<>();
      private final List<String> childrenC = new ArrayList<>();

      public TreeViewData(final String title) {
      this.title = title;
      randomChildren(childrenA);
      randomChildren(childrenB);
      randomChildren(childrenC);
      }

      private void randomChildren(final List<String> list) {
      for (int i = 0; i < RandomUtils.nextInt(2, 8); i++) {
      list.add(RandomStringUtils.randomAlphabetic(25));
      }
      }
      }

      private class TreeViewRowItem {

      final TreeViewDataType level;
      final int index;
      final TreeViewData data;

      public TreeViewRowItem(final TreeViewDataType level, final TreeViewData data, final int index) {
      this.level = level;
      this.index = index;
      this.data = data;
      }

      public String getText() {
      switch (level) {
      case FIRST_LEVEL:
      return data.title;
      case SECOND_LEVEL:
      return "Header";
      case THIRD_LEVEL_A:
      return data.childrenA.get(index);
      case THIRD_LEVEL_B:
      return data.childrenB.get(index);
      case THIRD_LEVEL_C:
      return data.childrenC.get(index);
      }
      return "";
      }
      }

      enum TreeViewDataType {
      FIRST_LEVEL,
      SECOND_LEVEL,
      THIRD_LEVEL_B,
      THIRD_LEVEL_C,
      THIRD_LEVEL_A
      }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      None found. API too private.

            jgiles Jonathan Giles
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: