【问题标题】:correct way to move a node by dragging in javafx 2?通过拖入 javafx 2 来移动节点的正确方法?
【发布时间】:2012-05-27 18:25:54
【问题描述】:

我正在将具有大量自定义绘画的 Swing/Graphics2D 应用程序转换为 JavaFX2 应用程序。虽然我非常喜欢新的 API,但在绘制椭圆时,我似乎遇到了性能问题,我想在鼠标移动的任何地方绘制我想在鼠标光标下方绘制的椭圆。当我以稳定的方式而不是快得离谱的方式移动鼠标时,我注意到椭圆总是在鼠标轨迹后面几厘米处绘制,并且只有在我停止移动光标时才会赶上。这在只有少数节点的场景图中。在我的 Swing 应用程序中,我没有这个问题。

我想知道这是否是在鼠标光标所在位置绘制形状的正确方法?

import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.SceneBuilder;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Ellipse;
import javafx.scene.shape.EllipseBuilder;
import javafx.stage.Stage;

public class TestApp extends Application {
public static void main(String[] args) {
    launch(args);
}

@Override
public void start(Stage primaryStage) throws Exception {
    Pane p = new Pane();

    final Ellipse ellipse = EllipseBuilder.create().radiusX(10).radiusY(10).fill(Color.RED).build();
    p.getChildren().add(ellipse);

    p.setOnMouseMoved(new EventHandler<MouseEvent>() {
        public void handle(MouseEvent event) {
            ellipse.setCenterX(event.getX());
            ellipse.setCenterY(event.getY());
        }
    });

    Scene scene = SceneBuilder.create().root(p).width(1024d).height(768d).build();
    primaryStage.setScene(scene);

    primaryStage.show();
}
}

小更新:我升级到 JavaFX 2.2 和 Java7u6(在 Windows 7 64 位上),但似乎没有什么不同。

【问题讨论】:

  • 您的解决方案本身对我帮助很大,谢谢!
  • 很高兴它对你有所帮助 :)

标签: java javafx


【解决方案1】:

这是我用来允许在Pane 中拖动Label 的一些代码。 我没有注意到它的鼠标轨迹有任何明显的滞后。

// allow the label to be dragged around.
final Delta dragDelta = new Delta();
label.setOnMousePressed(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent mouseEvent) {
    // record a delta distance for the drag and drop operation.
    dragDelta.x = label.getLayoutX() - mouseEvent.getSceneX();
    dragDelta.y = label.getLayoutY() - mouseEvent.getSceneY();
    label.setCursor(Cursor.MOVE);
  }
});
label.setOnMouseReleased(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent mouseEvent) {
    label.setCursor(Cursor.HAND);
  }
});
label.setOnMouseDragged(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent mouseEvent) {
    label.setLayoutX(mouseEvent.getSceneX() + dragDelta.x);
    label.setLayoutY(mouseEvent.getSceneY() + dragDelta.y);
  }
});
label.setOnMouseEntered(new EventHandler<MouseEvent>() {
  @Override public void handle(MouseEvent mouseEvent) {
    label.setCursor(Cursor.HAND);
  }
});

. . .

// records relative x and y co-ordinates.
class Delta { double x, y; }

这里是使用上述代码的小完整example app

更新 上面的例子,当被拖动的对象很小时,仍然会滞后于被拖动的对象。

另一种方法是使用ImageCursor,其中包含叠加在被拖动节点的图像表示上的鼠标指针,然后在拖动开始和完成时隐藏并显示实际节点。这意味着节点拖动渲染不会滞后光标(因为节点的图像表示现在是光标)。但是这种方法确实有缺点 => ImageCursors 的大小和格式有限制,另外您需要将 Node 转换为 Image 以将其放置在 ImageCursor 中,为此您可能需要高级 Node => Image 转换操作仅在 JavaFX 2.2+ 中可用。

