JavaFX中创建DoubleBinding后能否修改其依赖关系?
问题背景
在JavaFX实现图可视化时,存在Vertex和Edge两类:每条Edge连接两个(可相同的)Vertex。带自环(起止顶点相同的Edge)的Vertex会存储一个DoubleProperty,用于表示自环的偏好角度——该角度由当前Vertex及其所有邻居的位置计算得出。
但图是动态构建的,Vertex的邻居会变化,导致计算角度的依赖列表动态改变,因此需要修改绑定该角度的DoubleBinding的依赖。
现有限制
通过Bindings.createDoubleBinding创建的DoubleBinding的getDependencies方法仅返回不可变副本:
@Override public ObservableList<?> getDependencies() { return ((dependencies == null) || (dependencies.length == 0))? FXCollections.emptyObservableList() : (dependencies.length == 1)? FXCollections.singletonObservableList(dependencies[0]) : new ImmutableObservableList<Observable>(dependencies); }
尽管DoubleBinding类有一个bind方法看似能满足需求,但它是protected的:
protected final void bind(Observable... dependencies) { if ((dependencies != null) && (dependencies.length > 0)) { if (observer == null) { observer = new BindingHelperObserver(this); } for (final Observable dep : dependencies) { dep.addListener(observer); } } }
核心问题
是否有无需自定义DoubleBinding即可随时修改依赖的方法,或者无需修改依赖就能解决该问题的方案?
方案一:用ObservableList包装动态依赖
无需自定义DoubleBinding,将所有动态变化的依赖(比如邻居的位置属性)放入可观察列表,让DoubleBinding同时依赖列表本身和列表内的元素。
实现步骤
- 给
Vertex添加ObservableList<DoubleProperty>类型的属性(如neighborPositions),用来存储所有邻居的位置属性(若位置是Point2D,可拆分为x、y两个DoubleProperty)。 - 创建
DoubleBinding时,将当前Vertex的位置属性和neighborPositions列为依赖,计算逻辑中遍历列表内的所有位置属性。 - 邻居变化时,直接修改
neighborPositions列表(添加/移除对应位置属性),DoubleBinding会自动感知列表变化并重新计算。
示例代码
// Vertex类内部定义 private final ObservableList<DoubleProperty> neighborPositions = FXCollections.observableArrayList(); private final DoubleProperty selfLoopAngle = new SimpleDoubleProperty(); public Vertex() { selfLoopAngle.bind(Bindings.createDoubleBinding(() -> { // 角度计算逻辑:结合当前顶点位置与所有邻居位置 double currentX = this.xProperty().get(); double currentY = this.yProperty().get(); // 遍历邻居位置完成计算(此处替换为实际角度算法) double calculatedAngle = 0.0; for (int i = 0; i < neighborPositions.size(); i += 2) { double neighborX = neighborPositions.get(i).get(); double neighborY = neighborPositions.get(i+1).get(); // ... 计算角度 } return calculatedAngle; }, this.xProperty(), this.yProperty(), neighborPositions)); } // 新增邻居时更新依赖列表 public void addNeighbor(Vertex neighbor) { neighborPositions.add(neighbor.xProperty()); neighborPositions.add(neighbor.yProperty()); } // 删除邻居时更新依赖列表 public void removeNeighbor(Vertex neighbor) { neighborPositions.remove(neighbor.xProperty()); neighborPositions.remove(neighbor.yProperty()); }
原理
Bindings.createDoubleBinding会监听neighborPositions的列表变更(添加/移除元素),同时监听列表内每个属性的变化,无论邻居增减还是位置变动,都会触发重新计算。
方案二:手动维护监听器触发更新
若不想用ObservableList包装,可手动管理依赖的监听关系,触发绑定失效。
实现步骤
- 初始创建
DoubleBinding时,仅依赖当前Vertex的位置属性。 - 给每个邻居的位置属性添加
InvalidationListener,触发时调用selfLoopAngle.invalidate()强制重新计算。 - 邻居变化时,移除旧邻居的监听器,给新邻居添加监听器,再手动触发绑定失效。
示例代码
private final DoubleProperty selfLoopAngle = new SimpleDoubleProperty(); private final List<InvalidationListener> neighborListeners = new ArrayList<>(); private final List<Vertex> neighbors = new ArrayList<>(); public Vertex() { DoubleBinding angleBinding = Bindings.createDoubleBinding(() -> { // 角度计算逻辑:结合当前顶点位置与neighbors列表内的邻居位置 double currentX = this.xProperty().get(); double currentY = this.yProperty().get(); double calculatedAngle = 0.0; for (Vertex neighbor : neighbors) { double neighborX = neighbor.xProperty().get(); double neighborY = neighbor.yProperty().get(); // ... 计算角度 } return calculatedAngle; }, this.xProperty(), this.yProperty()); selfLoopAngle.bind(angleBinding); } public void addNeighbor(Vertex neighbor) { neighbors.add(neighbor); InvalidationListener listener = obs -> selfLoopAngle.invalidate(); neighbor.xProperty().addListener(listener); neighbor.yProperty().addListener(listener); neighborListeners.add(listener); } public void removeNeighbor(Vertex neighbor) { int index = neighbors.indexOf(neighbor); if (index != -1) { neighbors.remove(index); InvalidationListener listener = neighborListeners.remove(index); neighbor.xProperty().removeListener(listener); neighbor.yProperty().removeListener(listener); selfLoopAngle.invalidate(); } }
原理
通过手动维护监听器集合,在邻居变化时更新监听关系,再强制绑定失效,触发角度重新计算。
方案三:自定义DynamicDoubleBinding(可选)
若上述方案不满足需求,可自定义DoubleBinding子类,暴露公共方法管理依赖:
自定义绑定类
public class DynamicDoubleBinding extends DoubleBinding { private final ObservableList<Observable> dependencies = FXCollections.observableArrayList(); private final Callable<Double> computeFunction; public DynamicDoubleBinding(Callable<Double> computeFunction) { this.computeFunction = computeFunction; // 监听依赖列表变化,自动维护监听器 dependencies.addListener((ListChangeListener.Change<? extends Observable> c) -> { while (c.next()) { if (c.wasAdded()) { c.getAddedSubList().forEach(dep -> dep.addListener(observer)); } if (c.wasRemoved()) { c.getRemoved().forEach(dep -> dep.removeListener(observer)); } } }); } @Override protected double computeValue() { try { return computeFunction.call(); } catch (Exception e) { e.printStackTrace(); return 0.0; } } @Override public ObservableList<?> getDependencies() { return FXCollections.unmodifiableObservableList(dependencies); } // 公共方法:添加依赖 public void addDependency(Observable dep) { dependencies.add(dep); } // 公共方法:移除依赖 public void removeDependency(Observable dep) { dependencies.remove(dep); } }
使用示例
DynamicDoubleBinding angleBinding = new DynamicDoubleBinding(() -> { // 角度计算逻辑 return calculatedAngle; }); // 添加初始依赖 angleBinding.addDependency(this.xProperty()); angleBinding.addDependency(this.yProperty()); selfLoopAngle.bind(angleBinding); // 邻居变化时更新依赖 angleBinding.addDependency(neighbor.xProperty()); angleBinding.addDependency(neighbor.yProperty());
内容的提问来源于stack exchange,提问作者CirF

