通过在javafx 2中拖动来移动节点的正确方法?

我正在将一个带有大量自定义绘画的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() { 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 64bit上),但似乎没有什么区别。

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

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

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

 -Djavafx.animation.fullspeed=true 

这个标志通常用于内部性能测试,这就是为什么它没有文档,但我们已经使用了几个月,到目前为止它没有任何问题。

编辑:

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

 -Dprism.vsync=false 

在我们的应用程序中,这些变通方法中的任何一个都解决了滞后; 没有必要同时做到这两点。

下面是一些代码,用于允许在窗格中拖动Label 。 我没有注意到跟踪鼠标的任何明显滞后。

 // allow the label to be dragged around. final Delta dragDelta = new Delta(); label.setOnMousePressed(new EventHandler() { @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() { @Override public void handle(MouseEvent mouseEvent) { label.setCursor(Cursor.HAND); } }); label.setOnMouseDragged(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { label.setLayoutX(mouseEvent.getSceneX() + dragDelta.x); label.setLayoutY(mouseEvent.getSceneY() + dragDelta.y); } }); label.setOnMouseEntered(new EventHandler() { @Override public void handle(MouseEvent mouseEvent) { label.setCursor(Cursor.HAND); } }); . . . // records relative x and y co-ordinates. class Delta { double x, y; } 

这是一个使用上面代码的小型完整示例应用程序。

更新上面的示例,当拖动的对象很小时,仍将滞后于拖动光标后面的对象。

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

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

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

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

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

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

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

如果您的椭圆在整个时间内保持不变,但每次将其移动一个像素时都会重绘,这似乎是一个巨大的减速。

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

这是在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); } } 

你可以使用:Node.setCache(true); (我使用带有许多儿童的窗格,如TextField)

拖动球体

 @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; } }