Javax.Swing Timer Repeats Fine, But Actionlistener Doesn't Do Anything

Javax.swing timer repeats fine, but ActionListener doesn't do anything

There are a couple of problems here.

The first obvious thing is that you appear to be using mutable statics. This is a really bad idea and indicates (and causes!) confusion. In this particular case, one of the problems caused is that the flasher static is shared.

Flash flash = new Flash();                      //set up timer
tmr = new javax.swing.Timer(1000, new Flash());
tmr.addActionListener(flash);

We are adding two Flash actions. Ordinarily this would be bad, but just produce an undetectable "bug". The colour would be set twice.

Bring these two things together, and we have two actions without a break that perform the same toggle. Two toggles. The state does not change (although there are repaint, property change events, etc.).

So, don't use mutable statics, and keep the code clean.

swing timer doesn't work properly

An example of using a Swing Timer to pause action for xxx seconds:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

@SuppressWarnings("serial")
public class QuickTimerEg extends JPanel {
private static final int TIMER_DELAY = 2000;
private boolean buttonsWorking = true;
private JButton btn1 = null;
private JButton btn2 = null;
private Timer swingTimer;

public QuickTimerEg() {
ActionListener btnListener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
btnActionPerformed(e);
}
};
setLayout(new GridLayout(4, 4));
for (int i = 0; i < 4; i++) {
for (int j = 0; j < 4; j++) {
JButton button = new JButton(" ");
button.addActionListener(btnListener);
add(button);
}
}
}

private void btnActionPerformed(ActionEvent e) {
if (!buttonsWorking) {
return;
}
JButton button = (JButton)e.getSource();
button.setBackground(Color.blue);
button.setEnabled(false);
if (btn1 == null) {
btn1 = button;
} else {
buttonsWorking = false;
btn2 = button;
swingTimer = new Timer(TIMER_DELAY, new ActionListener() {
public void actionPerformed(ActionEvent e) {
btn1.setBackground(null);
btn2.setBackground(null);
btn1.setEnabled(true);
btn2.setEnabled(true);
btn1 = null;
btn2 = null;
buttonsWorking = true;
}
});
swingTimer.setRepeats(false);
swingTimer.start();
}
}

private static void createAndShowGui() {
JFrame frame = new JFrame("QuickTimerEg");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new QuickTimerEg());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}

public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

Why does my java timer not repeat the code at all?

Because you are not starting it inside the Event Dispatch Thread.

public class TimerTest {

static Timer timer = new Timer(10, new ActionListener() {

@Override
public void actionPerformed(ActionEvent e) {
System.out.println("test");

}

});

public static void main(String[] args) {
SwingUtilities.invokeLater(() -> { //Run in EDT
timer.start();
});
}
}

Also, have in mind that it is highly recommended (plus it helps us) to follow standard naming conventions - All class names should start with an Uppercase letter.

Why doesn't Swing Timer work?

Please read comments added :

public class Counter {

private JFrame frame;
private JPanel panel;
private JButton[][] buttons;
private int[][] isCooperating;
private Random rand;
private ActionListener actListner;
private Timer timer;
private int n = 0;

Counter(){
//do the initialization in the constructor
buttons = new JButton[50][50];
isCooperating = new int[50][50];
rand = new Random();
frame = new JFrame("GridLayout demo");
frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);
panel = new JPanel();
panel.setLayout(new GridLayout(50,50));
//no need to re construct action listener with each buildGUI run
actListner = new ActionListener() {
@Override
public void actionPerformed(ActionEvent event) {

buildGUI();
System.out.println("building gui " +n);
n++;
if(n >=3) {timer.stop();}
}
};
//no need to re construct timer with each buildGUI run
timer = new Timer(1500, actListner);
timer.setRepeats(true);
buildGUI();
}

void buildGUI() {

//remove all components from panel, if any
//otherwise you keep adding components
panel.removeAll();
for (int x = 0; x < 50; x++) {
for (int y = 0; y < 50; y++) {
int randomNumber = rand.nextInt(2); // randomly chooses between 1 and 0; for cooperating status
isCooperating[x][y] = randomNumber; // 1 is cooperating
}
}

for (int x = 0; x < 50; x++) {
for (int y = 0; y < 50; y++) {
JButton btn = new JButton();
if (isCooperating[x][y] == 0) {
btn.setBackground(Color.RED);
btn.setOpaque(true);
btn.setBorder(null);

} else if (isCooperating[x][y] == 1) {
btn.setBackground(Color.BLUE);
btn.setOpaque(true);
btn.setBorder(null);
}
btn.setPreferredSize(new Dimension(10, 10));
panel.add(btn);
buttons[x][y] = btn;
}
}

frame.add(panel);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//let layout set size by using pack
//frame.setSize(800,800);
frame.pack();
frame.setVisible(true);
}

public void solve() {

timer.start();
}

public static void main(String[]args) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new Counter().solve();
}
});
}
}

actionPerformed only runs when a Timer starts

You appear to be asking why code within an ActionListener is never called unless you turn on a Swing Timer.

The reason is, the Swing library is an event-driven GUI library, and this library has listener classes that when wired into your code properly, have call-back methods that are called in response to events that occur while the program is running, such as KeyListeners that are called only when a keypress occurs.

The same is true for ActionListeners. Their code is only called when the listener is attached to an event generator, and that generator generates an event. For an ActionListener the event generator may be a JButton (via addActionListener) and then the listener code is called when the JButton has been pressed, or it can be attached to a Swing Timer, and when the Timer is started this generates an event that repeats every timer-delay milliseconds until the timer stops.

Other issues with your code:

  • Always call the super's paintComponent method within your override. Otherwise housekeeping painting, such as cleaning of dirty pixels, is not done
  • Never delete a Graphics object given to you by the JVM, such as you are doing, since this breaks the painting chain. Only delete one that you yourself have created.
  • Your paintComponent method does more than it should, as it calls the paintDBG(...) method which changes the state of JLabel's visibility. That state change should go in the timer's ActionListener and not in the painting method.

Java Swing Timer Not Clear

So, the ActionListener you supply to the Timer is notified when the timer "ticks", so you ButtonHandler actionPerformed should look more like...

public void actionPerformed (ActionEvent e) {
// I'd be disabling the buttons here to prevent
// the user from trying to trigger another
// update...

// This is an instance field which is used by your
// listener
choice = e.getSource();

Timer timer = new Timer(1000, listener);
timer.setRepeats(false);
timer.start();
}

And your listener should look more like

public void actionPerformed (ActionEvent e) {
String tc = random(); //A method that chooses a random word.
them.setText("They chose: " + tc + "!");
if (choice == rock) {
whoWins("rock", tc); //whoWins is a method that will display a message.
} else if (choice == paper) {
whoWins("paper", tc);
} else if (choice == scissors) {
whoWins("scissors", tc);
}
yourWins.setText("Your wins: " + yw);
theirWins.setText("Their wins: " + tw);

// Start another Timer here that waits 1 second
// and re-enables the other buttons...
}

For example...

You may consider taking a look at How to use Swing Timers for more details

Updated

Start with a simple example...
public class TestPane extends JPanel {

    private JLabel label;
private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

public TestPane() {
setLayout(new GridBagLayout());
label = new JLabel();
add(label);
tick();

Timer timer = new Timer(500, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
tick();
}
});
timer.start();
}

protected void tick() {

label.setText(sdf.format(new Date()));

}
}

This just calls the tick method every half second to update the time on the JLabel...

shooting more than once cause swing timer to repeat

Two good points by both MadPrgrammer and camickr. I'm like to illustrate those points.

MapPrgogrammer: You should have a single timer which is responsible for updating the state of the game/model and scheduling the repaints, don't use multiple timers

What you should do is have a have a List of Spell or BlueSpell (whatever). What you want to do is iterate through the list in the Timer and your painting method. Something like

public class GamePanel extends JPanel {
List<BlueSpell> spells;
Timer timer = null;

public GamePanel() {
spells = new ArrayList<>();

timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
If (spells.size() > 0) {
Iterator spellIt = spells.iterator();
while (spellIt.hasNext()) {
BlueSpell spell = (BlueSpell)spellIt.next();
if (spell.collidesWithSomthing()) {
// do something, then remove from list
spellIt.remove();
} else if (spell.isOffScreen()) {
spellIt.remove(); // remove the spell from list
} else {
spell.animate(); // animate for each tick of timer
}
}
}
repaint(); // just repaint once in each timer tick after all
} // state id update.
});
timer.start();
}

protected void paintComponent(Graphics g) {
super.paintComponent(g);
If (spells.size() > 0) {
for (BlueSpell spell: spells) {
spell.drawSpell(g);
}
}
}
}

You can see the basic idea of the spells list in the Timer callback. It iterates through the timer and if the list has BlueSpells, then it will continue to check if the spells collides with something (if you need to), it checks if the spells is off the screen. With either, you'll want to remove it from the list. (Don't be tempted to use a for each loop. In this case you should use an iterator). If it is still on screen and hasn't hit anything, then the spell animates (maybe move it x position or something) then finally for each tick, the panel is repainted. Notice the if checks in both the paintComponent and Timer. There's no need to paint or iterate through the list if it is empty.

You'll want to maintain all the game's state in the single timer, not just the spell. If the spells are the only thing that animates, you may want to add a else { timer.stop() } in the timer, if the list is empty.


camickr: Don't use a KeyListener to control player movement. See Motion Using the Keyboard for more information and an alternative approach using Key Bindings

What you can do is when the space is pressed, add a new BlueSpell to the list (and start the timer [only if necessary] ). Something like

public GamePanel() {
...
InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(KeyStroke.getKeyStroke("SPACE"), "shootSpell");
getActionMap().put("shootSpell", new AbstractAction() {
@Override
public void actionPerformed(ActionEvent e) {
spells.add(new BlueSpell(...));
}
});
}

Once the spell is added to the list, the spell will begin to animate until it collides with something or is off the screen.


You can see a working example of both points in this answer.

Sample Image

Also you can see a bunch of other "animating multiple objects" answers here and here and here and here and here and here.


UPDATE

"the only question is in my repaint method do I just call the ImageIcon I created or do I have to create a new one?"

public class BlueSpell {
BufferedImage bi;
int x, y;
int width = someWidth;
int height = someHeight;
JPanel panel;

public BlueSpell(BufferedImage bi, int x, int y, panel) {
this.bi = bi;
this.x = x;
this.y = y;
this.panel = panel;
}

public void drawSpell(Graphics g) {
g.drawImage(bi, x, y, width, height, panel);
}

public void animate() {
x += 5;
}
}

You should create just the one BufferedImage in the GamePanel and when you create a new BlueSpell in the in the key binding, just pass the image to it, along with the other arguments.



Related Topics



Leave a reply



Submit