Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Force a UI update

I have a piece of code designed to take a screenshot of a node in 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;
    }
}

It has a twist, and that is it should hide the given nodes before taking the screenshot (in case they overlap with the node, which in some cases is definite.)

However, I'm stuck finding a way to force a redraw to include the opacity change before taking the screenshot - the only reference I found was to requestLayout(), but no joy there.

What method(s) should I call to force and wait for a redraw to complete?

like image 706
Michael Berry Avatar asked Jan 09 '13 23:01

Michael Berry


People also ask

How do I force install a Windows Update?

We’ve compiled some possible ways to force install a Windows Update by eliminating issues causing the delay. 1. Restart the Windows Update Service This service handles the delivery of software updates to Windows devices. Your PC may fail to automatically download or install a new update if the service is malfunctioning or inactive.

Why can’t I force a Windows Update?

You may be unable to force a Windows Update if Microsoft places a Safeguard Hold on your PC. A “safeguard hold” is a technique used to temporarily prevent users from installing an unstable or potentially harmful update. So, how do you identify a safeguard hold?

Is there a way to force a layout to update?

Instead of grabbing the layout and forcing that to update, you could just steal a line from LayoutElement (which never seems to have any problem like this) - LayoutRebuilder.MarkLayoutForRebuild (transform as RectTransform) which achieves the same effect and doesn't require you to find or store anything extra, as people have done above.

How do I force update a component in Vue?

The forceUpdate method. Vue offers us a built-in method called forceUpdate (), by using this method inside vue instance we can force update the component. In the above example, we have attached an update method to the button element. Inside the update method we have added a this.$forceUpdate () method, so that when we click on a button, ...


1 Answers

I find your code quite strange:

  1. Why use node.setOpacity(0) to make it invisible, rather than node.setVisible(false)?
  2. Why return an AWT BufferedImage rather than a JavaFX Image?
  3. Why use a robot to capture of the screen rather than taking a snapshot of the scene?
  4. Why mix Swing and JavaFX and end up having to worry about rendering order?

Perhaps there are reasons for these things which I don't understand, but I'd just do it this way:

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

I created a small sample app which uses the above routine.

The primary window includes a group with a circle and a rectangle. When a snapshot command is issued, the rectangle is hidden in the primary, a snapshot of the primary is taken, then the rectangle is made visible in the primary again.

primarysnapshot


To answer your question's title about forcing a UI update - you can't really. The JavaFX application thread and JavaFX rendering thread are to be treated as two separate things. What you need to do is run your processing on the JavaFX application thread, seed control back to the JavaFX system, wait for it to do it's rendering, then examine the results. The scene.snapshot method will take care of that synchronization for you so you don't need to worry about it.

If, for whatever reason, scene.snapshot won't work for you and you wanted to maintain something similar to your original strategy, then what you would do is:

  1. Issue some update commands (e.g. setting node opacity to 0) on the JavaFX application thread.
  2. Issue a Platform.runLater call and take your robotic snapshot in the runLater body.
  3. Once the snapshot has really been taken (notification in some awt callback), issue another Platform.runLater command to get back on the JavaFX application thread.
  4. Back in the JavaFX application thread, issue some more update commands (e.g. setting node opacity back to 1).

This should work as it will allow the JavaFX system to perform another pulse which performs a rendering layout of the screen with the opacity changes before your robot actually takes the snapshot. An alternate mechanism is to use a JavaFX AnimationTimer which will provide you with a callback whenever a JavaFX pulse occurs. Maintaining proper synchronization of all of this between the AWT and JavaFX threads, would be annoying.

like image 137
jewelsea Avatar answered Oct 22 '22 14:10

jewelsea