I have a JavaFX application, within it are about 20 labels. When a user clicks a label, I need the label to flash red. To make the label flash red, I am using a thread I actually developed for swing but converted to JavaFX. It worked for awhile, but I've recently traced the application lock-ups to the animation of the label. The way I did the animation was simple:
new Thread(new AnimateLabel(tl,idx)).start();
tl points to an ArrayList of labels, and idx to the index of it. Another label has an on click event attached to it, and when you click, it creates the thread that animates the label (makes it flash).
For some reason, this will cause the application to lock up if there's a lot of labels being pressed.
I'm pretty sure it's a thread safety issue with JavaFX. I have another thread that shares the JavaFX application thread as so:
TimerUpdater tu = new TimerUpdater(mDS);
Timeline incHandler = new Timeline(new KeyFrame(Duration.millis(130),tu));
incHandler.setCycleCount(Timeline.INDEFINITE);
incHandler.play();
TimerUpdater will constantly update the text on labels, even the ones that are flashing.
Here's the label animator:
private class AnimateLabel implements Runnable
{
private Label lbl;
public AnimateLabel(Label lbl, int myIndex)
{
// if inAnimation.get(myIndex) changes from myAnim's value, stop,
// another animation is taking over
this.lbl = lbl;
}
#Override
public void run() {
int r, b, g;
r=255;
b=0;
g=0;
int i = 0;
while(b <= 255 && g <= 255)
{
RGB rgb = getBackgroundStyle(lbl.getStyle());
if(rgb != null)
{
if(rgb.g < g-16) { return; }
}
lbl.setStyle("-fx-color: #000; -fx-background-color: rgb("+r+","+g+","+b+");");
try { Thread.sleep(6); }
catch (Exception e){}
b += 4;
g += 4;
++i;
}
lbl.setStyle("-fx-color: #000; -fx-background-color: fff;");
}
}
I would run this as such:
javafx.application.Platform.runLater(new AnimateLabel(tl, idx));
However, Thread.sleep(6) will be ignored. Is there a way to pause in a run later to control the speed of the animation while sharing a thread with javafx.application?
Regards,
Dustin
I think there's a slight misunderstanding of how the JavaFX event queue works.
1) Running your AnimateLabel code on a normal thread will cause the Label#setStyle(...) code to execute on that thread - this is illegal and likely to cause your issues.
2) Running the AnimateLabel code entirely on the JavaFX event queue, as per your second example, means that the event thread would be blocked until the animation is complete. Meanwhile, the application will not update, will not process user events or repaint, for that matter. Basically, you're changing the label style within a loop, but you're not giving the event queue time to actually redraw the label, which is why you won't see anything on screen.
The semi-correct approach is a mixture of both. Run AnimateLabel in a separate thread, but wrap the calls to Label#setStyle(...) in a Platform#runLater(...). This way you'll only bother the event thread with the relevant work, and leave it free to do other work in between (such as updating the UI).
As I said, this is the semi-correct approach since there's a build-in facility to do what you want in an easier fashion. You might want to check out the Transition class. It offers a simple approach for custom animations and even offers a bunch of prebuilt subclasses for animating the most common properties of a Node.
Sarcan has an excellent answer.
This answer just provides sample code (base on zonski's forum post) for a Timeline approach to modifying a css style. The code can be used as an alternative to the code you post in your question.
An advantage of this approach is that the JavaFX libraries handle all of the threading issues, ensuring all of your code is executed on the JavaFX thread and eliminating any thread safety concerns you may have.
import javafx.animation.*;
import javafx.application.Application;
import javafx.beans.property.*;
import javafx.beans.value.*;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;
import javafx.util.Duration;
public class CssFlash extends Application {
private Label flashOnClick(final Label label) {
label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: lightblue;"));
DoubleProperty opacity = new SimpleDoubleProperty();
opacity.addListener(new ChangeListener<Number>() {
#Override public void changed(ObservableValue<? extends Number> source, Number oldValue, Number newValue) {
label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: rgba(255, 0, 0, %f)", newValue.doubleValue()));
}
});
final Timeline flashAnimation = new Timeline(
new KeyFrame(Duration.ZERO, new KeyValue(opacity, 1)),
new KeyFrame(Duration.millis(500), new KeyValue(opacity, 0)));
flashAnimation.setCycleCount(Animation.INDEFINITE);
flashAnimation.setAutoReverse(true);
label.setOnMouseClicked(new EventHandler<MouseEvent>() {
#Override public void handle(MouseEvent t) {
if (Animation.Status.STOPPED.equals(flashAnimation.getStatus())) {
flashAnimation.play();
} else {
flashAnimation.stop();
label.setStyle(String.format("-fx-padding: 5px; -fx-background-radius: 5px; -fx-background-color: lightblue;"));
}
}
});
return label;
}
#Override public void start(Stage stage) throws Exception {
TilePane layout = new TilePane(5, 5);
layout.setStyle("-fx-background-color: whitesmoke; -fx-padding: 10;");
for (int i = 0; i < NUM_LABELS; i++) {
layout.getChildren().add(flashOnClick(new Label("Click to flash")));
}
Scene scene = new Scene(layout);
stage.setScene(scene);
stage.show();
}
private static final int NUM_LABELS = 20;
public static void main(String[] args) { Application.launch(args); }
}
JavaFX 8 will allow you to set the background of a region using a Java API, so that, if you wanted, you could accomplish the same thing without css.
Sample program output with a few of the labels clicked and in various states of flashing:
Related
I've read similar questions but my UI is still freezing when I add many nodes to a VBox. I've provided a fully functional program below which demonstrates the problem clearly.
After 4 seconds, the ProgressIndicator freezes as 5000 nodes are added to the VBox. This is an excessive amount used to demonstrate the JavaFX thread freezing despite using Task (for non-UI work) and then Platform.runLater() for adding the nodes to the scene.
In my actual application, instead of adding blank TitlePanes I'm adding a TitlePane obtained from an FXML file via new FXMLLoader(), and the resulting loader.load() then initializes the associated controller, which in turn initializes some moderately demanding computations - which are being performed on the JavaFX thread! So even though I'm adding closer to 250 nodes, the UI still freezes when the Platform.runLater is eventually used. How do I keep the ProgressIndicator from freezing until the red background is shown?
Full Example:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Accordion;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.TitledPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.util.Timer;
import java.util.TimerTask;
public class FreezingUI extends Application {
public static void main(String[] args) {
launch(args);
}
#Override
public void start(Stage primaryStage) {
VBox mainBox = new VBox();
mainBox.setPrefHeight(800);
mainBox.setStyle("-fx-background-color: #f1f1f1; -fx-alignment: center");
Label label = new Label();
label.setMinHeight(50);
label.setStyle("-fx-font-size: 24px; -fx-text-fill: #515151");
ProgressIndicator progressIndicator = new ProgressIndicator(ProgressIndicator.INDETERMINATE_PROGRESS);
mainBox.getChildren().addAll(progressIndicator, label);
Scene scene = new Scene(mainBox, 500, 800);
primaryStage.setScene(scene);
primaryStage.show();
Timer timer = new Timer();
TimerTask task = new TimerTask(){
private int i = 4;
public void run(){
if (i >= 0) {
Platform.runLater(()->{
label.setText("Freezing in " + i--);
});
}else{
addNodesToUI(mainBox);
timer.cancel();
}
}
};
timer.scheduleAtFixedRate(task, 0, 1000);
}
private void addNodesToUI(VBox mainBox) {
final int[] i = {0};
Platform.runLater(() -> {
Accordion temp = new Accordion();
mainBox.getChildren().add(temp);
while (i[0] < 5000) {
TitledPane tp = new TitledPane();
tp.setPrefWidth(300);
tp.setPrefHeight(12);
tp.setPadding(new Insets(10));
tp.setStyle("-fx-background-color: red;");
temp.getPanes().add(tp);
i[0]++;
}
});
}
}
This is happening because you are asking the UI thread to do a big bunch of things in one big lump. There is no way for the UI thread to exit the while loop until all 5000 nodes are created and added to the scene.
private void addNodesToUI(VBox mainBox) {
final int[] i = {0};
Accordion temp = new Accordion();
Platform.runLater(() -> {
mainBox.getChildren().add(temp);
});
while (i[0] < 5000) {
TitledPane tp = new TitledPane();
tp.setPrefWidth(300);
tp.setPrefHeight(12);
tp.setPadding(new Insets(10));
tp.setStyle("-fx-background-color: red;");
i[0]++;
Platform.runLater(() -> {
temp.getPanes().add(tp);
});
}
}
This will allow your nodes to be created in small batches. This way, the UI thread can attempt to render the UI while the nodes are added progressively.
For your FXML case, you can create and load the FXML in another thread. You only need to be in UI thread when you attach a scene branch into the scene. However, I would suspect that would only mitigate the effects, as you are still going to attach a big chunk at one go.
In JavaFX, it seems appropriate to me to use AnimationTimer for frequently updating Canvas Nodes; that is, for rasterized animations instead of vector animations. I'm running into an issue, which may be VSync related. I have a singular Canvas on my Scene which contains 2D data that changes frequently (the rest of the program is done with Nodes). I tend to get a tearing down the middle of the screen when I do this, which implies that the AnimationTimer may be falling out of sync with the monitor refresh rate.
This (simplified) code shows the error in detail. Note that I have no idea whether it's display hardware dependent, and the exact position of the tear may shift slightly.
import javafx.application.*;
import javafx.animation.*;
import javafx.scene.*;
import javafx.scene.canvas.*;
import javafx.stage.*;
import javafx.event.*;
import javafx.scene.input.*;
import javafx.scene.paint.*;
public class BugDemo extends Application {
public static void main(String...args) {
launch(args);
}
private Canvas background = new Canvas();
private Color bgColor = Color.rgb(0, 0, 0);
#Override
public void start(Stage primaryStage) throws Exception {
Group root = new Group();
startLoops(root);
Scene scene = new Scene(root);
initUserControls(scene);
background.widthProperty().bind(scene.widthProperty());
background.heightProperty().bind(scene.heightProperty());
root.getChildren().add(background);
primaryStage.setScene(scene);
primaryStage.setTitle("Bug Demo");
primaryStage.setMaximized(true);
primaryStage.show();
}
/**
* Initializes all running control managers for game field
* #param scene primary scene of game
*/
protected void initUserControls(Scene scene) {
EventHandler<MouseEvent> handler = new EventHandler<MouseEvent>(){
#Override
public void handle(MouseEvent event) {
bgColor = Color.rgb((int)((event.getX() / background.getWidth()) * 255),
(int)((event.getY() / background.getHeight()) * 255),
255);
}};
scene.getRoot().setOnMouseMoved(handler);
scene.getRoot().setOnMouseDragged(handler);
}
/**
* Starts all game loops, which will at the beginning of the simulation and often for the extent
* of it.
* #param parent Root node
*/
protected void startLoops(Parent parent) {
GraphicsContext gc = background.getGraphicsContext2D();
new AnimationTimer() {
#Override
public void handle(long now) {
drawBackground(gc);
}}.start();
}
protected void drawBackground(GraphicsContext gc) {
gc.setFill(bgColor);
gc.fillRect(0, 0, background.getWidth(), background.getHeight());
}
}
Moving the mouse cursor over the scene will cause the canvas to shift in background color. As you move the mouse, you will notice (presumably) a tearing in the frame.
It is quite possible that I'm simply being a N00B at JavaFX, and there's a perfectly friendly .sync()-ish method I'm supposed to call somewhere; but I haven't found it yet. I've found bugs submitted to Oracle that look similar to this. My answer may simply be dodging using Canvas and AnimationTimer for animations. All the same, even if there isn't a solution at this time, if someone has a workaround, I would love to hear about it!
Thanks!
Found the issue (thank you to #VGR for pointing out the lack of system behavioral congruity)! Seems that it wasn't Java at all; it was Marco. I switched Desktop Windowing Managers, to Compiz specifically; and the tearing is now gone.
Evidently this isn't a bug in Java at all; it's a trade-off for using Marco. This is a relief!
In JavaFX 2.2, is there any way to make TextArea (with setWrapText(true) and constant maxWidth) change its height depending on contents?
The desired behaviour: while user is typing something inside the TextArea it resizes when another line is needed and decreases when the line is needed no more.
Or is there a better JavaFX control that could be used in this situation?
You can bind the prefHeight of the text area to the height of the text it contains. This is a bit of a hack, because you need a lookup to get the text contained in the text area, but it seems to work. You need to ensure that you lookup the text node after CSS has been applied. (Typically this means after it has appeared on the screen...)
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class ResizingTextArea extends Application {
#Override
public void start(Stage primaryStage) {
TextArea textArea = new TextArea();
textArea.setWrapText(true);
textArea.sceneProperty().addListener(new ChangeListener<Scene>() {
#Override
public void changed(ObservableValue<? extends Scene> obs, Scene oldScene, Scene newScene) {
if (newScene != null) {
textArea.applyCSS();
Node text = textArea.lookup(".text");
textArea.prefHeightProperty().bind(Bindings.createDoubleBinding(new Callable<Double>() {
#Override
public Double call() {
return 2+text.getBoundsInLocal().getHeight();
}
}), text.boundsInLocalProperty()));
}
}
});
VBox root = new VBox(textArea);
Scene scene = new Scene(root, 400, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Two things to add to James_D's answer (because I lack the rep to comment):
1) For big fonts like size 36+, the text area size was wrong at first but corrected itself when I clicked inside the text area. You can call textArea.layout() after applying CSS, but the text area still does not resize immediately after the window is maximized. To get around this, call textArea.requestLayout() asynchronously in a Change Listener after any change to the Text object's local bounds. See below.
2) The text area was still a few pixels short and the scroll bar still visible. If you replace the 2 with textArea.getFont().getSize() in the binding, the height fits perfectly to the text, no matter whether the font size is tiny or huge.
class CustomTextArea extends TextArea {
CustomTextArea() {
setWrapText(true);
setFont(Font.font("Arial Black", 72));
sceneProperty().addListener((observableNewScene, oldScene, newScene) -> {
if (newScene != null) {
applyCss();
Node text = lookup(".text");
// 2)
prefHeightProperty().bind(Bindings.createDoubleBinding(() -> {
return getFont().getSize() + text.getBoundsInLocal().getHeight();
}, text.boundsInLocalProperty()));
// 1)
text.boundsInLocalProperty().addListener((observableBoundsAfter, boundsBefore, boundsAfter) -> {
Platform.runLater(() -> requestLayout());
});
}
});
}
}
(The above compiles for Java 8. For Java 7, replace the listener lambdas with Change Listeners according to the JavaFX API, and replace the empty ()-> lambdas with Runnable.)
i'm using a label for a little sprite for some testing that i'm doing, but i want to move the sprite 10 pixels per keypress. Now, i can do that, but now i'm trying to make it move the 10 pixels smoothly, so i tried the next code:
for(int i = 0; i < 100; i++){
x++;
container.setLocation(x, y);
System.out.println(x);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Now the problem is that, the sprite only moves when the for cycle ends, but the console shows the X value changing for each iteration. Any thoughts/help?
Thanks!
I suggest you to take a look at how to animate a JComponent using Swing Timer class, instead of for loop. You can find various tutorials about how to use Swing Timer. Here, to briefly explain, you are blocking EDT(Event Dispatch Thread) which operates the graphical side of the Java. Whenever you want to make a constant and smooth flow in your animations, make sure that you never block the EDT.
EDIT: Here is the demonstration of the usage of Swing Timer Class:
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;
public class AnimationTrial extends JFrame {
private final int DELAY = 10;
private Timer timer;
private int x, y;
private JLabel label;
public static void main(String[] args) {
EventQueue.invokeLater( new Runnable () {
#Override
public void run() {
new AnimationTrial();
}
});
}
public AnimationTrial()
{
setSize(500, 500);
x = 50;
y = 50;
label = new JLabel("They see me movin' they hatin'!");
timer = new Timer( DELAY, new ActionListener()
{
#Override
public void actionPerformed(ActionEvent arg0) {
x++;
label.setLocation(x, y);
}
});
timer.start();
getContentPane().add(label);
pack();
setVisible (true);
}
}
If you dont create new Thread, the user interface runs on the same thread as its method.
Therefore your for-cycle is fired after some action and thread cant do anything else until it ends.
Solution : Create your own class, pass the JLabel or the whole form as parameter in constructor, implement threading and run it as new thread.
I'd suggest you give a look to the Timing Framework, if you want to do something close to an animation in Swing. It could help you, depending on your general need.
If you want other sprites to move in sync with your sprite you can create a TimerTask and use scheduleAtFixedRate(). Your TimerTask would then be responsible for moving all sprites and redrawing everything that was part of the moving like the JPanel in the background and the sprites.
To make your code snippet work you would have to add redrawing of the Background and the sprite after setting the location but I would advise against that approach as it can easily lead to badly designed code where you create one God Class that does everything.
The TimerTask approach should also be more precise if the calculations need a bit time as it tries to have the same time between 2 calls where the approach with the sleeping thread can easily lead to different delays if the calculations are finished earlier or later.
I'm making a simple tower defense game in Swing and I've run into a performance problem when I try to put many sprites (more than 20) on screen.
The whole game takes place on a JPanel which has setIgnoreRepaint(true).
Here is the paintComponent method (con is the Controller):
public void paintComponent(Graphics g){
super.paintComponent(g);
//Draw grid
g.drawImage(background, 0, 0, null);
if (con != null){
//Draw towers
for (Tower t : con.getTowerList()){
t.paintTower(g);
}
//Draw targets
if (con.getTargets().size() != 0){
for (Target t : con.getTargets()){
t.paintTarget(g);
}
//Draw shots
for (Shot s : con.getShots()){
s.paintShot(g);
}
}
}
}
The Target class simple paints a BufferedImage at its current location. The getImage method doesn't create a new BufferedImage, it simply returns the Controller class's instance of it:
public void paintTarget(Graphics g){
g.drawImage(con.getImage("target"), getPosition().x - 20, getPosition().y - 20, null);
}
Each target runs a Swing Timer to calculate its position. This is the ActionListener it calls:
public void actionPerformed(ActionEvent e) {
if (!waypointReached()){
x += dx;
y += dy;
con.repaintArea((int)x - 25, (int)y - 25, 50, 50);
}
else{
moving = false;
mover.stop();
}
}
private boolean waypointReached(){
return Math.abs(x - currentWaypoint.x) <= speed && Math.abs(y - currentWaypoint.y) <= speed;
}
Other than that, repaint() is only called when placing a new tower.
How can I improve the performance?
Each target runs a Swing Timer to calculate its position. This is the ActionListener it calls:
This may be your problem - having each target/bullet (I assume?) responsible for keeping track of when to update itself and draw itself sounds like quite a bit of work. The more common approach is to have a loop along the lines of
while (gameIsRunning) {
int timeElapsed = timeSinceLastUpdate();
for (GameEntity e : entities) {
e.update(timeElapsed);
}
render(); // or simply repaint in your case, I guess
Thread.sleep(???); // You don't want to do this on the main Swing (EDT) thread though
}
Essentially, an object further up the chain has the responsibility to keep track of all entities in your game, tell them to update themselves, and render them.
I think what might be at fault here is your whole logic of the games setup (no offense intended), As stated in another answer you have different timers taking care of each entities movement, this is not good. I'd suggest taking a look at some gaming loop examples, and adjusting yours to this, you'll notice a great readability and performance improvement a few nice links:
http://www.java-gaming.org/index.php/topic,24220.0
http://www.cokeandcode.com/info/tut2d.html
http://entropyinteractive.com/2011/02/game-engine-design-the-game-loop/
I was initially wary of the too-many-timer theory. Instances of javax.swing.Timer use "a single, shared thread (created by the first Timer object that executes)." Dozens or even scores are perfectly fine, but hundreds typically start to become sluggish. Depending on period and duty cycle, the EventQueue eventually saturates. I agree with the others that you need to critically examine your design, but you may want to experiment with setCoalesce(). For reference, here's an sscce that you may like to profile.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
/**
* #see http://stackoverflow.com/a/11436660/230513
*/
public class TimerTest extends JPanel {
private static final int N = 25;
public TimerTest() {
super(new GridLayout(N, N));
for (int i = 0; i < N * N; i++) {
this.add(new TimedLabel());
}
}
private static class TimedLabel extends JLabel {
private static final Random r = new Random();
public TimedLabel() {
super("000", JLabel.CENTER);
// period 100 to 1000 ms; frequency 1 to 10 Hz.
Timer timer = new Timer(r.nextInt(900) + 100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TimedLabel.this.setText(next());
}
});
timer.setCoalesce(true);
timer.start();
}
private String next() {
return String.valueOf(r.nextInt(900) + 100);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
private void display() {
JFrame f = new JFrame("TimerTet");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(this));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TimerTest().display();
}
});
}
}
for painting in the Swing is better (in all cases >= Java5) use Swing Timer exclusivelly
this painting proccess required only one Swing Timer
example about bunch of Stars and one Swing Timer
Try to use one timer for all the targets.
If you have 20 targets then you will also have 20 timers running simultaneously (think about 1000 targets?). There is some expense and the most important thing is each of them is doing the similar job -- to calculate the position -- You don't need to split them. I guess it is a simple task, which will not take you a blink, even running 20 times.
If I got the point, What you want to do is trying to change the positions of all the targets at the same time. You can achieve this by changing all of them in one single method running in one thread.