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

TreeTableView relayouts wrongly after requesting refresh

XMLWordPrintable

    • generic
    • generic

      ADDITIONAL SYSTEM INFORMATION :
      Microsoft Windows [Version 10.0.18363.1198]
      openjdk version "11.0.5" 2019-10-15
      OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.5+10)
      OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.5+10, mixed mode)
      JavaFX version: 17.0.1+1 - https://github.com/openjdk/jfx17u/releases/tag/17.0.1%2B1

      A DESCRIPTION OF THE PROBLEM :
      TreeTableView's VirtualFlow loses cached item sizes after requesting javafx.scene.control.TreeTableView#refresh which leads to strange relayouting - scrollbar position and estimated size is wrongly computed. This is replicable while table cells has different sizes - not fixed ones.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      1. Run test case application.
      2. Scroll vertically to the bottom.
      3. Press "Refresh" button.
      4. Press "Run Pulse" button.
      5. Scroll to the top.
      6. Scroll to the bottom and press "Toggle fixed cell size" button.
      7. Press "Toggle fixed cell size" button again.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      After 1. Application presents table with tree structure, second tree item is expanded and first level items have smaller size than expanded ones.
      After 2. Rows are presented properly, scrollbar's thumb is at the bottom.
      After 4. State does not change, items are presented same as After 2.
      After 5. Rows are presented properly, first item is visible.
      After 6. Rows are presented in fixed size.
      After 7. Rows are presented in non-fixed size, as After 2.
      ACTUAL -
      After 1. Application presents table with tree structure, second tree item is expanded and first level items have smaller size than expanded ones.
      After 2. Rows are presented properly, scrollbar's thumb is at the bottom.
      After 4. Table layouts breaks, rows are presented in strange way, but will go back to normal after scrolling to them top.
      After 5. Rows are presented properly, first item is visible.
      After 6. Rows are presented in fixed size.
      After 7. There is a table layouting issue, scrollbar thumb is taking most of the scrollbar's space, last tree row is not visible, as it was previously, but will go back to normal after scrolling to top/bot.

      ---------- BEGIN SOURCE ----------
      import com.sun.javafx.tk.Toolkit;
      import javafx.application.Application;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.TreeItem;
      import javafx.scene.control.TreeTableCell;
      import javafx.scene.control.TreeTableColumn;
      import javafx.scene.control.TreeTableRow;
      import javafx.scene.control.TreeTableView;
      import javafx.scene.control.cell.TreeItemPropertyValueFactory;
      import javafx.scene.layout.HBox;
      import javafx.scene.layout.Region;
      import javafx.scene.layout.VBox;
      import javafx.stage.Stage;

      public class Test_treeTableView_itemSizeCacheAfterRefresh
      {

          public static void main( String[] args )
          {
              Application.launch( FxApp.class, args );
          }

          public static class FxApp extends Application
          {
              @Override
              public void start( Stage primaryStage )
              {
                  System.err.println( System.getProperty( "javafx.version" ) );

                  HBox rootHBox = new HBox( 10.0 );
                  Scene scene = new Scene( rootHBox, 800, 600 );

                  final TreeTableView< SampleData > treeTableView = new TreeTableView<>();
                  final TreeItem< SampleData > root = new TreeItem<>();
                  for( int i = 0; i < 20; i += 2 )
                  {
                      final TreeItem< SampleData > firstLevel_1 =
                          new TreeItem<>( new SampleData( "FirstLevel_" + i, 50 ) );

                      firstLevel_1.getChildren().add( new TreeItem<>( new SampleData( "SecondLevel_1", 50 ) ) );
                      root.getChildren().add( firstLevel_1 );
                      final TreeItem< SampleData > firstLevel_2 =
                          new TreeItem<>( new SampleData( "FirstLevel_" + ( i + 1 ), 50 ) );
                      for( int j = 0; j < 10; j++ )
                      {
                          firstLevel_2.getChildren()
                              .add( new TreeItem<>( new SampleData( "SecondLevel_" + j, 250 ) ) );
                      }
                      root.getChildren().add( firstLevel_2 );
                  }
                  root.getChildren().get( 1 ).setExpanded( true );
                  treeTableView.setRoot( root );
                  treeTableView.setShowRoot( false );
                  root.setExpanded( true );
                  final TreeTableColumn< SampleData, String > dataCol = new TreeTableColumn<>( "data" );
                  dataCol.setMinWidth( 300 );
                  dataCol.setCellValueFactory( new TreeItemPropertyValueFactory<>( "data" ) );
                  dataCol.setCellFactory( param -> {
                      final TreeTableCell< SampleData, String > cell = new TreeTableCell<>()
                      {
                          @Override
                          protected void updateItem( final String item, final boolean empty )
                          {
                              if( item == getItem() )
                              {
                                  return;
                              }

                              super.updateItem( item, empty );

                              if( item == null )
                              {
                                  super.setText( null );
                                  super.setGraphic( null );
                              }
                              else
                              {
                                  super.setText( item.toString() );
                                  super.setGraphic( null );
                              }
                          }
                      };
                      return cell;
                  } );
                  treeTableView.setFixedCellSize( Region.USE_COMPUTED_SIZE );
                  treeTableView.getColumns().add( dataCol );
                  treeTableView.setRowFactory( param -> {
                      return new TreeTableRow<>()
                      {

                          @Override
                          protected double computeMinHeight( final double width )
                          {
                              if( isFixedCellsEnabled() )
                              {
                                  return super.computeMinHeight( width );
                              }
                              final SampleData item = getItem();
                              if( item != null )
                              {
                                  return item.getHeight();
                              }
                              return super.computeMinHeight( width );
                          }

                          @Override
                          protected double computePrefHeight( final double width )
                          {
                              if( isFixedCellsEnabled() )
                              {
                                  return super.computePrefHeight( width );
                              }
                              final SampleData item = getItem();
                              if( item != null )
                              {
                                  return item.getHeight();
                              }
                              return super.computePrefHeight( width );
                          }

                          @Override
                          protected double computeMaxHeight( final double width )
                          {
                              if( isFixedCellsEnabled() )
                              {
                                  return super.computeMaxHeight( width );
                              }
                              final SampleData item = getItem();
                              if( item != null )
                              {
                                  return item.getHeight();
                              }
                              return super.computeMaxHeight( width );
                          }

                          private boolean isFixedCellsEnabled()
                          {
                              return treeTableView.getFixedCellSize() > 0;
                          }

                      };
                  } );
                  // WORKAROUND uncomment
                  // treeTableView.setRowSizeSupplier( treeItem -> {
                  // if( treeItem.getValue() != null )
                  // {
                  // return treeItem.getValue().getHeight();
                  // }
                  // return null;
                  // } );

                  final var gcButton = new Button( "Run GC" );
                  gcButton.setOnAction( e -> System.gc() );
                  final var refreshButton = new Button( "Refresh" );
                  refreshButton.setOnAction( action -> {
                      treeTableView.refresh();
                  } );
                  final var pulseButton = new Button( "Run Pulse" );
                  pulseButton.setOnAction( action -> {
                      Toolkit.getToolkit().firePulse();
                  } );
                  final var fixedCellSizeButton = new Button( "Toggle fixed cell size" );
                  fixedCellSizeButton.setOnAction( action -> {
                      if( treeTableView.getFixedCellSize() > 0 )
                      {
                          treeTableView.setFixedCellSize( Region.USE_COMPUTED_SIZE );
                      }
                      else
                      {
                          treeTableView.setFixedCellSize( 20d );
                      }
                  } );
                  final VBox buttons = new VBox( 5, gcButton, refreshButton, pulseButton, fixedCellSizeButton );
                  rootHBox.getChildren().addAll( treeTableView, buttons );

                  primaryStage.setScene( scene );
                  primaryStage.show();
              }
          }

          public static class SampleData
          {
              String data;
              double height;

              public SampleData( final String aData, final double aHeight )
              {
                  data = aData;
                  height = aHeight;
              }

              public String getData()
              {
                  return data;
              }

              public double getHeight()
              {
                  return height;
              }
          }

      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      1. Apply changes from https://github.com/xardif/jfx/commit/d22ff1b2d308ea16c6f6916098f4e27538057463 to your javafx version.
      2. Uncomment // WORKAROUND uncomment part of "test case".

      FREQUENCY : always


            Unassigned Unassigned
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: