how to use a swing timer to start/stop animation - java

Could someone teach me how to use a swing timer with the following purpose:
I need to have a polygon that begins being animated(simple animation such as rotating) when I click the mouse; and stops animating when I click again.
I do not have problems understanding the way the MouseListener works, but with the actual animation. I tried simulating the animation with a while block inside the paint() method where I would draw, erase and redraw the polygon(to simulate a rotation for example), but inside the while, the applet would not listen to the clicks. It would listen only after the while. I would need the swing timer to break the while when I click the mouse.

import javax.swing.Timer;
Add an attribute;
Timer timer;
boolean b; // for starting and stoping animation
Add the following code to frame's constructor.
timer = new Timer(100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
// change polygon data
// ...
repaint();
}
});
Override paint(Graphics g) and draw polygon from the data that was modified by actionPerformed(e).
Finally, a button that start/stop animation has the following code in its event handler.
if (b) {
timer.start();
} else {
timer.stop();
}
b = !b;

This example controls a javax.swing.Timer using a button, while this related example responds to a mouse click. The latter example reverses direction on each click, but start/stop is a straightforward alteration.

The applet won't listen to clicks because the main thread (the Event Dispatch Thread, EDT) is within the while-loop and isn't listening to your clicks.
You need another thread.
(try using SwingWorker http://download.oracle.com/javase/tutorial/uiswing/concurrency/worker.html)
So, the SwingWorker will do the while-loop in the background, publishing results to make your polygon move.
And the EDT can then focus on any events (like clicks). You can then just use the click-event to kill the SwingWorker if you want to stop it.
Good luck

Related

How to start Swing timer?

Right now, the object moves a certain increment each time I manually click a button, but its actually suppose to move on its own across the screen on its own once the button is clicked. I tried calling timer.start(); various times in my code. As well as, setting up an 'if' statement in the actionPerformed method that checks for a button being pressed and then calls timer.start() as a result. But, it didn't get the object to move on its own.
Can anyone lead me in the right direction? Am I not writing the code right? Or is does this problem have something to do with java swing timer.
PS. I am new to java,
And this is part of my code :
public void actionPerformed(ActionEvent e){
if (e.getSource() == rightBtn) {
objXpos += objMoveIncrement;
direction.equals("Right");
}
if (e.getSource() == leftBtn) {
direction.equals("Left");
objXpos -= objMoveIncrement;
}
repaint();
}
}
**edit
the timer is suppose to start once a button is clicked, and the timer is what allows the object to move across the screen
this problem have something to do with java swing timer.
No.
Am I not writing the code right?
That would be the problem.
the timer is suppose to start once a button is clicked
And how does the Timer stop? What happens if you click "right" and then "down"?
Without knowing the exact requirements it is hard to give an exact solution.
So I would suggest that one solution is to just start the Timer when your program starts.
Then in the ActionListener for each button, you change the direction.
Then when the ActionListner for the Timer is invoked, you simply move the object based on the current direction and then repaint the object.
Generally you would use Key Bindings for something like this. So when you press a key you start the Timer and when you release the key you stop the Timer. Check out the Motion With Key Bindings example from Motion Using the Keyboard for a working example of this approach.

JPanel doesn't repaint if repaint() is called within JFrame code

