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

时间:2012-05-21 08:51:06

标签: java javafx

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

8 个答案:

答案 0 :(得分:22)

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

// 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

<强>更新 当拖动的对象很小时,上面的例子仍然会滞后于拖动光标的对象。

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

答案 1 :(得分:7)

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

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

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

-Djavafx.animation.fullspeed=true

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

编辑:

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

-Dprism.vsync=false

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

答案 2 :(得分:3)

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

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

答案 3 :(得分:2)

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

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

  

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

     

此技术可以显着改善动画效果,但也可能导致视觉质量下降。 cacheHint变量向系统提供有关如何以及何时权衡(动画性能的视觉质量)是否可接受的提示。

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

答案 4 :(得分:2)

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

答案 5 :(得分:1)

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

答案 6 :(得分:1)

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

答案 7 :(得分:0)

拖动球体

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