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

ChangeListener not triggered when adding a new listener in invalidated method

    XMLWordPrintable

Details

    • Bug
    • Resolution: Fixed
    • P3
    • jfx21
    • jfx13, 8u60
    • javafx
    • b13
    • x86
    • windows_10

    Description

      ADDITIONAL SYSTEM INFORMATION :
      Tested with:
      java version "1.8.0_73"
      Java(TM) SE Runtime Environment (build 1.8.0_73-b02)
      Java HotSpot(TM) Client VM (build 25.73-b02, mixed mode)

      and

      java version "1.8.0_202"
      Java(TM) SE Runtime Environment (build 1.8.0_202-b08)
      Java HotSpot(TM) Client VM (build 25.202-b08, mixed mode, sharing)

      Betriebssystemname: Microsoft Windows 10 Enterprise
      Betriebssystemversion: 10.0.15063 Nicht zutreffend Build 15063

      A DESCRIPTION OF THE PROBLEM :
      When you have a property with a single listener attached to it, this listener will initially not be triggered when the implementation of the invalidated method of this property adds a new listener for this property.
      Sounds a little bit unreal but it is a real use-case when e.g. using the showingProperty of a javafx stage in combination with the ProgressIndicator.
      Since the implementation of the invalidated method of the showingProperty in the javafx window class will force the creation of a new ProgressIndicatorSkin. Due to a memory leak with the progress indicator a fix introduced a new listener for the showing property to make sure that the animator is GB-Collected. So while the showingProperty gets invalidated another component adds a new listener to the showingProperty which will lead to a Generic n the property framework. The Generic gets initialized with the observables value which has actually change to true. After the invalidated method was executed the method fireValueChangedEvent is called. But since the current and the old value are the same the frameworks thinks that the value did not change.

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Look at the two sample codes provided.
      # Execute the Sample class
      # Press "Open Stage" the button
      # Close the opened stage

      The Sample2 class shows the same problem a little bit simplified without any UI but only the properties. It looks weird to do so but this is what actually happens in the first sample just without the overhead of using the ProgressIndicator.

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      As i have a listener on the showing property, i would expect two lines in my console window:
      (testing fails listener) oldValue=<false> newValue=<true>
      (testing fails listener) oldValue=<true> newValue=<false>
      ACTUAL -
      The console window contains just a sinle line
      (testing fails listener) oldValue=<true> newValue=<false>

      So for changing the property value from false to true the listener was obviously not called.

      ---------- BEGIN SOURCE ----------
      package de.sick.sopas.one.toolkit.fx.samples.apps;

      import javafx.application.Application;
      import javafx.beans.value.ChangeListener;
      import javafx.beans.value.ObservableValue;
      import javafx.event.ActionEvent;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.ProgressIndicator;
      import javafx.scene.paint.Color;
      import javafx.stage.Modality;
      import javafx.stage.Stage;

      public class Sample extends Application
      {
          private Stage owner;

          private class MyStage extends Stage
          {
              public MyStage()
              {
                  initModality(Modality.WINDOW_MODAL);
                  initOwner(owner);
                  setTitle("Test");
                  setWidth(100);
                  setHeight(100);
                  setMinWidth(100);
                  setMinHeight(100);
                  setScene(new Scene(new ProgressIndicator(), Color.WHITE));
              }
          }

          @Override
          public void start(Stage primaryStage) throws Exception
          {
              this.owner = primaryStage;

              final Button openButton = new Button("Open Stage");
              openButton.setOnAction(this::onOpenStage);
              primaryStage.setScene(new Scene(openButton));
              primaryStage.show();
          }

          private void onOpenStage(final ActionEvent e)
          {
              final MyStage stage = new MyStage();
              stage.showingProperty().addListener(new ChangeListener<Boolean>()
              {
                  @Override
                  public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue)
                  {
                      System.out.println("(testing fails listener) oldValue=<" + oldValue + "> newValue=<" + newValue + ">");
                  }
              });
              stage.showAndWait();
          }

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

      package de.sick.sopas.one.toolkit.fx.samples.apps;

      import javafx.beans.property.ReadOnlyBooleanProperty;
      import javafx.beans.property.ReadOnlyBooleanWrapper;

      public class Sample2
      {
          private final ReadOnlyBooleanWrapper showingProperty = new ReadOnlyBooleanWrapper(false)
          {
              protected void invalidated()
              {
                  System.out.println("Invalidated");
                  showingProperty().addListener((observable, oldValue, newValue) -> {
                      System.out.println("oldValue=<" + oldValue + "> newValue=<" + newValue + ">");
                  });
              };
          };

          public void setShowing(boolean value)
          {
              showingProperty.set(value);
          }

          public ReadOnlyBooleanProperty showingProperty()
          {
              return showingProperty.getReadOnlyProperty();
          }

          public static void main(String[] args)
          {
              final Sample2 s = new Sample2();
              s.showingProperty().addListener((observable, oldValue, newValue) -> {
                  System.out.println("(testing fails listener) oldValue=<" + oldValue + "> newValue=<" + newValue + ">");
              });
              s.setShowing(true);
          }
      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      You can either use an InvalidationListener or adding a second ChangeListener to the property before the first change will also solve this issue. This will force the creation of a Generic in the ExpressionHelper instead of a SingleChange and the value will not be overridden anymore.

      FREQUENCY : always


      Attachments

        Issue Links

          Activity

            People

              jhendrikx John Hendrikx
              webbuggrp Webbug Group
              Votes:
              0 Vote for this issue
              Watchers:
              6 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: