已更新
我的问题是双向绑定(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运行它。
答案 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
感兴趣