I'm using a Swing Timer to execute animations in my program. In many instances, there are several different calls made to the animate() method at once, with a separate Timer created for each. I know that, because of the way Swing timers are designed, these all get executed together - all my animations occur at the same time. However, there are some instances where I need to wait for one animation to complete to execute another.
Is there a way to make Swing timers execute sequentially - one after the other, rather than all at once? Or is there an alternative mechanism to the Swing timer that might better match my use case?
EDIT: I'm afraid I oversimplified my use case a bit. #peeskillet's suggestion would work perfectly if I knew at each "scene transition" what animations would need to be executed, or if the same sequence of animations occurred each time. Unfortunately that's not the case -- each transition requires a different set of animations, with different sets of components moving onto, off of and around on the panel.
What I want is to execute the animations of items off the screen first, and then (after that completes) animate the components on the screen. It's not a problem to distinguish between these "types" of animations at runtime - they're initiated from different methods, and thus its easy to imagine adding them to two different "queues" - a queue of outgoing items and a queue of incoming items. Having done so, I could then implement the basic strategy of calling a
That said - that all only makes sense to me intuitively, heuristically - I haven't figured out how to implement it in practice. What would those "queues" actually be, and what class would hold and later execute them?? Presumably one that implements Runnable, creating a second thread that can execute the animations with tighter control on how they proceed? Or does the event-dispatch thread give me the ample control here, if only I fully grasped how to use it? In which case - please help me do that.
(PS I realize that I've changed the question significantly here, essentially turning it into a new question, and that #peetskillet answered it as previously worded perfectly well, so I accepted that answer and posted a new question here.
"Is there a way to make Swing timers execute sequentially - one after the other, rather than all at once? "
Just use a `boolean of some sort, telling when the first timer when it should stop and when the second timer should start. Something like
Timer timer1 = new Timer(delay, null); <---- initialize
Timer timer2 = new Timer(delay, null);
boolean something = false;
public Constructor() {
timer1 = new Timer(delay, new Action Listener(){
public void actionPerformed(ActionEvent e) {
if (something) { ------
timer2.start(); |
timer1.stop(); |---- some code should lead to
} esle { | `something` being true. Maybe
animateFirstSomething(); | another if statement inside the
} | else. Like if x equals y
} ------ something = true, else,
}); animateFirstSomething()
timer1.start();
timer2 = new Timer(delay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
animationSecondSomething();
}
});
}
Here's simple example
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestTwoTimers extends JPanel {
int rectOneX = 0;
int rectTwoX = 0;
Timer timer1 = new Timer(100, null);
Timer timer2 = new Timer(100, null);
boolean rectOneGo = true;
public TestTwoTimers() {
timer1 = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (rectOneGo) {
if (rectOneX >= 225) {
timer2.start();
timer1.stop();
} else {
rectOneX += 10;
repaint();
}
}
}
});
timer2 = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (rectTwoX < 225) {
rectTwoX += 10;
repaint();
} else {
timer2.stop();
}
}
});
final JButton button = new JButton("Start First Timer");
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
timer1.start();
}
});
setLayout(new BorderLayout());
add(button, BorderLayout.PAGE_END);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(rectOneX, 50, 75, 75);
g.setColor(Color.BLUE);
g.fillRect(rectTwoX, 150, 75, 75);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
public void run() {
JFrame frame = new JFrame("Double Timers");
frame.add(new TestTwoTimers());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Related
This question already has answers here:
Why does Java not see the updated value from another thread?
(5 answers)
Closed 2 years ago.
I have updated this ask, i created a simple program with the following problem.
This is the version code that work:
package com.company;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main {
static boolean readytoconnect = false;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
JPanel panel = new JPanel();
frame.add(panel);
panel.setBounds(0, 0, frame.getWidth(), frame.getHeight());
panel.setBackground(Color.lightGray);
panel.setLayout(null);
JButton connect = new JButton("Connect");
panel.add(connect);
connect.setBounds(200, 200, 80, 40);
connect.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
readytoconnect = true;
}
});
Thread threadtoconnect = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
System.out.println("WAITING TO CONNECT");
if (readytoconnect) {
System.out.println("CONNECTED");
}
}
}
});
threadtoconnect.start();
}
}
This is the version code that don't work:
package com.company;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class Main {
static boolean readytoconnect = false;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
frame.setVisible(true);
JPanel panel = new JPanel();
frame.add(panel);
panel.setBounds(0, 0, frame.getWidth(), frame.getHeight());
panel.setBackground(Color.lightGray);
panel.setLayout(null);
JButton connect = new JButton("Connect");
panel.add(connect);
connect.setBounds(200, 200, 80, 40);
connect.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
readytoconnect = true;
}
});
Thread threadtoconnect = new Thread(new Runnable() {
#Override
public void run() {
while (true) {
//System.out.println("WAITING TO CONNECT");
if (readytoconnect) {
System.out.println("CONNECTED");
}
}
}
});
threadtoconnect.start();
}
}
the difference between these is that in the first, which works, there is the output before the 'IF' and in the second, which does not work, there is no output.
Android does not support the Swing library, you must use functions from the Android SDK to get input etc.
I would advise looking up "OnClickListener" or the like in the Android Docs
First, I will point out a couple of problems with both versions of the code:
You appear to have two threads reading and updating a shared readytoscan field without any obvious synchronization. Unless that field is declared as volatile, that is liable to lead to memory anomalies.
The code in the run() method that assigns to readytoscan should probably be assigning false to it ... not true.
The code will leak open sockets!
You are creating an anonymous subclass of Thread which overrides the run() method. It is more conventional to create an instance of Thread and pass it a Runnable ... or a lambda.
I don't think that any of the above explain the strange behavior that you reported though. The only cause (that I can see) for the inner loop can run just once is if the run() method is crashing. Specifically, something in the inner loop must be throwing an unchecked exception.
To track this down, you could add an extra catch (Throwable ...) to the try and log the exception that it catches. If that doesn't yield an explanation for the behavior, try creating a minimal reproducible example. For example, you could simplify the plain Java version so that it didn't use Swing ... which I don't think is implicated in the problem.
My guess is that the problem is coming from the new Socket(...) call. For example, it could be a SecurityException.
The problems I described above are for both Android and Java with the use of the Swing library, so let's say Java is the problem in general.
It is a bad strategy to assume that the bug is in Java or Android or Swing ... just because you can't find a cause in your code. Think of it this way: the Java / Android platforms work for millions of other developers and run on billions of devices (I am guessing these numbers). They have worked for (in the Java case) 20+ years. The chances that there is something fundamentally wrong with the Java AND Android platforms that other people haven't already found, reported and fixed is vanishingly small.
I was given some demo code during class that worked on the Windows computer in the lab but doesn't work the same way on my 2010 MacBook, using Sierra.
Making the changes suggested in Java Graphics Not Displaying In OS X didn't fix my problem. I've also tried resizing the window, which changes the animation a bit -- it pops up intermittently after the resize. If I increase the Thread.sleep() time and resize, then the animation improves, but it still is choppy.
Why doesn't the code work on my MacBook and how can I get it to work?
The original code (which works on Windows 10 but not my Mac):
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class GraphicsTestA extends JFrame{
int x1 = 60;
int x2 = 150;
public static void main(String [] args){
GraphicsTestA gta = new GraphicsTestA();
gta.animate();
}
private void animate()
{
while(true)
{
for(int i=0; i <100; i ++)
{
x1 = x1 + 1;
x2 = x2 - 1;
try
{
Thread.sleep(100);
}
catch(Exception ex)
{}
repaint();
}
x1 = 60;
x2 = 150;
}
}
public GraphicsTestA() {
setSize(200,200);
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
this.validate();
}
public void paint(Graphics g) {
super.paint(g);
g.drawString("Hello World",80,80);
g.drawLine(x1,60,x2,150);
g.drawRect(100,40,30,30);
}
}
So a number of issues:
Extending from JFrame
It's generally discouraged to extend directly from a top level container like JFrame, as you rarely add any new re-usable functionality to it and it locks you into a single use case.
It's also a compound component, that is, there are a number of components which reside on it, which one of the main causes of issues for you.
Instead, start with something like JPanel and then add it to any other container you need.
Overriding paint
As a general rule, you should avoid overriding paint, it's to low level for most situations, and in the case of top level containers, just messes things up. Instead, start with paintComponent
See Performing Custom Painting and Painting in AWT and Swing for more details
Thread violations
As has already been pointed out, Swing is not thread safe AND it's single threaded.
This means that you should never update the UI or any values the UI relies on from outside the Event Dispatching Thread. It also means you should never perform any long running or blocking operations inside the context of the EDT either.
In simple cases, a Swing Timer is more then powerful enough to do the job. See How to use Swing Timers for more details
Putting it all together...
So, putting all that together might end up looking something like...
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import static javax.swing.JFrame.EXIT_ON_CLOSE;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class GraphicsTestA {
public static void main(String[] args) {
new GraphicsTestA();
}
public GraphicsTestA() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
int x1 = 60;
int x2 = 150;
int loops = 0;
public TestPane() {
Timer timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
x1 = x1 + 1;
x2 = x2 - 1;
loops++;
if (loops > 100) {
x1 = 60;
x2 = 150;
((Timer) e.getSource()).stop();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawString("Hello World", 80, 80);
g2d.drawLine(x1, 60, x2, 150);
g2d.drawRect(100, 40, 30, 30);
g2d.dispose();
}
}
}
In swing, you're not supposed to invoke swing things outside of the swing thread. Windows lets you get away with some slop, but Java on the mac is super picky about it.
In this code, the constructor makes swing calls, but it's invoked from the main thread. You can try to split that out so the constructor is called by the Swing thread, but then there are problems with the animate method.
The animate method shouldn't be called by the Swing thread because it's calling sleep(), and pausing the swing thread is a bad idea™. However, it's also calling repaint(), which is a swing call and that needs to be called by the Swing thread.
I suggest you use EventQueue.invokeLater to construct your window, and then figure out how to use Swing Workers or some other appropriate mechanism to do your animation.
I've got a problem here. The program below creates an animation ( a circle going from one point to another). The animation should start when you click the button.
The problem is that when I click the button, I cannot see the circle sliding. It just appears, after some time, in other place.
It's seems interesting to me(as a beginner), that if I do not use a button, and call the moveIt() method inside go(), I get a normal animation (I can see the circle sliding).
Can you give me, please, some suggestions on this problem ?
Thanks.
Here's the program:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.*;
final public class MiniMiniMusicPlayer1 implements ActionListener
{
JFrame frame;
DrawPanel drawPanel;
private int X = 7;
private int Y = 7;
public static void main(String... args)
{
new MiniMiniMusicPlayer1().go();
}
private void go()
{
frame = new JFrame("Player");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawPanel = new DrawPanel();
frame.getContentPane().add(BorderLayout.CENTER, drawPanel);
JButton buttonStart = new JButton("Start animation");
frame.getContentPane().add(BorderLayout.SOUTH, buttonStart);
buttonStart.addActionListener(this);
frame.setResizable(false);
frame.setSize(300, 300);
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
#Override
public void actionPerformed(ActionEvent ev){
moveIt();
}
class DrawPanel extends JPanel
{
public void paintComponent(Graphics g)
{
g.setColor(Color.BLACK);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.setColor(Color.RED);
g.fillOval(X, Y, 50, 50);
}
}
private void moveIt()
{
for (int i = 0; i < 100; ++i)
{
X++;
Y++;
try
{
Thread.sleep(10);
}
catch (Exception e)
{
e.printStackTrace();
}
frame.repaint();
}
}
}
Swing and AWT work using an Event Dispatch thread. That thread is responsible for handling all events (running the event handlers, such as a button's actionPerformed) and repaint requests.
When you run your moveIt from go, it is ran by the main thread. It creates repaint requests, and the event dispatch thread, which runs concurrently with the main thread, dispatches them. Thus, you can see the animation.
But when you call moveIt from an event handler, it is ran by the event dispatch thread itself. Thus, the whole loop is performed, and all the repaint requests are queued, but the thread is busy and can't dispatch them until it's finished the loop.
When it is done with the loop, it dispatches the repaints (or rather, it dispatches just one, there is no need for it to repaint 100 times). The repaint is done given the final state of X and Y.
If you want to see animations, you should make sure you are not running them inside the EDT. You can use a javax.swing.Timer object for this, for example.
Remember never to run a long operation inside an event handler. It holds up the EDT and makes your GUI unresponsive. Never use Thread.sleep() in an event handler. If your long task is supposed to do something on a periodic basis, use a Timer. If it's supposed to do some big task like load from database, use SwingWorker.
I have:
A JFrame with a JButton on it.
A separate Canvas subclass to show animations.
And I wish to, at the press of the JButton bring up a new JFrame displaying the Canvas subclass as it animates.
The problem I face right now is that the new JFrame appears, however it doesn't get a chance to render anything and the JButton on the main frame stays depressed. The logic I figure behind this is that the EDT hasn't finished doing it's jobs such as showing the JButton as released and so does not get a chance to run the animation method and ends up in deadlock.
This logic treated me well in the past as I made this work by creating a new thread, but having learned more about Java, threads and Swing lately I've come to know that all Swing related events must be handled on one thread: the EDT.
This confuses me as to how I got it working before but lead me to believe that using invokeLater would help the problem; as the job of making the JFrame visible and showing animation would be placed at the end of the queue allowing the JButton to unrelease etc. I've had no luck however; have I completely misunderstood something?
Thanks!
(Also please no comments on my use of the Canvas class as opposed to JPanel, I have my reasons).
Sample code:
Test5 (class with main method).
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*
public class Test5 {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new Test5().setup();
}
});
}
private void setup() {
JFrame frame = new JFrame("Test");
JButton button = new JButton("Click here");
button.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
newFrame();
}
});
}
});
frame.getContentPane().add(button);
frame.pack();
frame.setVisible(true);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
private void newFrame() {
JFrame newFrame = new JFrame("The new frame");
newFrame.setVisible(true);
newFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
CanvasClass canvas = new CanvasClass();
newFrame.getContentPane().add(canvas);
newFrame.pack();
canvas.runAnimation();
}
}
CanvasClass (Canvas subclass)
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
public class CanvasClass extends Canvas {
int x;
public CanvasClass() {
setSize(new Dimension(550,550));
this.x = (int) (Math.random() * 255);
}
//#Override
public void paint(Graphics g) {
g.setColor(new Color(x, x, x));
g.fillOval(0,0,500,500);
}
void runAnimation() {
while (true) {
randomise();
repaint();
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
void randomise() {
x = (int) (Math.random() * 255);
}
}
You actualy invoke it in EDT but it's blocked in the canvas.runAnimation();
Place the code to be executed in a separate Thread (where you can call sleep) but call the repaint() in SwingUtilities.invokeLater()
Or even better to define a javax.swing.Timer and call the runAnimation() in the Timer's actionPerformed()
UPDATE:
int delay = 20; //milliseconds
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
canvasInstance.randomise();
canvasInstance.repaint();
}
};
new Timer(delay, taskPerformer).start();
to be called instead of the runAnimation()
I would like to create a JButton that changes its text periodically after the first click. I'm not really familiar with Swing library. What would be a good starting point? May I update its text without an action?
Thank you.
for all periodical events in Swing I only suggest javax.swing.Timer
output by using Timer should be, for example
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.Timer;
public class CrazyButtonTimer {
private JFrame frame = new JFrame(" Crazy Button Timer");
private JButton b = new JButton("Crazy Colored Button");
private Random random;
public CrazyButtonTimer() {
b.setPreferredSize(new Dimension(250, 35));
frame.getContentPane().add(b);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
javax.swing.Timer timer = new Timer(500, new TimerListener());
timer.setInitialDelay(250);
timer.start();
}
private class TimerListener implements ActionListener {
private TimerListener() {
}
#Override
public void actionPerformed(final ActionEvent e) {
Color c = b.getForeground();
if (c == Color.red) {
b.setForeground(Color.blue);
} else {
b.setForeground(Color.red);
}
}
}
public static void main(final String[] args) {
java.awt.EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
CrazyButtonTimer crazyButtonTimer = new CrazyButtonTimer();
}
});
}
}
If you to change it on every fixed amount of time then you can use Swing Timer or Thread to do this. But for this you have to listen at least one action so that you can initialize and start it.
You can also use TimerTask class from java.util like follow:
java.util.TimerTask timerTask = new java.util.TimerTask() {
#Override
public void run() {
//change button text here using button.setText("newText"); method
}
};
java.util.Timer myTimer = new java.util.Timer();
myTimer.schedule(timerTask, 3 * 1000, 3* 1000); // This will start timer task after 3 seconds and repeat it on every 3 seconds.
I suggest you to create a timer (here you can find some doc)
Timer timer = new Timer(100,this);
Your class has to extend action listener ed implements the following method which allow you to change the text of your JButton(I called it ``button).
public void actionPerformed(ActionEvent e) {
if(e.getSource.equals(timer)){
button.setText("newText");
}
}
Luca
All the other answers fail to mention how to update non-periodically. If you need it to update irregularly, you can make a method in your GUI class called something like: updateButton(); and just call that every time you want it to change your text.
public void updateButton(String newText)
{
Button.setText(newText);
}
Just thought I'd add this in case someone wanted to set it irregularly.
If you want to change it periodically (e.g. every 5th second) you could create a new Thread which sets the text of the button to the desired value and repaints it (if necessary).