26

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

4

9 回答 9

25

这是我用来允许在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; }

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

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

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

于 2012-05-21T17:05:26.740 回答
10

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

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

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

-Djavafx.animation.fullspeed=true

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

编辑:

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

-Dprism.vsync=false

在我们的应用程序中,这些解决方法中的任何一个都可以解决滞后问题;没有必要两者都做。

于 2014-03-27T17:35:58.253 回答
4

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

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

于 2012-08-25T08:02:28.333 回答
2

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

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

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

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

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

于 2012-07-17T15:16:03.517 回答
2

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

于 2012-09-30T15:58:47.170 回答
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);
    }
}
于 2014-09-18T08:55:34.310 回答
1

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

于 2015-07-23T12:35:21.523 回答
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;
    }
}
于 2017-01-14T11:28:45.193 回答
0

这是根据@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
    }
于 2021-09-03T09:29:18.417 回答