强制更新用户界面

5

我有一段代码,旨在在JavaFX中截取节点的屏幕截图:

public BufferedImage getSnapshot(final Node... hideNodes) {
    Window window = getScene().getWindow();
    Bounds b = localToScene(getBoundsInLocal());
    int x = (int) Math.round(window.getX() + getScene().getX() + b.getMinX());
    int y = (int) Math.round(window.getY() + getScene().getY() + b.getMinY());
    int w = (int) Math.round(b.getWidth());
    int h = (int) Math.round(b.getHeight());
    try {
        Robot robot = new Robot();
        for(Node node : hideNodes) {
            node.setOpacity(0);
            node.getParent().requestLayout();
        }
        BufferedImage image = robot.createScreenCapture(new java.awt.Rectangle(x, y, w, h));
        for(Node node : hideNodes) {
            node.setOpacity(1);
            node.getParent().requestLayout();
        }
        return image;
    }
    catch(AWTException ex) {
        return null;
    }
}

这段代码有个特点,就是在截图之前需要隐藏给定的节点(以防它们与节点重叠,某些情况下这是肯定会发生的)。

然而,我现在遇到了一个问题,就是找不到一种方法来强制重新绘制,以便在截图之前包括透明度更改 - 我唯一找到的参考是使用requestLayout(),但并没有起作用。

应该调用哪些方法来强制重新绘制并等待重绘完成呢?

1个回答

9

我觉得你的代码有些奇怪:

  1. 为何使用 node.setOpacity(0) 让它消失,而不是 node.setVisible(false)
  2. 为何返回 Java AWT 的 BufferedImage 而不是 JavaFX 的 Image
  3. 为何使用机器人来捕获屏幕,而不是对场景进行快照?
  4. 为什么要混合使用 Swing 和 JavaFX,并且还需要担心渲染顺序?

也许有些原因我没有理解,但我会这样做:

public Image takeSnapshot(Scene scene, final Node... hideNodes) {
  for (Node node: hideNodes) node.setVisible(false);
  Image image = scene.snapshot(null);
  for (Node node: hideNodes) node.setVisible(true);

  return image;
}  

我创建了一个小的示例应用程序,它使用了上述例程。

主窗口包括一个带有圆形和矩形的组。当发出快照命令时,在主窗口中隐藏矩形,拍摄主窗口的快照,然后再将矩形显示在主窗口中。

primary snapshot


回答您关于强制UI更新的问题标题 - 你不能真正实现。JavaFX应用程序线程和JavaFX渲染线程应视为两个独立的线程。您需要在JavaFX应用程序线程上运行处理过程,将控制权交回JavaFX系统,等待其进行渲染,然后检查结果。 scene.snapshot方法将为您处理该同步,因此您不必担心它。

如果由于某种原因,scene.snapshot无法为您工作,并且您想要保持类似于原始策略的内容,则可以执行以下操作:

  1. 在JavaFX应用程序线程上发出一些更新命令(例如将节点不透明度设置为0)。
  2. 发出Platform.runLater调用,并在runLater主体中进行机器人快照。
  3. 一旦真正拍摄了快照(在某些awt回调中通知),请发出另一个Platform.runLater命令以返回JavaFX应用程序线程。
  4. 回到JavaFX应用程序线程,在其中发出更多的更新命令(例如将节点不透明度设置回1)。

这应该可以工作,因为它允许JavaFX系统执行另一个脉冲,该脉冲在机器人实际拍摄快照之前执行屏幕的渲染布局和不透明度更改。另一种机制是使用JavaFX AnimationTimer,它将在每次JavaFX脉冲发生时为您提供回调。在AWT和JavaFX线程之间维护正确的同步将非常麻烦。


2
非常感谢您提供的详尽答案 - 很多奇怪之处都可以解释为我对快照方法一无所知,而且我以为只能使用旧的awt机器人。我很愿意采用这种方法并摒弃它!然而,仍然需要BufferedImage,因为它需要传递给一个接受BufferedImage的库方法 - 但这是一个很容易的转换。 - Michael Berry
4
将JavaFX的Image转换为BufferedImage,建议使用SwingFXUtils.fromFXImage - jewelsea

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接