JavaFX自定义DoubleBinding异常行为排查求助
问题描述
以下代码在AnchorPane中绘制一个初始坐标为(50,50)、半径50的Circle,拖动Circle时可使其在面板上移动,同时通过自定义DoubleBinding跟踪Circle的centerX最小值并输出变化日志。但拖动到i约为7000时,控制台输出会停止。
import javafx.application.Application; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.DoubleBinding; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage; public class HelloApplication extends Application { private final AnchorPane mainPane=new AnchorPane(); double x=0,y=0; double tx =0, ty =0; int i=0; // create list with an extractor: ObservableList<Circle> circles =FXCollections.observableArrayList(circle1 -> new Observable[]{circle1.centerXProperty()}); Circle circle = new Circle(50,50,50); @Override public void start(Stage stage) { // Set up stage for show! Scene scene = new Scene(mainPane, 1200, 800); stage.setTitle("Hello!"); stage.setScene(scene); stage.show(); // set fill color. circle.setFill(new Color(1, 0, 0, 0.5)); // set up onMousePressed listener. circle.setOnMousePressed(mouseEvent -> { x=mouseEvent.getX(); y=mouseEvent.getY(); }); // set up onMouseDragged listener. circle.setOnMouseDragged(mouseEvent -> { tx=x; ty=y; x=mouseEvent.getX(); y=mouseEvent.getY(); circle.setCenterX(circle.getCenterX()+x-tx); circle.setCenterY(circle.getCenterY()+y-ty); i++; }); // add rectangle pane children. mainPane.getChildren().addAll(circle); // add rectangle to rectangles. circles.add(circle); DoubleBinding centerX = Bindings.createDoubleBinding( () -> circles .stream() .mapToDouble(Circle::getCenterX) .min() .orElse(0), circles ); centerX.addListener((obs, oldMin, newMin) -> System.out.println("center X changed from " + oldMin + " to " + newMin+" , i = "+i)); } public static void main(String[] args) { launch(); } }
补充说明:后续需支持多个Circle,因此需要跟踪所有Circle的centerX最小值,示例中仅保留单个Circle简化场景。
问题原因
- 原代码的
DoubleBinding仅将ObservableListcircles作为依赖,虽然列表通过提取器能感知Circle的centerX变化,但JavaFX的Binding采用懒加载机制:只有调用get()方法时才会重新计算最小值。 - 代码仅给
DoubleBinding添加了监听器,没有任何地方主动触发get(),导致Circle拖动时,Binding仅被标记为无效,不会自动重新计算值,监听器自然收不到变化通知。 - 初期能输出是因为JavaFX内部隐式调用了
get(),但拖动次数增多后这种隐式调用停止,最终输出中断。
修复方案
核心是让Binding的依赖包含每个Circle的centerXProperty,同时通过绑定到DoubleProperty确保Binding会被JavaFX自动求值,具体修改如下:
修改后的完整代码
import javafx.application.Application; import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleDoubleProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.layout.AnchorPane; import javafx.scene.paint.Color; import javafx.scene.shape.Circle; import javafx.stage.Stage; public class HelloApplication extends Application { private final AnchorPane mainPane = new AnchorPane(); private final DoubleProperty minCenterX = new SimpleDoubleProperty(); double x = 0, y = 0; double tx = 0, ty = 0; int i = 0; // 创建带属性提取器的ObservableList,监听元素的centerX变化 ObservableList<Circle> circles = FXCollections.observableArrayList(circle -> new Observable[]{circle.centerXProperty()}); Circle circle = new Circle(50, 50, 50); @Override public void start(Stage stage) { Scene scene = new Scene(mainPane, 1200, 800); stage.setTitle("Hello!"); stage.setScene(scene); stage.show(); circle.setFill(new Color(1, 0, 0, 0.5)); circle.setOnMousePressed(mouseEvent -> { x = mouseEvent.getX(); y = mouseEvent.getY(); }); circle.setOnMouseDragged(mouseEvent -> { tx = x; ty = y; x = mouseEvent.getX(); y = mouseEvent.getY(); circle.setCenterX(circle.getCenterX() + x - tx); circle.setCenterY(circle.getCenterY() + y - ty); i++; }); mainPane.getChildren().add(circle); circles.add(circle); // 监听列表变化,动态更新最小值绑定 circles.addListener((ListChangeListener.Change<? extends Circle> change) -> updateMinCenterXBinding()); // 初始化绑定 updateMinCenterXBinding(); // 监听最小值变化并输出 minCenterX.addListener((obs, oldMin, newMin) -> System.out.println("center X changed from " + oldMin + " to " + newMin + " , i = " + i) ); } // 更新最小值绑定的方法 private void updateMinCenterXBinding() { minCenterX.unbind(); // 先解绑之前的绑定 if (circles.isEmpty()) { minCenterX.set(0); } else { // 将所有Circle的centerXProperty作为绑定依赖 Observable[] dependencies = circles.stream() .map(Circle::centerXProperty) .toArray(Observable[]::new); minCenterX.bind(Bindings.createDoubleBinding( () -> circles.stream() .mapToDouble(Circle::getCenterX) .min() .orElse(0), dependencies )); } } public static void main(String[] args) { launch(); } }
关键修改点
- 新增
DoubleProperty minCenterX,通过绑定它确保Binding会被JavaFX自动求值,避免懒加载导致的不更新问题。 - 添加
updateMinCenterXBinding()方法,动态将所有Circle的centerXProperty作为绑定依赖,确保单个Circle的坐标变化能触发Binding重新计算。 - 监听
circles列表的变化,当列表增删元素时自动更新绑定,适配后续多Circle的场景。
内容的提问来源于stack exchange,提问作者Keen Teen
相关产品推荐
相关产品推荐

