在每次 window 出现后复制 属性 的 ChangeListener

Duplication of Property's ChangeListener after every window's appearance

假设我们有一个根 window 和 fx:include:

<?import javafx.scene.layout.VBox?>

<VBox fx:controller="sample.StartWindowController"
      xmlns:fx="http://javafx.com/fxml" alignment="center">
    <fx:include source="startwindow.fxml"/>
</VBox>

startwindow.fxml代码:

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>

<VBox fx:id="mainPane" fx:controller="sample.StartWindowController"
      xmlns:fx="http://javafx.com/fxml" alignment="center">
    <Button text="Go to New Window" onAction="#goToNewWindow"/>
</VBox>

单击 Button 将 window 更改为新的。它的控制器,StartWindowController:

package sample;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import java.io.IOException;

public class StartWindowController {

  @FXML
  VBox mainPane;

  @FXML
  private void goToNewWindow() {
    Pane parentPane = (Pane) mainPane.getParent();
    parentPane.getChildren().clear();
    try {
      parentPane.getChildren().add(FXMLLoader.load(getClass().getResource("newwindow.fxml")));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }
}

在我向您展示 'New Window' 的视图和控制器之前,您必须知道应用程序有一个单例 class 和 BooleanProperty 字段。 MySingleton代码:

package sample;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class MySingleton {

  private static MySingleton instance;
  private BooleanProperty booleanProperty;

  private MySingleton() {
    booleanProperty = new SimpleBooleanProperty(false);
  }

  public static MySingleton getInstance() {
    if (instance == null) {
      instance = new MySingleton();
    }
    return instance;
  }

  public boolean isBooleanProperty() {
    return booleanProperty.get();
  }

  public BooleanProperty booleanPropertyProperty() {
    return booleanProperty;
  }
}

newwindow.fxml代码:

<?import javafx.scene.control.Button?>
<?import javafx.scene.layout.VBox?>

<VBox fx:id="mainPane" fx:controller="sample.NewWindowController"
      xmlns:fx="http://javafx.com/fxml" alignment="center">
    <Button text="Change BooleanProperty" onAction="#changeBooleanProperty"/>
    <Button text="Back" onAction="#goBack"/>
</VBox>

在 'New Window' 创建过程中,我在控制器的 initialize 方法中向 MySingletonBooleanProperty 添加了一个侦听器。新的侦听器代码引用了非静态控制器的私有方法,printMessageNewWindowController的代码:

package sample;

import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;
import java.io.IOException;

public class NewWindowController {

  private MySingleton mySingleton;
  @FXML VBox mainPane;
  private int duplicateCounter = 1;

  @FXML private void initialize() {
    System.out.println("Initializing New Window's Controller.");
    System.out.println("Duplicate counter: " + duplicateCounter);
    mySingleton = MySingleton.getInstance();
    mySingleton.booleanPropertyProperty().addListener(
            (observable, oldValue, newValue) -> printMessage());
    duplicateCounter++;
  }

  @FXML private void changeBooleanProperty() {
    mySingleton.booleanPropertyProperty().setValue(!mySingleton.booleanPropertyProperty().getValue());
  }

  @FXML private void goBack() {
    Pane parentPane = (Pane) mainPane.getParent();
    parentPane.getChildren().clear();
    try {
      parentPane.getChildren().add(FXMLLoader.load(getClass().getResource("startwindow.fxml")));
    } catch (IOException e) {
      e.printStackTrace();
    }
  }

  private void printMessage() {
    System.out.println("Boolean property changed!");
  }
}

现在,问题来了。假设第一步是去 'New Window':
在每个 "Back" -> "Go to New Window" 序列之后,当我单击 "Change BooleanProperty" 时,有 i+1 个 "Boolean property changed!" 的打印件,其中 i 是 [= 的数量69=] -> 'New Window' 转换(从 0 开始)。为什么不是只有一个打印?

我知道每次启动 'New Window' 时,应用程序都会向 BooleanProperty 添加新的侦听器,问题可能是由多个 属性 引起的听众。但是,如果在新的侦听器代码中我引用了在 window 转换后被销毁的对象的非静态方法,那怎么可能呢?

我认为 "Maybe controller is not destroyed? Maybe initialize method works in a way I don't understand and controller's object is still there?" 所以正如您可能看到的那样,我添加了一个额外的变量 duplicateCounter,它在 initialize 方法的末尾递增。但每次都是 1,所以我假设创建了全新的 NewWindowController 对象。

如何防止 BooleanProperty 听众重复?

But how is it possible if in a new listener's code I refer to the non-static method of the object which is destroyed after window transition?

I thought "Maybe controller is not destroyed? Maybe initialize method works in a way I don't understand and controller's object is still there?" So as you probably see, I added an extra variable, duplicateCounter which increments at the end of the initialize method. But every time it's 1, so I assume the brand new NewWindowController object is created.

确实,每次加载 fxml 时都会创建一个新控制器,但是旧控制器不是 "destroyed"(可用于垃圾回收),因为仍然有对该对象的引用:

MySingleton 将实例存储在静态成员中,使其无法用于垃圾回收。此实例包含对 BooleanProperty 的引用,其中包含对侦听器的引用,侦听器包含对 NewWindowController.

的引用

要只打印一次消息,您必须取消注册侦听器:

private final ChangeListener<Boolean> listener = (observable, oldValue, newValue) -> printMessage();

@FXML private void initialize() {
    ...
    mySingleton.booleanPropertyProperty().addListener(listener);
    ...
}

...

@FXML private void goBack() {
    // remove listener
    MySingleton.getInstance().booleanPropertyProperty().removeListener(listener);        
    ...
}