Java绑定:BindBidirectional不起作用

时间:2015-11-05 13:21:12

标签: java binding javafx jvm

已更新

我的问题是双向绑定(bindBidirectional)不起作用。

我找到了使用以下代码重现行为的方法:

这是FXML:

<VBox spacing="5.0" xmlns="http://javafx.com/javafx/null" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.test.MyTestPaneView">
   <children>
      <Label text="This is a Test Pane" />
      <ScrollPane fitToWidth="true">
         <content>
            <FlowPane fx:id="pnl" />
         </content>
      </ScrollPane>
   </children>
</VBox>

这是控制器类:

public class MyTestPaneView implements Initializable{
    private static final int MAX = 1000;
    private static final Random RANDOM = new Random();

    @FXML
    private FlowPane pnl;

    private final BooleanProperty[] modelArray = new BooleanProperty[MAX];
    private final Node[] viewArray = new Node[MAX];

    private final ToggleGroup rbGrp = new ToggleGroup(), btnGrp = new ToggleGroup();

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        for (int i=0; i<MAX; i++) {
            final int index = i;

            modelArray[i] = new SimpleBooleanProperty();
            modelArray[i].addListener(observable -> System.out.println("Model Update: " + index));

            switch (Math.abs(RANDOM.nextInt() % 3)) {
                case 0:
                    final CheckBox ckb = new CheckBox("CKB " + i);
                    ckb.selectedProperty().addListener(observable -> System.out.println("View Update: " + index));
                    ckb.selectedProperty().bindBidirectional(modelArray[i]);
                    viewArray[i] = ckb;
                    break;
                case 1:
                    final RadioButton rb = new RadioButton("RB " + i);
                    rb.setToggleGroup(rbGrp);
                    rb.selectedProperty().addListener(observable -> System.out.println("View Update: " + index));
                    rb.selectedProperty().bindBidirectional(modelArray[i]);
                    viewArray[i] = rb;
                    break;
                case 2:
                    final ToggleButton btn = new ToggleButton("BTN " + i);
                    btn.setToggleGroup(btnGrp);
                    btn.selectedProperty().addListener(observable -> System.out.println("View Update: " + index));
                    btn.selectedProperty().bindBidirectional(modelArray[i]);
                    viewArray[i] = btn;
                    break;
            }

            pnl.getChildren().add(viewArray[i]);
        }
    }
}

这是基本窗格:

public class MyTestPane extends BorderPane {

    public MyTestPane() {
        try {
            FXMLLoader loader = new FXMLLoader();
            loader.setLocation(Thread.currentThread().getContextClassLoader().getResource("org/test/MyTestPaneView.fxml"));
            loader.setControllerFactory(cl -> new MyTestPaneView());
            final Pane pane = loader.load();

            setCenter(pane);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

要运行的代码:

public class Runner extends Application {
  public static void main(String[] args) {
    launch(args);
  }
  public void start(Stage stage) {
    stage.setScene(new Scene(new MyTestPane(), 1280, 1024));
    stage.show();
  }
}

预期的行为是每次点击任何组件都会产生2条输出线:一条用于UI更改,另一条用于背景模型更改:

View Update: 127
Model Update: 127
View Update: 405
Model Update: 405

在某些情况下,它会显示:

View Update: 5
Model Update: 5
View Update: 233
View Update: 509
View Update: 12
Model Update: 12

如果将其复制到项目中并运行它,您会注意到按钮和复选框的第一部分正常工作而最后部分没有。有多少节点无法正常工作因执行而异。

此代码的核心是无法正常工作的双向绑定。

这是JVM中的错误吗?我使用Java 1.8 U60 / U66(x64)和Windows 7 x64运行它。

1 个答案:

答案 0 :(得分:0)

这基本上是JavaFX Beans Binding suddenly stops working的副本。您的绑定正在收集垃圾,因为您没有保留对使用它们的任何内容的引用。

在大多数情况下,在“真实”代码(即非示例代码)中,您保留对模型的引用,因为它将在UI中的不同组件之间共享,因此这不是问题。

您可以通过强制垃圾收集来重现问题:

import java.io.IOException;

import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;

public class MyTestPane extends BorderPane {

    public MyTestPane() {
        try {
            FXMLLoader loader = new FXMLLoader();
//            loader.setLocation(Thread.currentThread().getContextClassLoader().getResource("MyTestPaneView.fxml"));
            loader.setLocation(getClass().getResource("MyTestPaneView.fxml"));
            loader.setControllerFactory(cl -> new MyTestPaneView());
            final Pane pane = loader.load();

            setCenter(pane);

            Button gc = new Button("GC");
            gc.setOnAction(e -> System.gc());
            setBottom(gc);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

在此版本中,按“GC”按钮后,所有型号更新都将停止。

您可以通过在自定义控件类中保留对控制器的引用来解决此问题:

import java.io.IOException;

import javafx.fxml.FXMLLoader;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;

public class MyTestPane extends BorderPane {

    private MyTestPaneView controller ;

    public MyTestPane() {
        try {
            FXMLLoader loader = new FXMLLoader();
//            loader.setLocation(Thread.currentThread().getContextClassLoader().getResource("MyTestPaneView.fxml"));
            loader.setLocation(getClass().getResource("MyTestPaneView.fxml"));
            loader.setControllerFactory(cl -> new MyTestPaneView());
            final Pane pane = loader.load();

            controller = loader.getController();

            setCenter(pane);

            Button gc = new Button("GC");
            gc.setOnAction(e -> System.gc());
            setBottom(gc);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

显然,您强制保留这些引用的具体细节可能会因实际代码而异,但这应足以解决问题。

您可能也对http://tomasmikula.github.io/blog/2015/02/10/the-trouble-with-weak-listeners.html

感兴趣