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

Stage 'showingProperty' listener not called if lone and stage is closed from a PopupWindow child

XMLWordPrintable

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

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [version 10.0.10586]

      A DESCRIPTION OF THE PROBLEM :
      When creating a Stage which:
      * has only one 'showingProperty' listener set by user
      * contains a PopupWindow (e.g. through a DatePicker)
      * is closed by an action event handler related to the PopupWindow
      Then the listener is not called when the stage is closed (and 'showing' changed from 'true' to 'false').

      As far as I can understand:
      1. The PopupWindow, when being shown, registers a WeakListener
      2. Upon closing the parent Stage, its 'showing' property is set to 'false', which triggers the child PopupWindow to being closed (before notifying the listeners)
      3. Upon being closed the PopupWindow removes its WeakListener from the parent Stage
      4. The ExpressionHelper class which handles the actual listener removal, as a special case (2 listeners known, 0 invalid listener known; see below) creates a new SingleChange from the original observable with the remaining listener
        -> the original observable is the parent Stage 'showingProperty', which value has already been changed to 'false'
        -> the remaining listener is the one the user set
      5. Then finally the 'showingProperty' change is notified to listeners, which ends up being handled by the SingleChange created in step 4 which does nothing because from its point of view the value was not changed (since it was ceated with 'false' already)

      The ExpressionHelper code that does create a new SingleChange from the observable (which value has already been changed):

              @Override
              protected ExpressionHelper<T> removeListener(ChangeListener<? super T> listener) {
                  if (changeListeners != null) {
                      for (int index = 0; index < changeSize; index++) {
                          if (listener.equals(changeListeners[index])) {
                              if (changeSize == 1) {
      ...
                              } else if ((changeSize == 2) && (invalidationSize == 0)) {
                                  return new SingleChange<T>(observable, changeListeners[1-index]);
                              } else {
      ...


      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Provided sample code does reproduce the issue by hitting the 'Fails' button and picking a date.

      Hitting either of 'Fails' or 'Works' buttons will create a transparent modal Stage containing a DatePicker.
      Selecting a date will close this stage. Until closed the parent window will be blurred.

      The 'Fails' button triggers the issue because it attaches only one listener.
      The 'Works' button does not trigger the issue because it attaches two listeners (ExpressionHelper listener removal does not goes through the special case).


      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      When hitting the 'Fails' button, the following line should be printed because the modal Stage is being shown:
      (testing fails listener) oldValue=<false> newValue=<true>

      Then upon picking a date the following line should be printed because the modal Stage is being closed:
      (testing fails listener) oldValue=<true> newValue=<false>

      ACTUAL -
      Due to the bug, only the first line is printed:
      (testing fails listener) oldValue=<false> newValue=<true>

      Upon picking a date, the second line is never printed (meaning the listener is not called).


      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      package sample;

      import javafx.application.Application;
      import javafx.geometry.Pos;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.DatePicker;
      import javafx.scene.effect.BoxBlur;
      import javafx.scene.layout.StackPane;
      import javafx.scene.layout.VBox;
      import javafx.scene.paint.Color;
      import javafx.stage.Modality;
      import javafx.stage.Stage;
      import javafx.stage.StageStyle;
      import javafx.stage.Window;

      public class Main extends Application {

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

          @Override
          public void start(Stage primaryStage) throws Exception {
              VBox vbox = new VBox();
              vbox.setSpacing(10);
              vbox.setAlignment(Pos.CENTER);

              Button buttonFails = new Button("Fails");
              buttonFails.setOnAction(event -> onFails(primaryStage));

              Button buttonWorks = new Button("Works");
              buttonWorks.setOnAction(event -> onWorks(primaryStage));

              vbox.getChildren().addAll(buttonFails, buttonWorks);

              primaryStage.setTitle("Sample");
              primaryStage.setScene(new Scene(vbox, 300, 300));
              primaryStage.show();
          }


          private void onFails(Stage primaryStage) {
              Stage stage = buildModal(primaryStage);
              stage.showingProperty().addListener((observable, oldValue, newValue) -> {
                  System.out.println("(testing fails listener) oldValue=<" + oldValue + "> newValue=<" + newValue + ">");
              });
              showModal(primaryStage, stage);
          }

          private void onWorks(Stage primaryStage) {
              Stage stage = buildModal(primaryStage);
              stage.showingProperty().addListener((observable, oldValue, newValue) -> {
                  System.out.println("(testing works listener1) oldValue=<" + oldValue + "> newValue=<" + newValue + ">");
              });
              stage.showingProperty().addListener((observable, oldValue, newValue) -> {
                  System.out.println("(testing works listener2) oldValue=<" + oldValue + "> newValue=<" + newValue + ">");
              });
              showModal(primaryStage, stage);
          }

          private Stage buildModal(Window parent) {
              Stage stage = new Stage();
              stage.initStyle(StageStyle.TRANSPARENT);
              stage.initOwner(parent);
              stage.initModality(Modality.WINDOW_MODAL);

              DatePicker picker = new DatePicker();

              StackPane contentPane = new StackPane();
              contentPane.getChildren().add(picker);
              contentPane.setStyle(" -fx-padding: 20;\n" +
                      " -fx-alignment: center;\n" +
                      " -fx-background-color: linear-gradient(to bottom, derive(cadetblue, 20%), cadetblue);\n" +
                      " -fx-border-color: derive(cadetblue, -20%);\n" +
                      " -fx-border-width: 5;\n" +
                      " -fx-border-radius: 6;\n" +
                      " -fx-background-radius: 6;\n");
              Scene scene = new Scene(contentPane, Color.TRANSPARENT);
              stage.setScene(scene);

              picker.setOnAction(event -> stage.close());

              return stage;
          }

          private void showModal(Stage primaryStage, Stage stage) {
              primaryStage.getScene().getRoot().setEffect(new BoxBlur());
              stage.showAndWait();
              primaryStage.getScene().getRoot().setEffect(null);
          }

      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      Attaching more than one listener prevents the issue, because there the special case in ExpressionHelper listener removal is not executed, and all remaining listeners are simply copied in a new Array.

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

              Created:
              Updated: