Force a UI update - java

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?

I find your code quite strange:
Why use node.setOpacity(0) to make it invisible, rather than node.setVisible(false)?
Why return an AWT BufferedImage rather than a JavaFX Image?
Why use a robot to capture of the screen rather than taking a snapshot of the scene?
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.
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:
Issue some update commands (e.g. setting node opacity to 0) on the JavaFX application thread.
Issue a Platform.runLater call and take your robotic snapshot in the runLater body.
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.
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.

Related

Fire only the last event in a series of events in JavaFX

I have a PackingCanvas that draws elements on the screen, which is an extension of the JavaFX Canvas control. I want my elements to be drawn on resize, but only when the resize is done, as redrawing the entire canvas on every resize event might be too costly in some situations.
What I have now is the following:
widthProperty().addListener(evt -> draw());
heightProperty().addListener(evt -> draw());
I have tried doing this with threads; add a small delay, see if new a new thread is created, if not; draw(), else abort current thread.
However, this doesn't seem like an elegant solution to me. Is there an idiom to deal with these problems?
you can cache width & height inside draw() function.
void draw()
{
cacheHeight = getHeight();
cacheWidth = getWidth();
}
then
heightProperty().addListener(evt -> {if(getHeight()!=cacheHeight) draw();});

Moving an Image on Canvas with KeyPressed

My KeyPressed is working but there is no change in avatarDX from my moveLeft method. Did I do something wrong with moveLeft? It seems like it should be simple but I'm not sure what I did wrong. I don't have any error messages.
I'm drawing an image on a canvas with
gc.drawImage(avatar, avatarSX, avatarSY, avatarSW, avatarSH, avatarDX, avatarDY, avatarDW, avatarDH);
For KeyPressed I have
canvas.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent ke) {
double speed = 2;
switch(ke.getCode()) {
case A:
System.out.println("pressed a");
moveLeft(avatarDX, speed);
System.out.println(avatarDX);
break;
}
}
});
And moveLeft is
private void moveLeft(double avatarDX, double speed) {
while (avatarDX > 0) {
avatarDX -= speed;
}
}
I would appreciate any help I am very new at this.
A reason (there may be others) the program doesn't act as you expect is that you are trying to process via a while loop to control your avatar.
This will just pause the JavaFX application until the loop is completed and then update the screen to the final state at the end of the loop. JavaFX is not an immediate drawing system, it is a retained drawing system that only renders a scene when you cede control of the JavaFX application thread back to the JavaFX system.
Instead, you need to use a callback to update the scene on each pulse.
I suggest you use the following approach:
As you are new at this, try writing the application using the JavaFX scene graph rather than a Canvas. Programming the scene graph is simply easier for many things. If, at a later stage, you find that Canvas is a better fit, then you can always convert to Canvas at that time when you better understand the JavaFX programming model.
Review this sample of moving a character around the scene graph using key presses.
Don't try to loop to move your character. Instead, either use the in-built high level JavaFX animation facilities such as transitions, or (when those aren't a good fit, as is likely the case here), use the low level AnimationTimer interface.
Read up on game loops and JavaFX and apply the knowledge you learn there to your program.

computing JavaFX animation values without knowing layout

