Java绑定:绑定不起作用
已更新 我的问题是双向绑定(Java绑定:绑定不起作用,java,binding,javafx,jvm,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
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 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();
}
}
预期的行为是,每次单击任何组件都会生成两行输出:一行用于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中的一个bug吗?我使用Java 1.8 U60/U66(x64)和Windows 7 x64运行它。这基本上是一个重复,您的绑定正在被垃圾收集,因为您没有保留对任何使用它们的引用 在大多数情况下,在“真实”代码(即非示例代码)中,您保留对模型的引用,因为它将在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);
}
}
}
显然,在实际代码中,强制保留这些引用的具体细节可能有所不同,但这应该足以解决问题
您可能还对感兴趣,请创建一个。从您发布的小片段中,无法判断这是您的代码中的错误还是库代码中的错误。值得一提的是,我创建了一个最小的可编译示例,可以准确地包含您的代码,并且两条消息都显示出来了,因此我强烈怀疑问题出在您的代码(您没有显示)上。这是一个示例。我无法用相同的行为重建代码,因为这种行为不容易复制。除非它复制了你所说的行为,否则它不是真正的“示例”。如果您不能创建代码来复制它,那么任何人都没有问题可以回答。我已经更新了exampleAdded代码,它强制垃圾收集,证明了这确实是一个问题。我认为这是一个非常大的错误。显示和实例化窗格时,控制器必须对GC安全。这就是FXML框架的一个例子。它显示控制器将在控制器工厂调用后使用。好吧,您可以将其作为错误进行归档,但这是故意的,因此可能无法修复。问题是,如果不使用弱侦听器,在许多情况下(特别是使用虚拟化控件,如
ListView
等),绑定将产生内存泄漏,而无需大量非常复杂的代码来释放绑定。查看Tomas Mikula的博客(在更新的答案中链接),了解更多讨论和替代方法。此外,在这里缓存控制器实际上只是缓存模型的一种机制。真正需要保留引用的是BooleanProperty
的数组。在现实生活中,应用程序结构通常将模型实现为某种单例(例如,使用DI框架),然后将对模型的引用传递给需要访问它的每个控制器/演示者。在该应用程序结构中,您自然会保留对绑定的任何对象的强引用。但在这种情况下,我永远无法在JavaFX框架中使用FXMLLoader.load(getClass().getResource(…)
方法,因为我从未获得控制器。
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);
}
}
}
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);
}
}
}