I have a class Forest and CellularJPanel, which extends JPanel and displays Forest. I wrote a primitive code to create JFrame, Forest, CellularJPanel and add CellularJPanel to the JFrame. Next is an infinite loop, which makes Forest update and CellularJPanel repaint.
JFrame jFrame = new JFrame();
Forest forest = new Forest();
CellularJPanel forestJPanel = new CellularJPanel(forest);
jFrame.add(forestJPanel);
jFrame.pack();
//jFrame.setResizable(false);
jFrame.setLocationRelativeTo(null);
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setVisible(true);
while (true)
{
try
{
forestJPanel.repaint();
forest.update();
forest.sleep(); // calls Thread.sleep(...)
}
catch (InterruptedException e)
{
}
}
Here is a code of the CellularJPanel class:
public class CellularJPanel extends JPanel
{
private CellularAutomata cellularAutomata;
public CellularJPanel(CellularAutomata cellularAutomata)
{
super();
this.cellularAutomata = cellularAutomata;
setPreferredSize(this.cellularAutomata.getDimension());
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D graphics2D = (Graphics2D)g;
cellularAutomata.draw(graphics2D);
}
}
If I use above code within main() method, then everything works fine,
CellularJPanel repaints, paintComponent() is called normally.
If I paste the same code to UI JFrame button click event method, then new JFrame shows and even displays initial state of the Forest, because paintComponent is once called, when jFrame.setVisible(true) is called. Then while loop is being executed, but CellularJPanel doesn't repaint, paintComponent is not called. I have no idea why, maybe I should use SwingUtilities.invokeLater(...) or java.awt.EventQueue.invokeLater somehow, but I've tried them and it didn't work, I'm doing something wrong.
Any suggestions?
P.S.
My target is to display CellularJPanel within the same UI JFrame, from which button was clicked. But even if I add this panel to main UI JFrame, it doesn't work.
Your problem is having a while(true) on the Event Dispatch Thread which will block anything related to UI because UI events aren't getting handled anymore.
The event dispatch thread (a single thread) works down a queue of UI event messages, until it handles the one where your while(true) loop is running. It then blocks any further processing because there's an infinite loop on it. Calling SwingUtilities.invokeLater from that loop won't help because it posts an event to the event dispatch thread, which is blocked in the while(true) loop.
So remove that loop, instead use a javax.swing.Timer to time your events. In the timer event, change the state of your UI and call for a repaint. The timer event will be synchronized with the UI thread, so changing state of UI components is allowed.
There is a single UI thread that draws things - it's also the one that handles button clicks. In swing, this is called the event dispatch thread. If The UI thread is busy running a while loop, it can't paint.
You can quickly verify this by making your button click handler run only a single iteration of your loop (without the sleep): forest.update(); forestJpanel.repaint();
You can auto update from a separate thread (like Timer) that calls repaint/sleep in a loop.

Make an ActionListener commit a change to a JButton before it has finished executing the entire ActionListener

