如何实现多个属性的绑定以相互监听?
How to implement bindings of multiple properties to all listen to each other?
我有两个可拖动的 Circle
节点,我想用 Line
连接它们(不是从它们的中心,而是从它们的周边)。但是当一个 Circle
在被拖动时改变了位置,Line
的 startX
和 startY
的值改变了所以 Line
成为了两个 Circle
,与连接它们半径的线共线。
我的问题是 Line
's startX
, startY
, endX
, and endY
each individually listen to or bind to both Circle
s' centerXProperty
和 centerYProperty
似乎过于冗长(或者更确切地说,绑定到具有这些属性值的计算), 因为这将导致总共 16 bindings/listeners.
我想知道是否有更简单或更方便的方法来完成此操作。我正在考虑创建一个 SimpleDoubleProperty
,它将是 Line
对象 (y2 - y1)/(x2 - x1) 的斜率,绑定到两个 centerXProperty
和 centerYProperty
s,并让 startX
、startY
、endX
和 endY
各自收听 属性,但我也不确定如何拥有单个 属性 绑定到这四个属性的结果计算。
这是我目前构建 Line
的方式。我正在试验 startXProperty
和 startYProperty
绑定并意识到它正确地更新了 Line
但只有当源 Circle source
被移动时,这促使我问这个问题。 endXProperty
和 endYProperty
仍然将 Line
锚定在目标圆的中心。如果需要,我可以提供我的完整代码,尽管我认为这足以满足我要完成的任务。
public GraphEdge(GraphNode source, GraphNode target) {
this.source = source;
this.target = target;
this.setFill(Color.BLACK);
this.startXProperty().bind(Bindings.createDoubleBinding(() -> {
slope = (target.getCenterY() - source.getCenterY())/(target.getCenterX() - source.getCenterX());
return source.getCenterX() + Math.cos(Math.atan(slope)) * source.getRadius();
}, source.boundsInParentProperty()));
this.startYProperty().bind(Bindings.createDoubleBinding(() -> {
slope = (target.getCenterY() - source.getCenterY())/(target.getCenterX() - source.getCenterX());
return source.getCenterY() + Math.sin(Math.atan(slope)) * source.getRadius();
}, source.boundsInParentProperty()));
this.endXProperty().bind(Bindings.createDoubleBinding(() -> {
Bounds b = target.getBoundsInParent();
return b.getMinX() + b.getWidth() / 2;
}, target.boundsInParentProperty()));
this.endYProperty().bind(Bindings.createDoubleBinding(() -> {
Bounds b = target.getBoundsInParent();
return b.getMinY() + b.getHeight() / 2;
}, target.boundsInParentProperty()));
}
createDoubleBinding
接受依赖项列表。您应该列出每行 属性 所依赖的所有属性。
this.startXProperty().bind(Bindings.createDoubleBinding(
() -> {
double slope = (target.getCenterY() - source.getCenterY())/(target.getCenterX() - source.getCenterX());
return source.getCenterX() + Math.cos(Math.atan(slope)) * source.getRadius();
},
source.centerXProperty(),
source.centerYProperty(),
target.centerXProperty(),
target.centerYProperty(),
source.radiusProperty(),
));
对其他三个线属性重复上述操作。
我同意@John Kugelman 的回答。
我快速尝试了一下,看起来您的实际公式(使用 sin/tan)对我来说并不像预期的那样有效。
所以我重新考虑了逻辑并提出了以下解决方案。该解决方案基于“给定直线 AB,在距离 d 处找到直线上的点 C”的概念。这里A和B是圆心。
所以想法是:
- 我们构建一个 DoubleBinding 来获取圆心之间的线的长度 (l)。
- 然后我们计算半径为'r'和'l-r'的点。这些点位于两个圆的边缘。
- 最后,我们通过绑定新的点来构建一条线。
请找到以下代码:
class GraphEdge extends Line {
public GraphEdge(GraphNode source, GraphNode target) {
DoubleBinding lineLength = Bindings.createDoubleBinding(() -> {
double xDiffSqu = (target.getCenterX() - source.getCenterX()) * (target.getCenterX() - source.getCenterX());
double yDiffSqu = (target.getCenterY() - source.getCenterY()) * (target.getCenterY() - source.getCenterY());
return Math.sqrt(xDiffSqu + yDiffSqu);
}, source.centerXProperty(), source.centerYProperty(), target.centerXProperty(), target.centerYProperty());
DoubleBinding sTx = pointBinding(source, target, lineLength, false, Circle::getCenterX);
DoubleBinding sTy = pointBinding(source, target, lineLength, false, Circle::getCenterY);
DoubleBinding eTx = pointBinding(source, target, lineLength, true, Circle::getCenterX);
DoubleBinding eTy = pointBinding(source, target, lineLength, true, Circle::getCenterY);
setStroke(Color.BLUE);
setStrokeWidth(2);
startXProperty().bind(sTx);
startYProperty().bind(sTy);
endXProperty().bind(eTx);
endYProperty().bind(eTy);
}
private DoubleBinding pointBinding(Circle startDot, Circle endDot, DoubleBinding lineLength, boolean isFarEnd, Function<Circle, Double> refPoint) {
return Bindings.createDoubleBinding(() -> {
double dt = isFarEnd ? lineLength.get() - endDot.getRadius() : startDot.getRadius();
double t = dt / lineLength.get();
double startPoint = refPoint.apply(startDot);
double endPoint = refPoint.apply(endDot);
double dy = ((1 - t) * startPoint) + (t * endPoint);
return dy;
}, lineLength);
}
}
下面是完整的工作演示:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import java.util.function.Function;
public class DoubleBindingsDemo extends Application {
@Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
root.setPadding(new Insets(20));
Pane pane = new Pane();
pane.setStyle("-fx-border-width:1px;-fx-border-color:black;");
root.getChildren().add(pane);
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
GraphNode greenNode = new GraphNode("green");
GraphNode redNode = new GraphNode("red");
GraphEdge edge = new GraphEdge(greenNode, redNode);
pane.getChildren().addAll(greenNode, redNode, edge);
}
class GraphNode extends Circle {
double sceneX, sceneY, centerX, centerY;
public GraphNode(String color) {
double radius = 30;
setRadius(radius);
setStyle("-fx-fill:" + color + ";-fx-stroke-width:2px;-fx-stroke:black;-fx-opacity:.5");
setCenterX(radius);
setCenterY(radius);
setOnMousePressed(e -> {
sceneX = e.getSceneX();
sceneY = e.getSceneY();
centerX = getCenterX();
centerY = getCenterY();
});
EventHandler<MouseEvent> dotOnMouseDraggedEventHandler = e -> {
// Offset of drag
double offsetX = e.getSceneX() - sceneX;
double offsetY = e.getSceneY() - sceneY;
// Taking parent bounds
Bounds parentBounds = getParent().getLayoutBounds();
double dotRadius = getRadius();
double maxCx = parentBounds.getWidth() - dotRadius;
double maxCy = parentBounds.getHeight() - dotRadius;
double cxOffset = centerX + offsetX;
double cyOffset = centerY + offsetY;
if (cxOffset < dotRadius) {
setCenterX(dotRadius);
} else if (cxOffset < maxCx) {
setCenterX(cxOffset);
} else {
setCenterX(maxCx);
}
if (cyOffset < dotRadius) {
setCenterY(dotRadius);
} else if (cyOffset < maxCy) {
setCenterY(cyOffset);
} else {
setCenterY(maxCy);
}
};
setOnMouseDragged(dotOnMouseDraggedEventHandler);
}
}
class GraphEdge extends Line {
public GraphEdge(GraphNode source, GraphNode target) {
DoubleBinding lineLength = Bindings.createDoubleBinding(() -> {
double xDiffSqu = (target.getCenterX() - source.getCenterX()) * (target.getCenterX() - source.getCenterX());
double yDiffSqu = (target.getCenterY() - source.getCenterY()) * (target.getCenterY() - source.getCenterY());
return Math.sqrt(xDiffSqu + yDiffSqu);
}, source.centerXProperty(), source.centerYProperty(), target.centerXProperty(), target.centerYProperty());
DoubleBinding sTx = pointBinding(source, target, lineLength, false, Circle::getCenterX);
DoubleBinding sTy = pointBinding(source, target, lineLength, false, Circle::getCenterY);
DoubleBinding eTx = pointBinding(source, target, lineLength, true, Circle::getCenterX);
DoubleBinding eTy = pointBinding(source, target, lineLength, true, Circle::getCenterY);
setStroke(Color.BLUE);
setStrokeWidth(2);
startXProperty().bind(sTx);
startYProperty().bind(sTy);
endXProperty().bind(eTx);
endYProperty().bind(eTy);
}
private DoubleBinding pointBinding(Circle startDot, Circle endDot, DoubleBinding lineLength, boolean isFarEnd, Function<Circle, Double> refPoint) {
return Bindings.createDoubleBinding(() -> {
double dt = isFarEnd ? lineLength.get() - endDot.getRadius() : startDot.getRadius();
double t = dt / lineLength.get();
double startPoint = refPoint.apply(startDot);
double endPoint = refPoint.apply(endDot);
double dy = ((1 - t) * startPoint) + (t * endPoint);
return dy;
}, lineLength);
}
}
}
我有两个可拖动的 Circle
节点,我想用 Line
连接它们(不是从它们的中心,而是从它们的周边)。但是当一个 Circle
在被拖动时改变了位置,Line
的 startX
和 startY
的值改变了所以 Line
成为了两个 Circle
,与连接它们半径的线共线。
我的问题是 Line
's startX
, startY
, endX
, and endY
each individually listen to or bind to both Circle
s' centerXProperty
和 centerYProperty
似乎过于冗长(或者更确切地说,绑定到具有这些属性值的计算), 因为这将导致总共 16 bindings/listeners.
我想知道是否有更简单或更方便的方法来完成此操作。我正在考虑创建一个 SimpleDoubleProperty
,它将是 Line
对象 (y2 - y1)/(x2 - x1) 的斜率,绑定到两个 centerXProperty
和 centerYProperty
s,并让 startX
、startY
、endX
和 endY
各自收听 属性,但我也不确定如何拥有单个 属性 绑定到这四个属性的结果计算。
这是我目前构建 Line
的方式。我正在试验 startXProperty
和 startYProperty
绑定并意识到它正确地更新了 Line
但只有当源 Circle source
被移动时,这促使我问这个问题。 endXProperty
和 endYProperty
仍然将 Line
锚定在目标圆的中心。如果需要,我可以提供我的完整代码,尽管我认为这足以满足我要完成的任务。
public GraphEdge(GraphNode source, GraphNode target) {
this.source = source;
this.target = target;
this.setFill(Color.BLACK);
this.startXProperty().bind(Bindings.createDoubleBinding(() -> {
slope = (target.getCenterY() - source.getCenterY())/(target.getCenterX() - source.getCenterX());
return source.getCenterX() + Math.cos(Math.atan(slope)) * source.getRadius();
}, source.boundsInParentProperty()));
this.startYProperty().bind(Bindings.createDoubleBinding(() -> {
slope = (target.getCenterY() - source.getCenterY())/(target.getCenterX() - source.getCenterX());
return source.getCenterY() + Math.sin(Math.atan(slope)) * source.getRadius();
}, source.boundsInParentProperty()));
this.endXProperty().bind(Bindings.createDoubleBinding(() -> {
Bounds b = target.getBoundsInParent();
return b.getMinX() + b.getWidth() / 2;
}, target.boundsInParentProperty()));
this.endYProperty().bind(Bindings.createDoubleBinding(() -> {
Bounds b = target.getBoundsInParent();
return b.getMinY() + b.getHeight() / 2;
}, target.boundsInParentProperty()));
}
createDoubleBinding
接受依赖项列表。您应该列出每行 属性 所依赖的所有属性。
this.startXProperty().bind(Bindings.createDoubleBinding(
() -> {
double slope = (target.getCenterY() - source.getCenterY())/(target.getCenterX() - source.getCenterX());
return source.getCenterX() + Math.cos(Math.atan(slope)) * source.getRadius();
},
source.centerXProperty(),
source.centerYProperty(),
target.centerXProperty(),
target.centerYProperty(),
source.radiusProperty(),
));
对其他三个线属性重复上述操作。
我同意@John Kugelman 的回答。
我快速尝试了一下,看起来您的实际公式(使用 sin/tan)对我来说并不像预期的那样有效。 所以我重新考虑了逻辑并提出了以下解决方案。该解决方案基于“给定直线 AB,在距离 d 处找到直线上的点 C”的概念。这里A和B是圆心。
所以想法是:
- 我们构建一个 DoubleBinding 来获取圆心之间的线的长度 (l)。
- 然后我们计算半径为'r'和'l-r'的点。这些点位于两个圆的边缘。
- 最后,我们通过绑定新的点来构建一条线。
请找到以下代码:
class GraphEdge extends Line {
public GraphEdge(GraphNode source, GraphNode target) {
DoubleBinding lineLength = Bindings.createDoubleBinding(() -> {
double xDiffSqu = (target.getCenterX() - source.getCenterX()) * (target.getCenterX() - source.getCenterX());
double yDiffSqu = (target.getCenterY() - source.getCenterY()) * (target.getCenterY() - source.getCenterY());
return Math.sqrt(xDiffSqu + yDiffSqu);
}, source.centerXProperty(), source.centerYProperty(), target.centerXProperty(), target.centerYProperty());
DoubleBinding sTx = pointBinding(source, target, lineLength, false, Circle::getCenterX);
DoubleBinding sTy = pointBinding(source, target, lineLength, false, Circle::getCenterY);
DoubleBinding eTx = pointBinding(source, target, lineLength, true, Circle::getCenterX);
DoubleBinding eTy = pointBinding(source, target, lineLength, true, Circle::getCenterY);
setStroke(Color.BLUE);
setStrokeWidth(2);
startXProperty().bind(sTx);
startYProperty().bind(sTy);
endXProperty().bind(eTx);
endYProperty().bind(eTy);
}
private DoubleBinding pointBinding(Circle startDot, Circle endDot, DoubleBinding lineLength, boolean isFarEnd, Function<Circle, Double> refPoint) {
return Bindings.createDoubleBinding(() -> {
double dt = isFarEnd ? lineLength.get() - endDot.getRadius() : startDot.getRadius();
double t = dt / lineLength.get();
double startPoint = refPoint.apply(startDot);
double endPoint = refPoint.apply(endDot);
double dy = ((1 - t) * startPoint) + (t * endPoint);
return dy;
}, lineLength);
}
}
下面是完整的工作演示:
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.beans.binding.DoubleBinding;
import javafx.event.EventHandler;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.stage.Stage;
import java.util.function.Function;
public class DoubleBindingsDemo extends Application {
@Override
public void start(Stage stage) throws Exception {
StackPane root = new StackPane();
root.setPadding(new Insets(20));
Pane pane = new Pane();
pane.setStyle("-fx-border-width:1px;-fx-border-color:black;");
root.getChildren().add(pane);
Scene sc = new Scene(root, 600, 600);
stage.setScene(sc);
stage.show();
GraphNode greenNode = new GraphNode("green");
GraphNode redNode = new GraphNode("red");
GraphEdge edge = new GraphEdge(greenNode, redNode);
pane.getChildren().addAll(greenNode, redNode, edge);
}
class GraphNode extends Circle {
double sceneX, sceneY, centerX, centerY;
public GraphNode(String color) {
double radius = 30;
setRadius(radius);
setStyle("-fx-fill:" + color + ";-fx-stroke-width:2px;-fx-stroke:black;-fx-opacity:.5");
setCenterX(radius);
setCenterY(radius);
setOnMousePressed(e -> {
sceneX = e.getSceneX();
sceneY = e.getSceneY();
centerX = getCenterX();
centerY = getCenterY();
});
EventHandler<MouseEvent> dotOnMouseDraggedEventHandler = e -> {
// Offset of drag
double offsetX = e.getSceneX() - sceneX;
double offsetY = e.getSceneY() - sceneY;
// Taking parent bounds
Bounds parentBounds = getParent().getLayoutBounds();
double dotRadius = getRadius();
double maxCx = parentBounds.getWidth() - dotRadius;
double maxCy = parentBounds.getHeight() - dotRadius;
double cxOffset = centerX + offsetX;
double cyOffset = centerY + offsetY;
if (cxOffset < dotRadius) {
setCenterX(dotRadius);
} else if (cxOffset < maxCx) {
setCenterX(cxOffset);
} else {
setCenterX(maxCx);
}
if (cyOffset < dotRadius) {
setCenterY(dotRadius);
} else if (cyOffset < maxCy) {
setCenterY(cyOffset);
} else {
setCenterY(maxCy);
}
};
setOnMouseDragged(dotOnMouseDraggedEventHandler);
}
}
class GraphEdge extends Line {
public GraphEdge(GraphNode source, GraphNode target) {
DoubleBinding lineLength = Bindings.createDoubleBinding(() -> {
double xDiffSqu = (target.getCenterX() - source.getCenterX()) * (target.getCenterX() - source.getCenterX());
double yDiffSqu = (target.getCenterY() - source.getCenterY()) * (target.getCenterY() - source.getCenterY());
return Math.sqrt(xDiffSqu + yDiffSqu);
}, source.centerXProperty(), source.centerYProperty(), target.centerXProperty(), target.centerYProperty());
DoubleBinding sTx = pointBinding(source, target, lineLength, false, Circle::getCenterX);
DoubleBinding sTy = pointBinding(source, target, lineLength, false, Circle::getCenterY);
DoubleBinding eTx = pointBinding(source, target, lineLength, true, Circle::getCenterX);
DoubleBinding eTy = pointBinding(source, target, lineLength, true, Circle::getCenterY);
setStroke(Color.BLUE);
setStrokeWidth(2);
startXProperty().bind(sTx);
startYProperty().bind(sTy);
endXProperty().bind(eTx);
endYProperty().bind(eTy);
}
private DoubleBinding pointBinding(Circle startDot, Circle endDot, DoubleBinding lineLength, boolean isFarEnd, Function<Circle, Double> refPoint) {
return Bindings.createDoubleBinding(() -> {
double dt = isFarEnd ? lineLength.get() - endDot.getRadius() : startDot.getRadius();
double t = dt / lineLength.get();
double startPoint = refPoint.apply(startDot);
double endPoint = refPoint.apply(endDot);
double dy = ((1 - t) * startPoint) + (t * endPoint);
return dy;
}, lineLength);
}
}
}