0

I've got a strange glitch going on with a little program I'm working on. It's a scorekeeper/timer for games like Pictionary or Charades.

I'll start with what the program is supposed to do. Clicking the Start Timer button disables the Set Time, Reset Timer, and Start Timer buttons, then begins a countdown of the timer. The countdown itself is handled in a thread so that it can be stopped with the Timer Stop button. All of those functions are working fine, except for a weird little graphics glitch that happens when I click Start:

enter image description here

You can see that a 'ghost' of the Reset Timer button appears over the numbers, and a little bit of the '6' has appeared over the Reset Timer button. This is usually what happens, but sometimes it's a ghost of the Start Timer that appears, or sometimes it's nothing at all. This 'ghost' goes away on the next iteration of the countdown loop and its subsequent repainting of the timer.

That being said, I've tracked the problem down to the disabling of the three buttons. When I remove that from my code, there are no glitches. Perhaps that whole bottom JPanel needs repainted after disabling the buttons? I wouldn't think that would be necessary. At any rate, I'll paste the pertinent code below. I hope you can offer some suggestions as to how to prevent this from happening.

public void disableButtons() {
    timerStartButton.setEnabled(false);
    resetTimerButton.setEnabled(false);
    setTimeButton.setEnabled(false);
}

public void enableButtons() {
    timerStartButton.setEnabled(true);
    resetTimerButton.setEnabled(true);
    setTimeButton.setEnabled(true);
}

public void timerThread(){
    disableButtons();
    timerRunning = true;
    Thread t = new Thread(new Runnable(){
        @Override
        public void run(){
            timerInterrupted = false;
            while(timerRunning && timer > 0){
                try {
                    timerLabel.setText(Integer.toString(timer));
                    timerLabel.paintImmediately(timerLabel.getVisibleRect());
                    Thread.sleep(1000);
                } catch (InterruptedException ex) {
                    Logger.getLogger(ScoreTime.class.getName()).log(Level.SEVERE, null, ex);
                }

                timer--;                
            }

            timerRunning = false;

            if (!timerInterrupted) {
                timer = 0;
                timerLabel.setText(Integer.toString(timer));
                if (soundOn) {
                    try {
                        //load the buzzer sound as a clip
                        AudioInputStream buzzerAudioIn = AudioSystem.getAudioInputStream(buzzerSoundFile);
                        Clip buzzerClip = AudioSystem.getClip();
                        buzzerClip.open(buzzerAudioIn);   
                        buzzerClip.start();
                        Thread.sleep(1500);
                    } catch (UnsupportedAudioFileException | IOException | LineUnavailableException | InterruptedException ex) {
                        Logger.getLogger(ScoreTime.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                else {
                    try {
                        Thread.sleep(1500);
                    } catch (InterruptedException ex) {
                        Logger.getLogger(ScoreTime.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                timer = userTime;
                timerLabel.setText(Integer.toString(timer));
                enableButtons();
            }
        }
    });
  t.start();
}
  • 2
    All that stands out to me is that your thread is updating UI components directly. That should be handled through EventQueue.invokeLater(). – Steve11235 Jan 02 '18 at 19:01
  • Hmm...I haven't yet used EventQueue.invokeLater(). I'm still bungling my way through learning about threads. I'll have to look for some examples on how that works. Will the numbers in the timer still be updated real-time if I use that? – Jeremy Wilson Jan 02 '18 at 19:07
  • See https://docs.oracle.com/javase/tutorial/uiswing/concurrency/ . – VGR Jan 02 '18 at 19:19

1 Answers1

1

Use a Swing Timer instead of a separate Thread.

The Timer will generate an event at your specified time interval. The code will execute on the Event Dispatch Thread. Then you just set the text of the label. There is no need for the paintImmediately(...).

The Timer also support stop/start methods so it is easy to control.

Read the section from the Swing tutorial on How to Use Swing Timers for more information.

You can also check out: Program freezes during Thread.sleep() and with Timer for a simple example that simply updates the time every second.

camickr
  • 321,443
  • 19
  • 166
  • 288