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

时间:2016-09-11 14:56:10

标签: javafx data-binding

我在控制器中有一个Spinner

@FXML
private Spinner<Integer> spnMySpinner;

和控制器中的SimpleIntegerProperty

private static final SimpleIntegerProperty myValue = 
new SimpleIntegerProperty(3); //load a default value

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

spnMySpinner.getValueFactory().valueProperty().bindBidirectional(myValueProperty().asObject());

但绑定仅在控制器初始化第二次后才能正常工作。以下是我如何重现它:

  1. 我使用关联的控制器打开舞台,它会正确加载myValue属性中指定的默认值(数字3)。
  2. 我单击微调器上的增量按钮使其成为4.它更改微调器的value属性中的值,但绑定属性myValue保持不变,编号为3。
  3. 关闭舞台/窗口。
  4. 我重新打开它,旋转器的值再次为3。
  5. 我再次增加它。繁荣现在绑定工作,我有一个&#34; 4&#34;在微调器和绑定属性中都有。
  6. 完整简约但可启动/可重现的代码:

    Main.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);
        }
    
    }
    

    MainWindow.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.layout.AnchorPane?>
    
    
    <AnchorPane fx:id="myRoot" id="AnchorPane" prefHeight="231.0" prefWidth="337.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8.0.60" fx:controller="spinnerpoc.MainWindowController">
        <children>
            <Button fx:id="btnOpenSpinnerWindow" layoutX="102.0" layoutY="103.0" mnemonicParsing="false" text="Open SpinnerWindow" onAction="#onOpenSpinnerWindow"/>
        </children>
    </AnchorPane>
    

    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();
        }
    
    }
    

    SpinnerWindow.fxml:

    <?xml version="1.0" encoding="UTF-8"?>
    
    <?import javafx.scene.control.Button?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.RadioButton?>
    <?import javafx.scene.control.ScrollPane?>
    <?import javafx.scene.control.Slider?>
    <?import javafx.scene.control.Spinner?>
    <?import javafx.scene.control.SpinnerValueFactory.DoubleSpinnerValueFactory?>
    <?import javafx.scene.control.TitledPane?>
    <?import javafx.scene.control.ToggleGroup?>
    <?import javafx.scene.layout.HBox?>
    <?import javafx.scene.layout.VBox?>
    
    
    <ScrollPane xmlns:fx="http://javafx.com/fxml/1" fitToWidth="true" prefHeight="200.0" prefWidth="200.0" xmlns="http://javafx.com/javafx/8.0.60" fx:controller="spinnerpoc.SpinnerWindowController">
        <content>
            <VBox maxWidth="1.7976931348623157E308">
                <children>
                    <Spinner fx:id="spnMySpinner" editable="true" prefWidth="50.0" max="10" min="1" />
                </children>
            </VBox>
        </content>
    </ScrollPane>
    

    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());
        }
    
    }
    

    (代码也可在BitBucket repo处获得。)

    我错过了什么?

1 个答案:

答案 0 :(得分:3)

您正在遇到“过早的垃圾收集”问题。请参阅此here的说明。您可能会发现,并不总是每隔一段时间向旋转器显示它失败,但它只是零星的,并且行为会因机器而异。如果限制JVM可用的内存,您可能会发现它永远不会起作用。

当您致电IntegerProperty.asObject()时,

  

创建与此ObjectProperty双向绑定的IntegerProperty

现在请注意,双向绑定有this feature to prevent accidental memory leaks

  

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

因此,您显式创建的双向绑定不会阻止它被绑定的东西(ObjectProperty<Integer>创建的asObject())被垃圾收集。由于您没有引用它,因此只要退出initialize()中的SpinnerWindow Controller方法,它就有资格进行垃圾回收。显然,一旦你的微调器值双向绑定的值被垃圾收集,绑定将不再起作用。

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

<ScrollPane onMouseClicked="#gc" xmlns:fx="http://javafx.com/fxml/1" ...>

在SpinnerWindow.fxml和

@FXML
private void gc() {
    System.out.println("Invoking GC");
    System.gc();
}
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);
    }

}