Java 绑定:BindBidirectional 无效

Java Bindings: BindBidirectional not work

已更新

我的问题是双向绑定 (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>

这是控制器class:

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.

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

在大多数情况下,在 "real" 代码(即不是示例代码)中,您会保留对模型的引用,因为它将在 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"按钮,所有模型更新将停止。

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

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

感兴趣