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

Wrong rendering order after Node.toFront() with nonzero viewOrder

XMLWordPrintable

    • x86_64
    • windows_10

      ADDITIONAL SYSTEM INFORMATION :
      Windows 10, AdoptOpenJDK 11.0.6+10, JavaFX 14.0.1

      A DESCRIPTION OF THE PROBLEM :
      Node.toFront() never causes Parent to set the flag that marks its viewOrderChildren dirty.
      Thus, after a Node.toFront() call when a previously higher-indexed sibling has an equal nonzero viewOrder, the Node still renders behind the now lower-indexed sibling.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Set equal nonzero ViewOrders on two sibling Nodes and lay them out overlapping.
      Display them.
      Call Node.toFront() on the sibling at the lower children index, which moves it to a higher children index than the sibling.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      The Node at the higher children index should render in front of the sibling Node that still has an equal nonzero ViewOrder.

      From javadoc of method Node.viewOrderProperty():

      "If two children have the same viewOrder, the parent will traverse them in the order they appear in the parent's children list."

      The code in method Parent.computeViewOrderChildren() produces a render order consistent with that javadoc. The problem is that the flag to call computeViewOrderChildren() does not get set upon calling Node.toFront().
      ACTUAL -
      The Node at the higher children index still renders behind its sibling Node.

      ---------- BEGIN SOURCE ----------
      import javafx.application.Application;
      import javafx.application.Platform;
      import javafx.collections.ListChangeListener;
      import javafx.scene.Node;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.CheckBox;
      import javafx.scene.layout.Pane;
      import javafx.stage.Stage;

      /**
       * Demonstrates the bug in which {@code Parent.children} fails to set the flag to recompute
       * {@code Parent.viewOrderChildren} when {@link Node#toFront()} changes the children order
       * while children have nonzero {@link Node#viewOrderProperty()}.
       **/
      public class NodeToFrontBugDemo extends Application {

         @Override
         public void start( final Stage primaryStage ) throws Exception {
            final CheckBox workaroundCheckBox = new CheckBox( "Apply bug workaround after toFront()" );
            workaroundCheckBox.setLayoutX( 200 );
            workaroundCheckBox.setLayoutY( 300 );
            final Button button1 = new Button();
            button1.setViewOrder( -1 );
            button1.setOnAction( __ -> {
               button1.toFront();
               if( workaroundCheckBox.isSelected() ) {
                  // Temporarily change node's view order, then change it back.
                  // This causes a call to Parent.markViewOrderChildrenDirty().
                  button1.setViewOrder( 0 );
                  button1.setViewOrder( -1 );
               }
            } );
            final Button button2 = new Button();
            button2.setViewOrder( -1 );
            button2.setOnAction( __ -> {
               button2.toFront();
               if( workaroundCheckBox.isSelected() ) {
                  // Temporarily change node's view order, then change it back.
                  // This causes a call to Parent.markViewOrderChildrenDirty().
                  button2.setViewOrder( 0 );
                  button2.setViewOrder( -1 );
               }
            } );
            final Pane pane = new Pane( workaroundCheckBox, button1, button2 );
            final Runnable button1TextUpdater = () -> button1.setText(
                  "Click to bring to front. Children index: "
                        + pane.getChildren().indexOf( button1 ) + ", viewOrder: " + button1.getViewOrder() );
            button1TextUpdater.run();
            pane.getChildrenUnmodifiable().addListener( (ListChangeListener<Node>) __ -> button1TextUpdater.run() );
            button1.viewOrderProperty().addListener( ( __, ___, ____ ) -> button1TextUpdater.run() );
            final Runnable button2TextUpdater = () -> button2.setText(
                  "Click to bring to front. Children index: "
                        + pane.getChildren().indexOf( button2 ) + ", viewOrder: " + button2.getViewOrder() );
            button2TextUpdater.run();
            pane.getChildrenUnmodifiable().addListener( (ListChangeListener<Node>) __ -> button2TextUpdater.run() );
            button1.viewOrderProperty().addListener( ( __, ___, ____ ) -> button2TextUpdater.run() );
            primaryStage.setTitle( "Node.toFront() with nonzero viewOrder bug demo" );
            primaryStage.setScene( new Scene( pane, 600, 500 ) );
            primaryStage.show();
            Platform.runLater( () -> {
               button1.setLayoutX( 100 );
               button1.setLayoutY( 200 );
               button2.setLayoutX( 100 + 0.5 * button1.getLayoutBounds().getWidth() );
               button2.setLayoutY( 200 + 0.5 * button1.getLayoutBounds().getHeight() );
            } );
         }

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

      CUSTOMER SUBMITTED WORKAROUND :
      After Node.toFront(), temporarily change the Node's ViewOrder and then change it back to what you want, which causes a call to Parent.markViewOrderChildrenDirty(). You cannot call that method directly because it is private API.

      FREQUENCY : always


            kcr Kevin Rushforth
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated: