JavaFX中ObjectProperty为null时绑定依赖的解决方案咨询
I have an
ObjectProperty<VBox>namednode, and I want to create anodeSizeproperty that depends on it. Here's my current implementation:public final class PropertyProblem { public PropertyProblem() { nodeSize.bind(initNodeSizeBinding(node)); } private ObjectBinding<Dimension2D> initNodeSizeBinding(ObjectProperty<VBox> nodeProperty) { return Bindings.createObjectBinding(() -> { if (nodeProperty.get() == null) { return null; } VBox node = nodeProperty.get(); ReadOnlyDoubleProperty widthProperty = node.widthProperty(), heightProperty = node.heightProperty(); return new Dimension2D(widthProperty.get(), heightProperty.get()); }, nodeProperty); } // node property private final ObjectProperty<VBox> node = new SimpleObjectProperty<>(this, "node", null); public ObjectProperty<VBox> nodeProperty() { return node; } public void setNode(VBox val) { node.set(val); } public VBox getNode() { return node.get(); } // nodeSize property private final ReadOnlyObjectWrapper<Dimension2D> nodeSize = new ReadOnlyObjectWrapper<> (this, "nodeSize"); public ReadOnlyObjectProperty<Dimension2D> nodeSizeProperty() { return nodeSize.getReadOnlyProperty(); } public Dimension2D getNodeSize() { return nodeSize.get(); } }The issue with the
initNodeSizeBinding()method is that whennodeis null, I can't include the node's width and height as dependencies. Right now, the binding only updates whennodechanges—not when the node's size changes. Is there a way to avoid recreating the binding every time thenodeproperty updates?
Great question! The core problem here is that your original binding only listens to changes in the nodeProperty itself, not the nested width and height properties of the VBox once it's assigned. You don't need to recreate the binding every time the node changes—here are two clean solutions:
Solution 1: Use Bindings.selectDouble for automatic nested property tracking
This is the simplest approach, leveraging JavaFX's built-in binding utilities to dynamically track nested properties even when the parent node is null or changes.
private ObjectBinding<Dimension2D> initNodeSizeBinding(ObjectProperty<VBox> nodeProperty) { // Track width and height properties of the node (handles null automatically) DoubleBinding widthBinding = Bindings.selectDouble(nodeProperty, "width"); DoubleBinding heightBinding = Bindings.selectDouble(nodeProperty, "height"); return Bindings.createObjectBinding(() -> { // Return null if node is null or size values are unavailable (NaN) if (nodeProperty.get() == null || Double.isNaN(widthBinding.get()) || Double.isNaN(heightBinding.get())) { return null; } return new Dimension2D(widthBinding.get(), heightBinding.get()); }, widthBinding, heightBinding); }
How this works:
Bindings.selectDoublecreates a binding that first observes thenodeProperty. When a non-nullVBoxis set, it automatically starts observing thatVBox'swidthPropertyandheightProperty.- If the
nodeis set to null,widthBindingandheightBindingreturnNaN, which we check for to return null in thenodeSizebinding. - This binding only needs to be created once—it will update whenever the
nodechanges OR the node's size changes.
Solution 2: Custom Binding with Manual Listener Management
For more control (e.g., handling additional properties or custom logic when nodes are swapped), you can create a custom binding that manually manages listeners for the node's size properties.
private ObjectBinding<Dimension2D> initNodeSizeBinding(ObjectProperty<VBox> nodeProperty) { return new ObjectBinding<>() { private VBox currentNode; // Listeners to trigger recalculation when size changes private final InvalidationListener widthListener = obs -> invalidate(); private final InvalidationListener heightListener = obs -> invalidate(); { // Listen for changes to the node property nodeProperty.addListener((obs, oldNode, newNode) -> { // Clean up listeners from the old node if (oldNode != null) { oldNode.widthProperty().removeListener(widthListener); oldNode.heightProperty().removeListener(heightListener); } // Attach listeners to the new node if (newNode != null) { newNode.widthProperty().addListener(widthListener); newNode.heightProperty().addListener(heightListener); currentNode = newNode; } else { currentNode = null; } // Force a recalculation of the binding value invalidate(); }); } @Override protected Dimension2D computeValue() { if (currentNode == null) { return null; } return new Dimension2D(currentNode.getWidth(), currentNode.getHeight()); } }; }
How this works:
- The custom binding observes the
nodePropertydirectly. When the node changes, it removes listeners from the old node (if any) and adds listeners to the new node's size properties. - Whenever the node's size changes, the listeners trigger
invalidate(), which forces the binding to recalculate its value. - Like the first solution, this binding only needs to be created once and handles all subsequent node changes and size updates.
Which approach to choose?
- Use Solution 1 for simple cases where you just need to track nested properties—it's concise and leverages JavaFX's built-in utilities.
- Use Solution 2 if you need more control over listener management, additional logic when nodes are swapped, or if you're tracking multiple properties beyond just width and height.
内容的提问来源于stack exchange,提问作者StackOverflow

