2

I'm trying to realize a certain pattern for a little DigitalWatch class with minimal interface in java (just a practice).

It has 2 buttons: a mode button and an increment button.

The mode button has a listener which triggers the change of my program's state between "SetHours,SetMinutes,DisplayTime" (which exist as objects due to the pattern, the objects call the specific state-dependend methods in my DigitalWatch class).

My method displayTime() is something like this:

void displayTime()
{
    while (pressed==false)
    {
        try
        {
            display.setText(String.format("%02d:%02d", hours, minutes));
            display.repaint();
            Thread.sleep(1000);
            minutes = minutes + 1;
            if (minutes > 59)
            {
                minutes = 0;
                hours = hours + 1;
            }
            if (hours > 23)
            {
                hours = 0;
            }
            display.setText(String.format("%02d:%02d", hours, minutes));
            display.repaint();
        } catch (Exception e)
        {
            break;
        }
    }
    display.setText(String.format("%02d:%02d", hours, minutes));
    display.repaint();
}

But it seems that while this loop is active the button looses its clickability.

So once the "time counts" i am stuck with this state and am no longer able to change the mode. Is there a good way/best practice to maintain the clickability of my modebutton?

Jonas
  • 121,568
  • 97
  • 310
  • 388
Wolfone
  • 1,276
  • 3
  • 11
  • 31
  • Possible duplicate of [Prevent Swing GUI locking up during a background task](http://stackoverflow.com/questions/940913/prevent-swing-gui-locking-up-during-a-background-task) – Raniz Dec 29 '16 at 08:04
  • You are blocking your Event dispatch thread. I.e. your gui thread. You need to put that code in a separate thread. Updating the gui elements should be done on the edt though. So you need to split this up. – Fildor Dec 29 '16 at 08:11
  • That leads me to a probably stupid question: where is the edt in my case? – Wolfone Dec 29 '16 at 08:25

1 Answers1

1

In Swing, all events are dispatched on a single thread and since your loop never exits it will block the UI thread and prevent any future events from being handled.

What you need to do is execute your loop in a separate thread and control the state of that thread with the button. We also need to make sure that we run any UI-code on the UI-thread, so you'll need to switch back and forth between your timer thread and the UI thread.

Since you're running a recurring task every second, ScheduledExecutorService.scheduledAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) is a good fit.

Something like this:

private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
private ScheduledFuture<?> timerFuture = null;

void toggleTimer()
{
    if (timerFuture != null)
    {
        // Stop the timer
        timerFuture.cancel();
        timerFuture = null;
    } else
    {
        // Start the timer on the separate thread, run it every second
        timerFuture = executor.scheduleAtFixedRate((Runnable) () -> {
            minutes = minutes + 1;
            if (minutes > 59)
            {
                minutes = 0;
                hours = hours + 1;
            }
            if (hours > 23)
            {
                hours = 0;
            }
            // Update the UI, this needs to be done on the UI thread
            SwingUtilities.invokeLater(() -> {
                display.setText(String.format("%02d:%02d", hours, minutes));
                display.repaint();
            });
        }, 0, 1, TimeUnit.SECONDS);
    }
}
Raniz
  • 10,882
  • 1
  • 32
  • 64
  • Thanks for your fast answer! That seems very elaborate and to be honest i will need some time to unerstand what is really going on underneath (i'm a beginner). I found something similar to my situation here: http://stackoverflow.com/questions/12821220/how-to-stop-a-loop-with-a-button?rq=1 And because i'm not familiar with threads i'm not sure about the difference. I think the threads in your answer are created "under the coat" by "scheduleAtFixedRate" can you explain the qualitative difference to the second answer in the mentioned post? (with the volatile variable) – Wolfone Dec 29 '16 at 08:42
  • Yes, your problem is the same as in the question you linked (you may consider marking this question as a duplicate of that one). The difference in the answers is indeed that the executor service is handling the thread(s) for you so you only need to think in terms of tasks (Runnables/lambdas, ScheduledFuture) and not manage, start, or stop threads. – Raniz Dec 29 '16 at 08:47
  • Ok, thanks; do you know by heart if there are volatiles used under the hood in your answer? – Wolfone Dec 29 '16 at 08:53
  • Your `minutes` and `hours` fields should either be volatiles or `AtomicInteger/Long` to ensure that the correct value is read when updating the UI since it's performed on different threads. What the executor service is using is up to the JVM :) – Raniz Dec 29 '16 at 08:56
  • Ok, thanks again for your time and the ideas to investigate things further! :) (Unfortunately it seems i cannot mark as duplicate due to my low user-lvl or due to the fact that it is already marked as duplicate of another question, i'm not sure.) – Wolfone Dec 29 '16 at 09:00