【讨论】:

  • 感谢您的详尽回答,但使用您的方法我也遇到了滞后。奇怪!
  • 我添加了一个完整的简单示例来说明我的意思。即使是这个小程序在稳定移动鼠标时也会显示滞后。请注意,我不想拖动椭圆,但我想在鼠标光标下方显示它,即使只是移动鼠标而不是单击。
  • 好的,当我使拖动的对象足够小时,我可以注意到与我在示例代码中看到的方法相同的滞后(当被拖动的对象较大时,它只是被掩盖了)。
  • 我使用 ImageCursor 添加了替代解决方案的大纲。
  • 谢谢,我也是这么想的,但看起来很麻烦。我只是无法停止思考必须有另一种更简单的方法来实现正常性能。我会在一段时间内回来讨论这个..
【解决方案2】:

所有节点上都有这个“cacheHint”属性,这可能会有所帮助吗?

http://docs.oracle.com/javafx/2/api/javafx/scene/Node.html#cacheHintProperty

在某些情况下,例如渲染非常昂贵的动画节点,希望能够在节点上执行转换而不必重新生成缓存的位图。在这种情况下,一个选项是对缓存的位图本身执行转换。

这种技术可以显着提高动画性能,但也可能导致视觉质量下降。 cacheHint 变量向系统提供关于如何以及何时可以接受权衡(动画性能的视觉质量)的提示。

如果你的椭圆一直保持不变,但每次移动一个像素时都会重新绘制,这似乎是一个巨大的减速。