In Java, I'm using an ActionListener for an array of JButtons. I would like for an earlier part of the ActionListener to set a new ImageIcon to a JButton, that change to be displayed immediately, then near the end of the ActionListener to set the JButton's ImageIcon back to null after a second long delay.
My problem is that none of the changes that happen to the JButton get displayed in the GUI window that it is set in until the ActionListener is completely finished, making the change in the JButton's ImageIcon unnoticeable. Is there any way to make an ActionListener commit a change to a JButton before it has finished executing the entire ActionListener, or should I be going about this differently?
The reason this is happening:
Swing repaints the buttons on the same thread (EDT) as the ActionListener is ran on. Hence if it is executing you ActionListener it cannot repaint since the thread is busy - as simple as that. You may have noticed that while your action listener is executing you also can't properly move your frames around etc. (GUI freezes up).
The solution:
Move heavy processing outside the EDT. It doesn't belong there anyway. As you could have guessed - use a background thread/thread pool for that. A good guide to it is Swing tutorial for concurrency
Notes:
As portrayed in the guide you do not want to modify components outside the EDT. As such the easiest strategy is to make a Runnable to execute on a background thread, start it, change the picture on the button and return without waiting for the task to finish.
public void actionPerformed(ActionEvent ae) {
Runnable task = new Runnable() {..};
executor.execute(task);
button.setIcon(newIcon);
return;
}
Note that this doesn't lock up the EDT for the task, hence allowing Swing to change the picture immediately.
This of course means that the user has no idea if the task has finished or not (And if there were any exceptions)! It is in the background after all! Hence there is an extra state of your execution: GUI is responsive and non-frozen, button is changed, but task is still running. In most applications this may be a problem (the user will spam the button or your background tasks may interleave). In that case you may want to use a SwingWorker to have a "processing" state as well.
public void actionPerformed(ActionEvent ae) {
new TaskWorker().execute();
button.setIcon(loadingIcon); //Shows loading. Maybe on button, maybe somewhere else.
return;
}
private class TaskWorker extends SwingWorker<Void, T> {
public Void doInBackground() {
//Do your task in the background here
}
protected void done() {
try {
get();
button.setIcon(doneIcon);
catch (<relevant exceptions>) {
button.setIcon(failedIcon);
}
}
}
Here done() is called on the EDT when doInBackground() is finished.
You could create a new Thread or a Thread from a Thread pool if you have one. And with that let the task work on a seperate Thread so that the ActionListener returns immediatly. And then in the other Thread you do your code and repaint the button. This is by the way a threory I'm not sure if it will work.

swing - short-time highlight of a component

I have a JTable, where a user can select a single row. If that happens, i want to "highlight" another part of the page for a short time to indicate that this is the part of the page that changed after the user interaction.
So my question is: What's the best way to achieve this? At the moment i did it by setting the background color of that panel and starting a SwingWorker which sets the Color back after a short delay. It works as intended, but is it a good idea to use a SwingWorker like that? Are there any drawbacks to that approach? How would you solve this?
Thanks in advance.
I guess a Swing Timer would be a better option as it reuses a single thread for all scheduled events and executes the event code on the main event loop. So, inside your SelectionListener code you do:
// import javax.swing.Timer;
final Color backup = componentX.getBackground();
componentX.setBackground(Color.YELLOW);
final Timer t = new Timer(700, new ActionListener() {
public void actionPerformed(ActionEvent e) {
componentX.setBackground(backup);
}
});
t.setRepeats(false);
t.start();
I recommend a swing Timer (javax.swing.Timer). (do NOT use the Timer class in Java.util)
This is where you make the timer:
Timer t = new Timer(loopTime,actionListener)//loopTime is unimportant for your use of this
t.setInitialDelay(pause)//put the length of time between starting the timer and the color being reverted to normal
t.setRepeats(false);//by default, timer class runs on loop.
t.start();//runs the timer
It probably makes sense to hold on to a reference to the timer, and then just call t.start when you need it.
You need to implement an action listener to handle the timer events. I can edit this if you don't know how to do that, but as you are already doing stuff with Swing I figure it shouldn't be a problem.

Javax Swing Timer Help

I am having some problems concerning starting javax.swing.Timer after a mouse click. I want to start the timer to perform some animation after the user clicks on a button but it is not working.
Here are the code snippets:
public class ShowMe extends JPanel{
private javax.swing.Timer timer;
public ShowMe(){
timer = new javax.swing.Timer(20, new MoveListener());
}
// getters and setters here
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
// some code here to perform the animation
}
}
}
This is the class which contains a button so that when the user clicks on the button the timer starts to begin the animation
public class Test{
// button declarations go here and registering listeners also here
public void actionPerformed(ActionEvent e) {
if(e.getSource() == this.btnConnect){
ShowMe vis = new ShowMe();
vis.getTimer().start();
}
}
}
I want to start the timer to begin the animation but it is not working.
Need help how to make a timer start after button click.
Thanks.
You must call the start() method of the timer to start it.
public ShowMe(){
timer = new javax.swing.Timer(20, new MoveListener());
timer.start();
}
EDIT:
I have not seen that start() is being called in the Test class...
Next step would be to add some logging/printing to the MouseListener class to check if it is being called or not
private class MoveListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("MouseListener activated"); // TODO delete this line
// some code here to perform the animation
}
}
If it's running (I can't find any reason why not in the posted code), the problem is as Ash wrote above:
You created a new instance assigned to vis and started its Timer, but you have not added that instance to any visible container.
(maybe you added another instance of ShowMe earlier in the code...)
Some things to try:
Check that your panel is visible, e.g. make the background color red.
Check that the animation is being updated. For example, if you are animating by drawing different frames in a paint() method, then you will need to call repaint() in your timer, after updating the variables controlling animation. Alternatively, if animation is done by changing layout properties (e.g. to move a component around) then a call to validate() will be needed.
Using swing timer can get you started, but it's really the bare underpinnings. There are also libraries avaialbe that will allow you to go further with less effort:
animated transitions
Trident animation library
I know this question is a bit old, but I don't think you got an answer.
I believe the problem is that the ShowMe class and its Timer is being garbage collected, and hence fails to do what you think it should.
You are creating a new local ShowMe variable that goes out of scope as soon as the actionPerformed method completes. The Timer and its ActionListener are local to the ShowMe class instance, so when the actionPerformed method completes, they are also available for GC.
I'm not sure what the ShowMe class is doing. It appears to be a JPanel, so I assume it is something you want to display. It sounds like in your Test class (or real class), it might be better to have a ShowMe data member that you can just call start one when the button is clicked, instead of creating a new one every time.
Your usage of the Timer class seems to be correct. Maybe the problem lies in the MoveListener.
Did you remember to use the paintImmediately() method to repaint your animation?
If you use just repaint() you won't see a smooth animation, since repeated calls to repaint() are reduced to one call.

Categories

Resources