JavaFX8:在父级之间移动组件,同时保持原位
我需要在容器之间移动节点(大多数剪裁的图像视图)。在“父级更改”期间,我必须保持组件可视化,因此从用户的角度来看,这种重组应该是透明的 我的场景的模式如下所示: 我必须移动的节点被剪裁、转换并应用于它们的一些效果,最初位于JavaFX8:在父级之间移动组件,同时保持原位,javafx,coordinates,javafx-8,Javafx,Coordinates,Javafx 8,我需要在容器之间移动节点(大多数剪裁的图像视图)。在“父级更改”期间,我必须保持组件可视化,因此从用户的角度来看,这种重组应该是透明的 我的场景的模式如下所示: 我必须移动的节点被剪裁、转换并应用于它们的一些效果,最初位于板窗格下。办公桌的内容组被缩放、剪裁和翻译 我想把它们移到MoverLayer。它工作正常,因为移动层绑定到板: moverLayer.translateXProperty().bind(board.translateXProperty()); moverLa
板窗格下。办公桌
的内容
组被缩放、剪裁和翻译
我想把它们移到MoverLayer。它工作正常,因为移动层
绑定到板
:
moverLayer.translateXProperty().bind(board.translateXProperty());
moverLayer.translateYProperty().bind(board.translateYProperty());
moverLayer.scaleXProperty().bind(board.scaleXProperty());
moverLayer.scaleYProperty().bind(board.scaleYProperty());
moverLayer.layoutXProperty().bind(board.layoutXProperty());
moverLayer.layoutYProperty().bind(board.layoutYProperty());
因此,我可以简单地在它们之间移动节点:
public void start(MouseEvent me) {
board.getContainer().getChildren().remove(node);
desk.getMoverLayer().getChildren().add(node);
}
public void finish(MouseEvent me) {
desk.getMoverLayer().getChildren().remove(node);
board.getContainer().getChildren().add(node);
}
但是,当在托盘
的内容
和移动层
之间移动节点时,它开始变得复杂。我试着使用不同的坐标(本地、家长、场景、屏幕),但不知何故总是放错了位置。似乎,当desk.contents
的比例为1.0时,它可以将translateX
和translateY
的坐标映射到屏幕坐标,切换父坐标,然后将屏幕坐标映射回本地并用作平移。但如果缩放比例不同,坐标也会不同(节点也会移动)。我还尝试递归地将坐标映射到公共父对象(desk
),但两者都不起作用
我的问题是,计算同一点相对于不同父点的坐标的最佳实践是什么
这是MCVE代码。对不起,我无法让它变得更简单
package hu.vissy.puzzlefx.stackoverflow;
import javafx.application.Application;
import javafx.geometry.BoundingBox;
import javafx.geometry.Bounds;
import javafx.geometry.Insets;
import javafx.geometry.Point2D;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Background;
import javafx.scene.layout.BackgroundFill;
import javafx.scene.layout.CornerRadii;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Mcve extends Application {
// The piece to move
private Rectangle piece;
// The components representing the above structure
private Pane board;
private Group moverLayer;
private Pane trayContents;
private Pane desk;
private Group deskContents;
// The zoom scale
private double scale = 1;
// Drag info
private double startDragX;
private double startDragY;
private Point2D dragAnchor;
public Mcve() {
}
public void init(Stage primaryStage) {
// Added only for simulation
Button b = new Button("Zoom");
b.setOnAction((ah) -> setScale(0.8));
desk = new Pane();
desk.setPrefSize(800, 600);
desk.setBackground(new Background(new BackgroundFill(Color.LIGHTGREEN, CornerRadii.EMPTY, Insets.EMPTY)));
deskContents = new Group();
desk.getChildren().add(deskContents);
board = new Pane();
board.setPrefSize(700, 600);
board.setBackground(new Background(new BackgroundFill(Color.LIGHTCORAL, CornerRadii.EMPTY, Insets.EMPTY)));
// Symbolize the piece to be dragged
piece = new Rectangle();
piece.setTranslateX(500);
piece.setTranslateY(50);
piece.setWidth(50);
piece.setHeight(50);
piece.setFill(Color.BLACK);
board.getChildren().add(piece);
// Mover layer is always on top and is bound to the board (used to display the
// dragged items above all desk contents during dragging)
moverLayer = new Group();
moverLayer.translateXProperty().bind(board.translateXProperty());
moverLayer.translateYProperty().bind(board.translateYProperty());
moverLayer.scaleXProperty().bind(board.scaleXProperty());
moverLayer.scaleYProperty().bind(board.scaleYProperty());
moverLayer.layoutXProperty().bind(board.layoutXProperty());
moverLayer.layoutYProperty().bind(board.layoutYProperty());
board.setTranslateX(50);
board.setTranslateY(50);
Pane tray = new Pane();
tray.setPrefSize(400, 400);
tray.relocate(80, 80);
Pane header = new Pane();
header.setPrefHeight(30);
header.setBackground(new Background(new BackgroundFill(Color.LIGHTSLATEGRAY, CornerRadii.EMPTY, Insets.EMPTY)));
trayContents = new Pane();
trayContents.setBackground(new Background(new BackgroundFill(Color.BEIGE, CornerRadii.EMPTY, Insets.EMPTY)));
VBox layout = new VBox();
layout.getChildren().addAll(header, trayContents);
VBox.setVgrow(trayContents, Priority.ALWAYS);
layout.setPrefSize(400, 400);
tray.getChildren().add(layout);
deskContents.getChildren().addAll(board, tray, moverLayer, b);
Scene scene = new Scene(desk);
// Piece is draggable
piece.setOnMousePressed((me) -> startDrag(me));
piece.setOnMouseDragged((me) -> doDrag(me));
piece.setOnMouseReleased((me) -> endDrag(me));
primaryStage.setScene(scene);
}
// Changing the scale
private void setScale(double scale) {
this.scale = scale;
// Reseting piece position and parent if needed
if (piece.getParent() != board) {
piece.setTranslateX(500);
piece.setTranslateY(50);
trayContents.getChildren().remove(piece);
board.getChildren().add(piece);
}
deskContents.setScaleX(getScale());
deskContents.setScaleY(getScale());
}
private double getScale() {
return scale;
}
private void startDrag(MouseEvent me) {
// Saving drag options
startDragX = piece.getTranslateX();
startDragY = piece.getTranslateY();
dragAnchor = new Point2D(me.getSceneX(), me.getSceneY());
// Putting the item into the mover layer -- works fine with all zoom scale level
board.getChildren().remove(piece);
moverLayer.getChildren().add(piece);
me.consume();
}
// Doing the drag
private void doDrag(MouseEvent me) {
double newTranslateX = startDragX + (me.getSceneX() - dragAnchor.getX()) / getScale();
double newTranslateY = startDragY + (me.getSceneY() - dragAnchor.getY()) / getScale();
piece.setTranslateX(newTranslateX);
piece.setTranslateY(newTranslateY);
me.consume();
}
private void endDrag(MouseEvent me) {
// For MCVE's sake I take that the drop is over the tray.
Bounds op = piece.localToScreen(piece.getBoundsInLocal());
moverLayer.getChildren().remove(piece);
// One of my several tries: mapping the coordinates till the common parent.
// I also tried to use localtoScreen -> change parent -> screenToLocal
Bounds b = localToParentRecursive(trayContents, desk, trayContents.getBoundsInLocal());
Bounds b2 = localToParentRecursive(board, desk, board.getBoundsInLocal());
trayContents.getChildren().add(piece);
piece.setTranslateX(piece.getTranslateX() + b2.getMinX() - b.getMinX() * getScale());
piece.setTranslateY(piece.getTranslateY() + b2.getMinY() - b.getMinY() * getScale());
me.consume();
}
public static Point2D localToParentRecursive(Node n, Parent parent, double x, double y) {
// For simplicity I suppose that the n node is on the path of the parent
Point2D p = new Point2D(x, y);
Node cn = n;
while (true) {
if (cn == parent) {
break;
}
p = cn.localToParent(p);
cn = cn.getParent();
}
return p;
}
public static Bounds localToParentRecursive(Node n, Parent parent, Bounds bounds) {
Point2D p = localToParentRecursive(n, parent, bounds.getMinX(), bounds.getMinY());
return new BoundingBox(p.getX(), p.getY(), bounds.getWidth(), bounds.getHeight());
}
@Override
public void start(Stage primaryStage) throws Exception {
init(primaryStage);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
嗯。经过大量的调试、计算和尝试,我终于找到了解决方案
下面是我为执行父级交换而编写的实用程序函数:
/**
* Change the parent of a node.
*
* <p>
* The node should have a common ancestor with the new parent.
* </p>
*
* @param item
* The node to move.
* @param newParent
* The new parent.
*/
@SuppressWarnings("unchecked")
public static void changeParent(Node item, Parent newParent) {
try {
// HAve to use reflection, because the getChildren method is protected in common ancestor of all
// parent nodes.
// Checking old parent for public getChildren() method
Parent oldParent = item.getParent();
if ((oldParent.getClass().getMethod("getChildren").getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new IllegalArgumentException("Old parent has no public getChildren method.");
}
// Checking new parent for public getChildren() method
if ((newParent.getClass().getMethod("getChildren").getModifiers() & Modifier.PUBLIC) != Modifier.PUBLIC) {
throw new IllegalArgumentException("New parent has no public getChildren method.");
}
// Finding common ancestor for the two parents
Parent commonAncestor = findCommonAncestor(oldParent, newParent);
if (commonAncestor == null) {
throw new IllegalArgumentException("Item has no common ancestor with the new parent.");
}
// Bounds of the item
Bounds itemBoundsInParent = item.getBoundsInParent();
// Mapping coordinates to common ancestor
Bounds boundsInParentBeforeMove = localToParentRecursive(oldParent, commonAncestor, itemBoundsInParent);
// Swapping parent
((Collection<Node>) oldParent.getClass().getMethod("getChildren").invoke(oldParent)).remove(item);
((Collection<Node>) newParent.getClass().getMethod("getChildren").invoke(newParent)).add(item);
// Mapping coordinates back from common ancestor
Bounds boundsInParentAfterMove = parentToLocalRecursive(newParent, commonAncestor, boundsInParentBeforeMove);
// Setting new translation
item.setTranslateX(
item.getTranslateX() + (boundsInParentAfterMove.getMinX() - itemBoundsInParent.getMinX()));
item.setTranslateY(
item.getTranslateY() + (boundsInParentAfterMove.getMinY() - itemBoundsInParent.getMinY()));
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new IllegalStateException("Error while switching parent.", e);
}
}
/**
* Finds the topmost common ancestor of two nodes.
*
* @param firstNode
* The first node to check.
* @param secondNode
* The second node to check.
* @return The common ancestor or null if the two node is on different
* parental tree.
*/
public static Parent findCommonAncestor(Node firstNode, Node secondNode) {
// Builds up the set of all ancestor of the first node.
Set<Node> parentalChain = new HashSet<>();
Node cn = firstNode;
while (cn != null) {
parentalChain.add(cn);
cn = cn.getParent();
}
// Iterates down through the second ancestor for common node.
cn = secondNode;
while (cn != null) {
if (parentalChain.contains(cn)) {
return (Parent) cn;
}
cn = cn.getParent();
}
return null;
}
/**
* Transitively converts the coordinates from the node to an ancestor's
* coordinate system.
*
* @param node
* The node the starting coordinates are local to.
* @param ancestor
* The ancestor to map the coordinates to.
* @param x
* The X of the point to be converted.
* @param y
* The Y of the point to be converted.
* @return The converted coordinates.
*/
public static Point2D localToParentRecursive(Node node, Parent ancestor, double x, double y) {
Point2D p = new Point2D(x, y);
Node cn = node;
while (cn != null) {
if (cn == ancestor) {
return p;
}
p = cn.localToParent(p);
cn = cn.getParent();
}
throw new IllegalStateException("The node is not a descedent of the parent.");
}
/**
* Transitively converts the coordinates of a bound from the node to an
* ancestor's coordinate system.
*
* @param node
* The node the starting coordinates are local to.
* @param ancestor
* The ancestor to map the coordinates to.
* @param bounds
* The bounds to be converted.
* @return The converted bounds.
*/
public static Bounds localToParentRecursive(Node node, Parent ancestor, Bounds bounds) {
Point2D p = localToParentRecursive(node, ancestor, bounds.getMinX(), bounds.getMinY());
return new BoundingBox(p.getX(), p.getY(), bounds.getWidth(), bounds.getHeight());
}
/**
* Transitively converts the coordinates from an ancestor's coordinate
* system to the nodes local.
*
* @param node
* The node the resulting coordinates should be local to.
* @param ancestor
* The ancestor the starting coordinates are local to.
* @param x
* The X of the point to be converted.
* @param y
* The Y of the point to be converted.
* @return The converted coordinates.
*/
public static Point2D parentToLocalRecursive(Node n, Parent parent, double x, double y) {
List<Node> parentalChain = new ArrayList<>();
Node cn = n;
while (cn != null) {
if (cn == parent) {
break;
}
parentalChain.add(cn);
cn = cn.getParent();
}
if (cn == null) {
throw new IllegalStateException("The node is not a descedent of the parent.");
}
Point2D p = new Point2D(x, y);
for (int i = parentalChain.size() - 1; i >= 0; i--) {
p = parentalChain.get(i).parentToLocal(p);
}
return p;
}
/**
* Transitively converts the coordinates of the bounds from an ancestor's
* coordinate system to the nodes local.
*
* @param node
* The node the resulting coordinates should be local to.
* @param ancestor
* The ancestor the starting coordinates are local to.
* @param bounds
* The bounds to be converted.
* @return The converted coordinates.
*/
public static Bounds parentToLocalRecursive(Node n, Parent parent, Bounds bounds) {
Point2D p = parentToLocalRecursive(n, parent, bounds.getMinX(), bounds.getMinY());
return new BoundingBox(p.getX(), p.getY(), bounds.getWidth(), bounds.getHeight());
}
/**
*更改节点的父节点。
*
*
*节点应该与新父节点有一个共同的祖先。
*
*
*@param项目
*要移动的节点。
*@param newParent
*新的父母。
*/
@抑制警告(“未选中”)
公共静态无效changeParent(节点项,父项newParent){
试一试{
//必须使用反射,因为getChildren方法在所有方法的共同祖先中受到保护
//父节点。
//检查公共getChildren()方法的旧父级
Parent oldParent=item.getParent();
if((oldParent.getClass().getMethod(“getChildren”).getModifiers()&Modifier.PUBLIC)!=Modifier.PUBLIC){
抛出新的IllegalArgumentException(“旧父级没有公共getChildren方法”);
}
//正在检查公共getChildren()方法的新父级
if((newParent.getClass().getMethod(“getChildren”).getModifiers()&Modifier.PUBLIC)!=Modifier.PUBLIC){
抛出新的IllegalArgumentException(“新父对象没有公共getChildren方法”);
}
//为双亲寻找共同的祖先
父级CommonSenator=FindCommonSenator(旧父级、新父级);
if(common祖先==null){
抛出新的IllegalArgumentException(“项与新父项没有共同的祖先”);
}
//项目的边界
Bounds itemBoundsInParent=item.getBoundsInParent();
//将坐标映射到共同祖先
Bounds boundsInParentBeforeMove=localToParentRecursive(oldParent、Common祖先、itemBoundsInParent);
//交换父级
((集合)oldParent.getClass().getMethod(“getChildren”).invoke(oldParent)).remove(项);
((集合)newParent.getClass().getMethod(“getChildren”).invoke(newParent)).add(项);
//从共同祖先映射回坐标
Bounds boundsInParentAfterMove=parentToLocalRecursive(newParent、Common祖先、boundsInParentBeforeMove);
//设置新的翻译
item.setTranslateX(
item.getTranslateX()+(boundsInParentAfterMove.getMinX()-itemBoundsInParent.getMinX());
item.setTranslateY(
item.getTranslateY()+(boundsInParentAfterMove.getMinY()-itemBoundsInParent.getMinY());
}捕获(NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e){
抛出新的IllegalStateException(“切换父级时出错。”,e);
}
}
/**
*查找两个节点的最上面的共同祖先。
*
*@param firstNode
*要检查的第一个节点。
*@param secondNode
*要检查的第二个节点。
*@返回共同祖先,如果两个节点位于不同位置,则返回null
*亲本树。
*/
公共静态父级FindCommon祖先(节点firstNode、节点secondNode){
//建立第一个节点的所有祖先的集合。
Set parentalChain=new HashSet();
节点cn=第一个节点;
while(cn!=null){
parentalChain.add(中国);
cn=cn.getParent();
}
//向下遍历公共节点的第二个祖先。
cn=第二节点;
while(cn!=null){
if(parentalChain.contains(cn)){
返回(母公司)cn;
}
cn=cn.getParent();
}
返回null;
}
/**
*可传递地将坐标从节点转换为祖先的坐标
*坐标系。
*
*@param节点
*起始坐标所在的节点。
*@param祖先
*要将坐标映射到的祖先。
*@param x
*要转换的点的X。
*@param y
*要转换的点的Y轴。
*@返回转换后的坐标。
*/
公共静态点2D LocalTopArentrCursive(节点节点、父祖先、双x、双y){
点2dp=新点2d(x,y);
节点cn=节点;
while(cn!=null){
if(cn==祖先){
返回p;
}
p=cn.localToParent(p);
cn=cn.getParent();
}
抛出新的非法状态异常(“否