As succinctly as I can manage: Given that I need the layout information of a node (the actual height/width of a node as rendered) to compute my animation, how can I get that information before javafx draws a frame with it?
A little bit longer explanation:
I've got a TreeItem that has child items appearing in it (at the front). What I'd like to have is an animation to cause all existing children to slide down to make room for the new item which would slide in. Each child tree-items contents are different and only known at run-time, meaning the height of each child tree item cannot be expressed as a constant.
This got me writing code along these lines:
groupController.groupTreeItem.getChildren().addListener(
new ListChangeListener<TreeItem<Node>>() {
#Override public void onChanged(Change<? extends TreeItem<Node>> c) {
while(c.next()){
if ( ! c.wasAdded()){
continue;
}
TreeItem newItem = c.getAddedSublist().get(0)
new Timeline(
new KeyFrame(
seconds(0),
new KeyValue(view.translateYProperty(), -1 * newItem.getHeight())
),
new KeyFrame(
seconds(1),
new KeyValue(view.translateYProperty(), 0)
)
);
}
}
}
);
the issue here is that as when a treeItem is added to another, its components aren't laid out by the time the invalidation event is fired, meaning newItem.view.getHeight() returns 0.
My next thought was to then have the animation performed as a reaction to both a change in the list content and a sequential change to the height property, (which got me to write some really hideous code that I'd rather not share --listeners adding listeners is not something I really want to write). This almost works, except that javaFX will draw a single frame with the height property set but without the animations translation applied. I could hack down this road further and try to work something out with opacity being toggled and jobs being enqueued for later, but I figured that this would be the path to madness.
I'm wondering if there's some pseudo-class foo or some clever use of a layout property I could use to help me here. I've been poking around at various combinations of various properties, and haven't gotten anywhere. It seems that as soon as the component has a height, it is rendered, regardless of any listeners you put in or around that height assignment.
Any help much appreciated!
have you tried, overriding this
#Override
protected void updateBounds() {
super.updateBounds();
}

Change SWT tree node text foreground when highlighted

I’m pretty new in SWT/JFace technology and I’ve found a problem that it’s driving me crazy. In an Eclipse RCP application I have a view where I’ve placed a SWT tree with a JFace TreeViewer which provides the labels and the icons by means of a label provider. By requirements of the customer the background colour of the tree is dark blue and the font colour is white. This combination of colours results in a bad visualization of a node’s text when the node is selected, the text does not fit the tree region and we place the mouse pointer over the node. Somehow a “native highlighting” appears. This can be shown in the following image.
On the other side, this problem does not happen when the node where we place the mouse over is not selected. The highlighting changes the colour of the font to make it more visible. This can be shown in the following image.
After doing some research I’ve found that by adding a listener for the SWT.EraseItem event I am able to modify the background’s colour of a selected node and then disable the selection. This allows me to define my own selection background style and also disable the SWT.SELECTED flag of the event.detail in order to force the OS to highlight as the node is not selected.
private final class EraseItemListener implements Listener {
public void handleEvent(Event event) {
// Only perform the node highlight when it is selected.
if ((event.detail & SWT.SELECTED) == SWT.SELECTED) {
// Modify background, emulate Windows highlighting.
...
// Set Windows that we do not want to draw this item as a selection (we have already highlighted the item in our way).
event.detail &= ~SWT.SELECTED;
}
}
}
This “solution” can be reasonable. The main drawbacks I see is that my selection style only fits for the Windows 7 default visual themes. For those “Windows classic” or “High contrast” I’ll get visualization problems. Moreover (and this is the most annoying issue), the fact of adding a listener for the SWT.EraseItem (even without code to handle the event) produces two new problems.
This makes either SWT or JFace to draw the icon of the tree node in
the wrong place as you can see in the following image.
The highlight of the tree’s root node is completely wrong. As you
can see in the following image, the node seems to be highlighted in
2 different ways and the icon is repeated.
My questions are basically two.
Do you think there is an easier solution for the main problem? What
I would like is to show a selected node (the one of the first image)
in the same way as in the second image. I would like to change the
foreground colour of the selected node to make it more visible.
In case of using the SWT.EraseItem approach, is there any way of
showing the icons in the correct location? Is this behaviour a known
bug?
Thanks in advance.
If you use a label provider based on StyledCellLabelProvider (perhaps one based on DelegatingStyledCellLabelProvider) you can specify COLORS_ON_SELECTION to retain normal colors on selection.
If that is not good enough (don't have Windows here to test) you can override the paint method - again you can try using event.detail &= ~SWT.SELECTED to suppress the normal selection handling or even handle the paint yourself.
Just stumbled across this old post and realized it matches a bug that I opened recently on eclipse.
My workaround to have the text in the correct color was to paint over the text with a different color using a PaintListener (registered in the tree with SWT.PaintItem):
private static class TreePaintListener implements Listener {
#Override
public void handleEvent(Event event) {
boolean isSelected = (event.detail & SWT.SELECTED) != 0;
if (isSelected && event.item instanceof TreeItem) {
TreeItem treeItem = (TreeItem) event.item;
Tree parent = treeItem.getParent();
GC gc = event.gc;
Color foreground = null/* Some Color */;
gc.setForeground(foreground);
Rectangle imageBounds = treeItem.getImageBounds(0);
Rectangle textBounds = treeItem.getTextBounds(0);
Point stringExtent = gc.stringExtent(treeItem.getText());
int offsetX = imageBounds.width != 0 ? imageBounds.height - imageBounds.width : 0;
int offsetY = (textBounds.height - stringExtent.y) / 2;
int x = textBounds.x + offsetX;
int y = textBounds.y + offsetY;
if (gc.getClipping().contains(x, y)) {
gc.drawString(treeItem.getText(), x, y);
}
}
}
}
But it still misbehaves when there are multiple columns defined (only the first one is painted over), and depending on the operating system and DPI the calculations might be a bit off.

Random numbers in an applet displayed at runtime update each frame, or get overdrawn by background images

Edit: uses awt...
I am trying to display random numbers in an applet based maths game, and have run into an issue. Either one of two things happen depending on where I call the method:
The numbers generate and display properly, without automatically updating each frame but get drawn over by the background and artwork each frame as they're being updated at runtime, or...
The number displayed on-screen appear above the background elements but are redrawn fresh every frame.
Quick illustration:
private int setNumberOne() {
return rand.nextInt(11) + 2;
}
private int setNumberTwo() {
return rand.nextInt(11) + 2;
}
private int setAnswer() {
return setNumberTwo() * setNumberOne();
}
private void displayOutput() {
Graphics2D g2d = graphics();
int one = setNumberOne();
int ans = setAnswer();
setNumberTwo();
g2d.setColor(Color.WHITE);
g2d.drawString(one + " x ? = " + ans, 480, 480);
}
Calling this function in the initialisation method displays a static question that I can update elsewhere when specific triggers are met, however everything else gets drawn on top of it by the update event, rendering it invisible. The only way I've managed to see this working is to remove the other images from the game for testing.
Is there a way to set the "precedence" of GUI elements in an applet?
I'm currently looking at attempting to include the displayOutput() method in it's own thread, but I'm not so experienced with Java and its proving very hard to resolve.
I also tried to not allow the background to update at runtime, but the result was that the moving game objects left trails all over the screen.
What am I missing? If anyone has any suggestions as to how this can be correctly implemented, I'd be delighted to hear them.
update: "...draw in a [...] Component's paint(...) method if AWT, and use the Graphics object passed in by the JVM" -
Resolved, Thank you!
Don't use getGraphics() for your Graphics component as this will return a non-persisting object that will get drawn over with the next repaint. Instead draw in a JComponent's paintComponent(...) method if this is a Swing app (you don't say) or a Component's paint(...) method if AWT, and use the Graphics object passed in by the JVM. Mostly read the tutorials on how to draw with Java as this has been well explained there.

Categories

Resources