为什么有些节点具有x和y位置,而另一些却没有 [英] Why does some nodes have a x and y position and others not

查看:74
本文介绍了为什么有些节点具有x和y位置,而另一些却没有的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图了解JavaFX的坐标系.

对于某些节点(形状?),例如LineRectangle,我可以(或应该)在坐标系中指定x和y值.

这到底是什么?这是翻译和拉伸,后来又附加到节点或其他东西了吗?其他节点仅具有setLayoutX()方法,而例如Line同时具有setLayoutX()setStartX().

谢谢!

解决方案

每个Node都有两个不同的bounds属性(忽略 Node#boundsInLocal

Node的矩形边界在节点的未变形局部坐标空间中.对于扩展Shape的节点,局部边界还将包括非零笔划所需的空间,该笔划可能落在由位置和尺寸属性定义的形状几何形状之外.局部范围还将包括使用clip设置的任何裁剪以及通过effect设置的效果.

[...]

  • Node#boundsInParent

    Node的矩形边界,包括其变换. boundsInParent是通过采用局部边界(由boundsInLocal定义)并应用通过设置以下附加变量创建的变换来计算的

    1. transforms ObservableList
    2. scaleXscaleYscaleZ
    3. rotate
    4. layoutXlayoutY
    5. translateXtranslateYtranslateZ


    所得的边界在概念上将位于Node父级的坐标空间中,但是节点不必具有父级即可计算这些边界.

    [...]

  • 这意味着layoutXtranslateX之类的属性仅会影响父级边界.基本上,父级边界就是简单的 bounds-in-local ,并应用了各种转换.但是,当涉及到特殊" Shape属性时,例如Rectanglexy属性,它们直接影响本地绑定.我找不到解释此问题的文档,尽管也许我只是错过了它,或者对于那些知道地"的人来说,这种行为应该是显而易见的.不幸的是,由于我在这方面缺乏基础知识,因此我无法向您解释为什么这些Shape属性直接影响 locals-in-local . >

    也就是说,我可以通过以下示例直观地说明关于 bounds-in-local 的区别:

    App.java

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class App extends Application {
    
        @Override
        public void start(Stage primaryStage) throws IOException {
            var root = FXMLLoader.<Parent>load(getClass().getResource("App.fxml"));
            primaryStage.setScene(new Scene(root));
            primaryStage.show();
        }
    
    }
    

    Controller.java

    import javafx.fxml.FXML;
    import javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.shape.Rectangle;
    
    public class Controller {
    
        @FXML
        private void handleMousePressed(MouseEvent event) {
            event.consume();
    
            var source = (Node) event.getSource();
            source.setUserData(source.localToParent(event.getX(), event.getY()));
        }
    
        @FXML
        private void handleMouseDragged(MouseEvent event) {
            event.consume();
    
            var source = (Node) event.getSource();
            var lastPoint = (Point2D) source.getUserData();
            var nextPoint = source.localToParent(event.getX(), event.getY());
    
            source.setTranslateX(source.getTranslateX() + nextPoint.getX() - lastPoint.getX());
            source.setTranslateY(source.getTranslateY() + nextPoint.getY() - lastPoint.getY());
    
            source.setUserData(nextPoint);
        }
    
        @FXML
        private void handleMouseReleased(MouseEvent event) {
            event.consume();
    
            var source = (Node) event.getSource();
            if (source instanceof Rectangle) {
                var rectangle = (Rectangle) source;
                rectangle.setX(rectangle.getX() + rectangle.getTranslateX());
                rectangle.setTranslateX(0);
                rectangle.setY(rectangle.getY() + rectangle.getTranslateY());
                rectangle.setTranslateY(0);
            }
            source.setUserData(null);
        }
    
    }
    

    App.fxml

     <?import javafx.geometry.Insets?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.Separator?>
    <?import javafx.scene.layout.HBox?>
    <?import javafx.scene.layout.Pane?>
    <?import javafx.scene.layout.Region?>
    <?import javafx.scene.layout.VBox?>
    <?import javafx.scene.paint.Color?>
    <?import javafx.scene.shape.Rectangle?>
    <?import javafx.scene.text.Font?>
    
    <HBox xmlns="http://javafx.com/javafx/12.0.2" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller"
          prefWidth="1000" prefHeight="600" spacing="15">
    
        <fx:define>
            <Font fx:id="titleFont" name="Impact" size="24"/>
            <Color fx:id="inLocalColor" fx:value="LIME"/>
            <Color fx:id="inParentColor" fx:value="RED"/>
        </fx:define>
    
        <padding>
            <Insets topRightBottomLeft="25"/>
        </padding>
    
        <VBox HBox.hgrow="ALWAYS" spacing="10" alignment="CENTER">
            <Label text="SHAPE" font="$titleFont"/>
            <Separator/>
            <Pane fx:id="shapePane" VBox.vgrow="ALWAYS">
                <clip>
                    <Rectangle x="-5" y="-5" width="${shapePane.width}" height="${shapePane.height}"/>
                </clip>
                <Pane managed="false">
                    <Rectangle fx:id="shape" width="100" height="100" managed="false" onMousePressed="#handleMousePressed"
                               onMouseDragged="#handleMouseDragged" onMouseReleased="#handleMouseReleased"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inLocalColor" strokeType="INSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${shape.boundsInLocal.minX}" y="${shape.boundsInLocal.minY}"
                               width="${shape.boundsInLocal.width}" height="${shape.boundsInLocal.height}"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inParentColor" strokeType="OUTSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${shape.boundsInParent.minX}" y="${shape.boundsInParent.minY}"
                               width="${shape.boundsInParent.width}" height="${shape.boundsInParent.height}"/>
                </Pane>
            </Pane>
        </VBox>
    
        <Separator orientation="VERTICAL"/>
    
        <VBox HBox.hgrow="ALWAYS" spacing="10" alignment="CENTER">
            <Label text="NON-SHAPE" font="$titleFont"/>
            <Separator/>
            <Pane fx:id="nonShapePane" VBox.vgrow="ALWAYS">
                <clip>
                    <Rectangle x="-5" y="-5" width="${nonShapePane.width}" height="${nonShapePane.height}"/>
                </clip>
                <Pane managed="false">
                    <Region fx:id="nonShape" prefWidth="100" prefHeight="100" style="-fx-background-color: black;"
                            onMousePressed="#handleMousePressed" onMouseDragged="#handleMouseDragged"
                            onMouseReleased="#handleMouseReleased"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inLocalColor" strokeType="INSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${nonShape.boundsInLocal.minX}" y="${nonShape.boundsInLocal.minY}"
                               width="${nonShape.boundsInLocal.width}" height="${nonShape.boundsInLocal.height}"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inParentColor" strokeType="OUTSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${nonShape.boundsInParent.minX}"
                               y="${nonShape.boundsInParent.minY}" width="${nonShape.boundsInParent.width}"
                               height="${nonShape.boundsInParent.height}"/>
                </Pane>
            </Pane>
        </VBox>
    </HBox>
     

    运行上述结果会导致:

    红色轮廓是节点的父项边界,绿色轮廓是节点的父项边界.拖动节点时,形状和非形状都会通过translate[X|Y]属性更新位置,这不会影响 locals-in-local .但是对于形状,释放鼠标时,转换转换将复制到形状属性"(即Rectangle.xRectangle.y),然后将转换属性重置为0.这说明,对于形状,局部边界可能具有与(0,0)不同的原点.

    Shape更改其局部边界的事实会对诸如转换之类的事情产生影响.例如,如果要使用Rotate将节点绕其中心旋转,则形状的枢轴点将与非形状的枢轴点不同:

    • 形状(例如Rectangle):(x + width / 2, y + height / 2) 1

    • 非形状(例如Region):(width / 2, height / 2) 2

    受影响的另一件事是您从MouseEvent获得的本地鼠标坐标.


    1.请注意,对于Circle这样的形状,您可以简单地使用centerXcenterY.对于Circle来说,一个有趣的结果是,本地边界的最小值将为负.
    2.从技术上讲,这里唯一的区别是xy被称为0.

    I'm trying to understand the coordinate system of JavaFX.

    For some nodes (shapes?) like Line or Rectangle I can (or should) specify a x and y value in the coordinate system.

    What exactly is this? Is this a translation and stretch which is later appended to the node or something else? Other nodes only have a setLayoutX() method, whereas e.g. the Line has both setLayoutX() as well as setStartX().

    Thanks!

    解决方案

    Every Node has two different bounds properties (ignoring Node#layoutBounds) relating to two different coordinate spaces—local and parent.

    1. Node#boundsInLocal

      The rectangular bounds of this Node in the node's untransformed local coordinate space. For nodes that extend Shape, the local bounds will also include space required for a non-zero stroke that may fall outside the shape's geometry that is defined by position and size attributes. The local bounds will also include any clipping set with clip as well as effects set with effect.

      [...]

    2. Node#boundsInParent

      The rectangular bounds of this Node which include its transforms. boundsInParent is calculated by taking the local bounds (defined by boundsInLocal) and applying the transform created by setting the following additional variables

      1. transforms ObservableList
      2. scaleX, scaleY, scaleZ
      3. rotate
      4. layoutX, layoutY
      5. translateX, translateY, translateZ


      The resulting bounds will be conceptually in the coordinate space of the Node's parent, however the node need not have a parent to calculate these bounds.

      [...]

    This means properties such as layoutX and translateX only affect the bounds-in-parent. Basically, the bounds-in-parent is simply the bounds-in-local with the various transformations applied. But when it comes to the "special" Shape properties, such as the x and y properties of Rectangle, they directly affect the bounds-in-local. I was unable to find documentation explaining this, though maybe I just missed it or this behavior is supposed to be obvious to those "in the know". Unfortunately I won't be able to explain to you why these Shape properties affect the bounds-in-local directly as I lack foundational knowledge in this area.

    That said, I can visually demonstrate the differences regarding bounds-in-local with the following example:

    App.java

    import javafx.application.Application;
    import javafx.fxml.FXMLLoader;
    import javafx.scene.Parent;
    import javafx.scene.Scene;
    import javafx.stage.Stage;
    
    import java.io.IOException;
    
    public class App extends Application {
    
        @Override
        public void start(Stage primaryStage) throws IOException {
            var root = FXMLLoader.<Parent>load(getClass().getResource("App.fxml"));
            primaryStage.setScene(new Scene(root));
            primaryStage.show();
        }
    
    }
    

    Controller.java

    import javafx.fxml.FXML;
    import javafx.geometry.Point2D;
    import javafx.scene.Node;
    import javafx.scene.input.MouseEvent;
    import javafx.scene.shape.Rectangle;
    
    public class Controller {
    
        @FXML
        private void handleMousePressed(MouseEvent event) {
            event.consume();
    
            var source = (Node) event.getSource();
            source.setUserData(source.localToParent(event.getX(), event.getY()));
        }
    
        @FXML
        private void handleMouseDragged(MouseEvent event) {
            event.consume();
    
            var source = (Node) event.getSource();
            var lastPoint = (Point2D) source.getUserData();
            var nextPoint = source.localToParent(event.getX(), event.getY());
    
            source.setTranslateX(source.getTranslateX() + nextPoint.getX() - lastPoint.getX());
            source.setTranslateY(source.getTranslateY() + nextPoint.getY() - lastPoint.getY());
    
            source.setUserData(nextPoint);
        }
    
        @FXML
        private void handleMouseReleased(MouseEvent event) {
            event.consume();
    
            var source = (Node) event.getSource();
            if (source instanceof Rectangle) {
                var rectangle = (Rectangle) source;
                rectangle.setX(rectangle.getX() + rectangle.getTranslateX());
                rectangle.setTranslateX(0);
                rectangle.setY(rectangle.getY() + rectangle.getTranslateY());
                rectangle.setTranslateY(0);
            }
            source.setUserData(null);
        }
    
    }
    

    App.fxml

    <?import javafx.geometry.Insets?>
    <?import javafx.scene.control.Label?>
    <?import javafx.scene.control.Separator?>
    <?import javafx.scene.layout.HBox?>
    <?import javafx.scene.layout.Pane?>
    <?import javafx.scene.layout.Region?>
    <?import javafx.scene.layout.VBox?>
    <?import javafx.scene.paint.Color?>
    <?import javafx.scene.shape.Rectangle?>
    <?import javafx.scene.text.Font?>
    
    <HBox xmlns="http://javafx.com/javafx/12.0.2" xmlns:fx="http://javafx.com/fxml/1" fx:controller="Controller"
          prefWidth="1000" prefHeight="600" spacing="15">
    
        <fx:define>
            <Font fx:id="titleFont" name="Impact" size="24"/>
            <Color fx:id="inLocalColor" fx:value="LIME"/>
            <Color fx:id="inParentColor" fx:value="RED"/>
        </fx:define>
    
        <padding>
            <Insets topRightBottomLeft="25"/>
        </padding>
    
        <VBox HBox.hgrow="ALWAYS" spacing="10" alignment="CENTER">
            <Label text="SHAPE" font="$titleFont"/>
            <Separator/>
            <Pane fx:id="shapePane" VBox.vgrow="ALWAYS">
                <clip>
                    <Rectangle x="-5" y="-5" width="${shapePane.width}" height="${shapePane.height}"/>
                </clip>
                <Pane managed="false">
                    <Rectangle fx:id="shape" width="100" height="100" managed="false" onMousePressed="#handleMousePressed"
                               onMouseDragged="#handleMouseDragged" onMouseReleased="#handleMouseReleased"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inLocalColor" strokeType="INSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${shape.boundsInLocal.minX}" y="${shape.boundsInLocal.minY}"
                               width="${shape.boundsInLocal.width}" height="${shape.boundsInLocal.height}"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inParentColor" strokeType="OUTSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${shape.boundsInParent.minX}" y="${shape.boundsInParent.minY}"
                               width="${shape.boundsInParent.width}" height="${shape.boundsInParent.height}"/>
                </Pane>
            </Pane>
        </VBox>
    
        <Separator orientation="VERTICAL"/>
    
        <VBox HBox.hgrow="ALWAYS" spacing="10" alignment="CENTER">
            <Label text="NON-SHAPE" font="$titleFont"/>
            <Separator/>
            <Pane fx:id="nonShapePane" VBox.vgrow="ALWAYS">
                <clip>
                    <Rectangle x="-5" y="-5" width="${nonShapePane.width}" height="${nonShapePane.height}"/>
                </clip>
                <Pane managed="false">
                    <Region fx:id="nonShape" prefWidth="100" prefHeight="100" style="-fx-background-color: black;"
                            onMousePressed="#handleMousePressed" onMouseDragged="#handleMouseDragged"
                            onMouseReleased="#handleMouseReleased"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inLocalColor" strokeType="INSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${nonShape.boundsInLocal.minX}" y="${nonShape.boundsInLocal.minY}"
                               width="${nonShape.boundsInLocal.width}" height="${nonShape.boundsInLocal.height}"/>
                    <Rectangle fill="TRANSPARENT" stroke="$inParentColor" strokeType="OUTSIDE" strokeWidth="2"
                               mouseTransparent="true" x="${nonShape.boundsInParent.minX}"
                               y="${nonShape.boundsInParent.minY}" width="${nonShape.boundsInParent.width}"
                               height="${nonShape.boundsInParent.height}"/>
                </Pane>
            </Pane>
        </VBox>
    </HBox>
    

    Running the above results in:

    The red outlines are the bounds-in-parent of the node while the green outlines are the bounds-in-local of the node. When dragging the node the position is updated via the translate[X|Y] properties, for both the shape and non-shape, which don't affect the bounds-in-local. But for the shape, when the mouse is released the translate transforms are copied over to the "shape properties" (i.e. Rectangle.x and Rectangle.y) and then the translate properties are reset to 0. This shows how, for a shape, the bounds-in-local may have a different origin point than (0,0).

    The fact a Shape changes its bounds-in-local has consequences for things such as transforms. For example, if you want to rotate a node about its center using a Rotate, then the pivot point would be different for a shape than a non-shape:

    • Shape (e.g. Rectangle): (x + width / 2, y + height / 2)1

    • Non-shape (e.g. Region): (width / 2, height / 2)2

    Another thing affected is the local mouse coordinates you get from a MouseEvent.


    1. Note for shapes like Circle you can simply use centerX and centerY. An interesting consequence of this for Circle is that the bounds-in-local will have a negative min values.
    2. Technically the only difference here is that x and y are known to be 0.

    这篇关于为什么有些节点具有x和y位置,而另一些却没有的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

    查看全文
    相关文章
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