为什么我的 Spinner 在第一次初始化时不更新其绑定属性?

Lep*_*aun 3 data-binding javafx

我有一个Spinner控制器:

@FXML
private Spinner<Integer> spnMySpinner;
Run Code Online (Sandbox Code Playgroud)

和一个SimpleIntegerProperty控制器:

private static final SimpleIntegerProperty myValue = 
new SimpleIntegerProperty(3); //load a default value
Run Code Online (Sandbox Code Playgroud)

我已在控制器的方法中将它们绑定在一起initialize

spnMySpinner.getValueFactory().valueProperty().bindBidirectional(myValueProperty().asObject());
Run Code Online (Sandbox Code Playgroud)

但只有在控制器第二次初始化后,绑定才能正常工作。我可以通过以下方式重现它:

  1. 我使用关联的控制器打开舞台,它会加载属性中myValue正确指定的默认值(数字 3)。
  2. 我单击微调器上的增量按钮将其设置为 4。它更改了微调器的 value 属性中的值,但绑定属性myValue保持不变,仍为数字 3。
  3. 我关闭舞台/窗户。
  4. 我重新打开它,微调器的值再次变为 3。
  5. 我再次增加它。现在绑定工作了,我在微调器和绑定属性内都有一个“4”。

整个简约但可启动/可复制的代码:

主要.java:

package spinnerpoc;

import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        Parent root  = FXMLLoader.load(getClass().getResource("MainWindow.fxml"));
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }

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

}
Run Code Online (Sandbox Code Playgroud)

主窗口.fxml:

@FXML
private Spinner<Integer> spnMySpinner;
Run Code Online (Sandbox Code Playgroud)

MainWindowController.java:

package spinnerpoc;

import java.io.IOException;
import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Modality;
import javafx.stage.Stage;

public class MainWindowController implements Initializable {

    @FXML
    private Button btnOpenSpinnerWindow;
    @FXML
    private AnchorPane myRoot;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
    }

    @FXML
    private void onOpenSpinnerWindow(ActionEvent event) throws IOException{
        FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("SpinnerWindow.fxml"));
        Parent root = (Parent) fxmlLoader.load();
        Stage stage = new Stage();
        stage.initOwner(myRoot.getScene().getWindow());
        stage.initModality(Modality.WINDOW_MODAL);
        stage.setTitle("SpinnerWindow");
        stage.setScene(new Scene(root));
        stage.show();
    }

}
Run Code Online (Sandbox Code Playgroud)

SpinnerWindow.fxml:

private static final SimpleIntegerProperty myValue = 
new SimpleIntegerProperty(3); //load a default value
Run Code Online (Sandbox Code Playgroud)

SpinnerWindowController.java:

package spinnerpoc;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Spinner;

public class SpinnerWindowController implements Initializable {

    private static final SimpleIntegerProperty myValue = new SimpleIntegerProperty(3);

    public static SimpleIntegerProperty myValueProperty() {
        return myValue;
    }

    public static Integer getMyValue() {
        return myValue.getValue();
    }

    public static void setMyValue(int value) {
        myValue.set(value);
    }

    @FXML
    private Spinner<Integer> spnMySpinner;

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        spnMySpinner.getValueFactory().valueProperty().bindBidirectional(myValueProperty().asObject());
    }

}
Run Code Online (Sandbox Code Playgroud)

(代码也可以在BitBucket 存储库中找到。)

我缺少什么?

Jam*_*s_D 5

您遇到了“过早的垃圾收集”问题。请参阅此处的描述。您可能会发现,并非每次都向旋转器显示失败,而是偶尔出现,并且行为会因一台机器而异。如果限制 JVM 可用的内存,您可能会发现它永远无法工作。

当你打电话时IntegerProperty.asObject(),它

创建一个ObjectProperty双向绑定到 this 的 that IntegerProperty

现在请注意,双向绑定具有此功能来防止意外的内存泄漏

JavaFX 双向绑定实现使用弱侦听器。这意味着双向绑定不会阻止属性被垃圾收集。

因此,您显式创建的双向绑定不会阻止它所绑定的事物(由 所ObjectProperty<Integer>创建asObject())被垃圾收集。由于您没有保留对它的引用,因此一旦您initialize()退出SpinnerWindow Controller. 显然,一旦微调器值双向绑定到的值被垃圾收集,绑定将不再起作用。

仅出于演示目的,您可以通过放置一个钩子来强制垃圾回收来看到这一点。例如做

<ScrollPane onMouseClicked="#gc" xmlns:fx="http://javafx.com/fxml/1" ...>
Run Code Online (Sandbox Code Playgroud)

在 SpinnerWindow.fxml 中和

@FXML
private void gc() {
    System.out.println("Invoking GC");
    System.gc();
}
Run Code Online (Sandbox Code Playgroud)

在 SpinnerWindowController 中。如果执行此操作,则单击滚动窗格将强制垃圾回收,并且更改微调器值将不会更新属性。

要解决此问题,请保留对您从以下位置获取的属性的引用asObject()

public class SpinnerWindowController implements Initializable {

    private static final SimpleIntegerProperty myValue = new SimpleIntegerProperty(3);

    public static SimpleIntegerProperty myValueProperty() {
        return myValue;
    }

    public static Integer getMyValue() {
        return myValue.getValue();
    }

    public static void setMyValue(int value) {
        myValue.set(value);
    }

    @FXML
    private Spinner<Integer> spnMySpinner;

    private ObjectProperty<Integer> spinnerValue = myValueProperty().asObject();

    @Override
    public void initialize(URL url, ResourceBundle rb) {
        spnMySpinner.getValueFactory().valueProperty().bindBidirectional(spinnerValue);
    }

}
Run Code Online (Sandbox Code Playgroud)