You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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仅将ObservableList circles作为依赖,虽然列表通过提取器能感知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();
    }
}

关键修改点

  1. 新增DoubleProperty minCenterX,通过绑定它确保Binding会被JavaFX自动求值,避免懒加载导致的不更新问题。
  2. 添加updateMinCenterXBinding()方法,动态将所有Circle的centerXProperty作为绑定依赖,确保单个Circle的坐标变化能触发Binding重新计算。
  3. 监听circles列表的变化,当列表增删元素时自动更新绑定,适配后续多Circle的场景。

内容的提问来源于stack exchange,提问作者Keen Teen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 04:52:01