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