【讨论】:

    【解决方案3】:

    在我看来,这似乎不是绘画性能的问题,而是鼠标事件序列是如何生成的。这些事件不是实时生成的,当鼠标快速移动时,有些事件会被跳过。对于大多数应用程序,这将是足够的方式。鼠标指针实时移动,没有任何延迟。

    如果您不想要这种效果,您将不得不直接听鼠标指针或找到一种方法来获得更高密度的事件。我自己也不知道。

    【讨论】:

    • 嗯,你有任何文件支持这些说法吗?考虑到你可以在 SceneBuilder 中获取一个元素,我会觉得这很奇怪,尽可能快地拖动鼠标,它会一直粘在鼠标指针上。
    • 抱歉,找不到任何东西。写了一个测试,我机器上的两个事件之间大约有 10 毫秒。在 SceneBuilder 中,我识别出类似的滞后。
    • 我认为你是对的,问题与MouseEvents有关。 JavaFX JIRA 存储库中有一个与此相关的错误:javafx-jira.kenai.com/browse/RT-34608
    【解决方案4】:

    我在尝试使图表上的节点可拖动时遇到了同样的问题。我通过调用 chart.setAnimated(false); 来修复它在我的情况下,延迟是由 JavaFX 对我的代码所做的更改应用一个漂亮的平滑动画造成的。

    【讨论】:

    • 我认为只适用于图表api
    【解决方案5】:

    您所描述的延迟(在您的鼠标和拖动的形状之间)是一个已知的 JavaFX 错误:

    https://bugs.openjdk.java.net/browse/JDK-8087922

    您可以通过使用未记录的 JVM 标志来解决它(至少在 Windows 上):

    -Djavafx.animation.fullspeed=true
    

    这个标志通常用于内部性能测试,这就是它没有记录的原因,但我们已经使用了几个月,到目前为止还没有遇到任何问题。

    编辑:

    还有另一种类似的方法来解决这个错误,它可能更容易降低 CPU 使用率。只需关闭 Prism 的垂直同步:

    -Dprism.vsync=false
    

    在我们的应用中,这两种变通方法都可以解决延迟问题;没必要两者都做。

    【讨论】:

    • 我有点初学者。我将在哪里插入该代码?自从回答了这个问题后,该错误是否已修复?
    【解决方案6】:

    这是在 javafx 中使用鼠标拖放标签的代码

    @FXML
    public void lblDragMousePressed(MouseEvent m)
    {
        System.out.println("Mouse is pressed");
    
        prevLblCordX= (int) lblDragTest.getLayoutX();
        prevLblCordY= (int) lblDragTest.getLayoutY();
        prevMouseCordX= (int) m.getX();
        prevMouseCordY= (int) m.getY();     
    }
    //set this method on mouse released event for lblDrag
    @FXML
    public void lblDragMouseReleased(MouseEvent m)
    {       
        System.out.println("Label Dragged");    
    }
    // set this method on Mouse Drag event for lblDrag
    @FXML
    public void lblDragMouseDragged(MouseEvent m)
    {   
        diffX= (int) (m.getX()- prevMouseCordX);
        diffY= (int) (m.getY()-prevMouseCordY );        
        int x = (int) (diffX+lblDragTest.getLayoutX()-rootAnchorPane.getLayoutX());
        int y = (int) (diffY+lblDragTest.getLayoutY()-rootAnchorPane.getLayoutY());     
        if (y > 0 && x > 0 && y < rootAnchorPane.getHeight() && x < rootAnchorPane.getWidth()) 
        { 
         lblDragTest.setLayoutX(x);
         lblDragTest.setLayoutY(y);
        }
    }
    

    【讨论】:

      【解决方案7】:

      你可以使用:Node.setCache(true); (我将它与具有许多子项的窗格一起使用,例如 TextField)

      【讨论】:

        【解决方案8】:

        拖动球体

        @Override
        public void initialize(URL location, ResourceBundle resources) {
            super.initialize(location, resources);
            labelTableName.setText("Table Categories");
        
            final PhongMaterial blueMaterial = new PhongMaterial();
            blueMaterial.setDiffuseColor(Color.BLUE);
            blueMaterial.setSpecularColor(Color.LIGHTBLUE);
        
            final Sphere sphere = new Sphere(50);
            sphere.setMaterial(blueMaterial);
        
            final Measure dragMeasure = new Measure();
            final Measure position = new Measure();
            sphere.setOnMousePressed(mouseEvent -> {
                dragMeasure.x = mouseEvent.getSceneX() - position.x;
                dragMeasure.y = mouseEvent.getSceneY() - position.y;
                sphere.setCursor(Cursor.MOVE);
            });
            sphere.setOnMouseDragged(mouseEvent -> {
                position.x = mouseEvent.getSceneX() - dragMeasure.x;
                position.y = mouseEvent.getSceneY() - dragMeasure.y;
                sphere.setTranslateX(position.x);
                sphere.setTranslateY(position.y);
            });
            sphere.setOnMouseReleased(mouseEvent -> sphere.setCursor(Cursor.HAND));
            sphere.setOnMouseEntered(mouseEvent -> sphere.setCursor(Cursor.HAND));
        
            bottomHeader.getChildren().addAll( sphere);
        
        }
        
        class Measure {
            double x, y;
        
            public Measure() {
                x = 0; y = 0;
            }
        }
        

        【讨论】:

          【解决方案9】:

          这是根据@jewelsea 的回答修改的 Kotlin 代码

              var dragDelta = Delta()
              var releasedDelta = Delta()
          
              scene.setOnMousePressed {
                  if (releasedDelta.x > 0 && releasedDelta.y > 0) {
                      val offsetX = it.sceneX - releasedDelta.x
                      var offsetY = it.sceneY - releasedDelta.y
                      dragDelta.x = dragDelta.x + offsetX
                      dragDelta.y = dragDelta.y + offsetY
                  } else {
                      dragDelta.x = it.sceneX
                      dragDelta.y = it.sceneY
                  }
                  scene.cursor = Cursor.MOVE;
                  releasedDelta = Delta()
              }
          
              scene.setOnMouseReleased {
                  releasedDelta.x = it.sceneX
                  releasedDelta.y = it.sceneY
                  scene.cursor = Cursor.HAND;
              }
          
              scene.setOnMouseDragged {
                  scene.translateX = it.sceneX - dragDelta.x;
                  scene.translateY = it.sceneY - dragDelta.y;
              }
          
              scene.setOnMouseEntered {
                  scene.cursor = Cursor.HAND
              }
          

          【讨论】:

            猜你喜欢
            • 2015-01-20
            • 1970-01-01
            • 2014-07-12
            • 1970-01-01
            • 1970-01-01
            • 2015-09-06
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多