I'm learning to implement a count down timer with GUI showing the time reduction. I'm using Groovy's @Bindable
in the hope that the change of time reduction can be displayed automatically in the corresponding UI label.
The reduction of the count-down time value is done in the timer thread, separated from the UI thread. The countdown timer is not being updated in the UI, however.
What's the appropriate way to have the count-down time in the UI update properly?
import groovy.swing.SwingBuilder
import java.awt.FlowLayout as FL
import javax.swing.BoxLayout as BXL
import javax.swing.JFrame
import groovy.beans.Bindable
import java.util.timer.*
// A count-down timer using Bindable to reflcet the reduction of time, when the reduction is done in a TimerTask thread
class CountDown {
int delay = 5000 // delay for 5 sec.
int period = 60*1000 // repeat every minute.
int remainingTime = 25*60*1000
// hope to be able to update the display of its change:
@Bindable String timeStr = "25:00"
public void timeString () {
int seconds = ((int) (remainingTime / 1000)) % 60 ;
int minutes =((int) (remainingTime / (1000*60))) % 60;
timeStr = ((minutes < 9) ? "0" : "") + String.valueOf (minutes) + ":" + ((seconds < 9) ? "0" : "") + String.valueOf (seconds)
}
public void update () {
if (remainingTime >= period)
remainingTime = (remainingTime - period)
// else // indicate the timer expires on the panel
// println remainingTime
// convert remainingTime to be minutes and secondes
timeString()
println timeStr // this shows that the TimerTaskCountDown thread is producting the right reduction to timeStr
}
}
model = new CountDown()
class TimerTaskCountDown extends TimerTask {
public TimerTaskCountDown (CountDown modelIn) {
super()
model = modelIn
}
CountDown model
public void run() {
model.update() // here change to model.timeStr does not reflected
}
}
Timer timer = new Timer()
timer.scheduleAtFixedRate(new TimerTaskCountDown(model), model.delay, model.period)
def s = new SwingBuilder()
s.setVariable('myDialog-properties',[:])
def vars = s.variables
def dial = s.dialog(title:'Pomodoro', id:'working', modal:true,
// locationRelativeTo:ui.frame, owner:ui.frame, // to be embedded into Freeplane eventually
defaultCloseOperation:JFrame.DISPOSE_ON_CLOSE, pack:true, show:true) {
panel() {
boxLayout(axis:BXL.Y_AXIS)
panel(alignmentX:0f) {
flowLayout(alignment:FL.LEFT)
label text: bind{"Pomodoro time: " + model.timeStr}
}
panel(alignmentX:0f) {
flowLayout(alignment:FL.RIGHT)
button(action: action(name: 'STOP', defaultButton: true, mnemonic: 'S',
closure: {model.timeStr = "stopped"; vars.ok = true//; dispose() // here the change to model.timeStr gets reflected in the label
}))
}
}
}
Yes, it can. Nutshell: call setTimeStr
instead of setting the property directly.
Bypassing the setter meant that none of the code added by @Bindable
was being executed, so no property change notifications were being sent.
Other edits include minor cleanup, noise removal, shortening delay to speed debugging, etc.
import groovy.swing.SwingBuilder
import java.awt.FlowLayout as FL
import javax.swing.BoxLayout as BXL
import javax.swing.JFrame
import groovy.beans.Bindable
import java.util.timer.*
class CountDown {
int delay = 1000
int period = 5 * 1000
int remainingTime = 25 * 60 *1000
@Bindable String timeStr = "25:00"
public void timeString() {
int seconds = ((int) (remainingTime / 1000)) % 60 ;
int minutes =((int) (remainingTime / (1000*60))) % 60;
// Here's the issue
// timeStr = ((minutes < 9) ? "0" : "") + minutes + ":" + ((seconds < 9) ? "0" : "") + seconds
setTimeStr(String.format("%02d:%02d", minutes, seconds))
}
public void update() {
if (remainingTime >= period) {
remainingTime -= period
}
timeString()
}
}
class TimerTaskCountDown extends TimerTask {
CountDown model
public TimerTaskCountDown (CountDown model) {
super()
this.model = model
}
public void run() {
model.update()
}
}
model = new CountDown()
ttcd = new TimerTaskCountDown(model)
timer = new Timer()
timer.scheduleAtFixedRate(ttcd, model.delay, model.period)
def s = new SwingBuilder()
s.setVariable('myDialog-properties',[:])
def dial = s.dialog(title:'Pomodoro', id:'working', modal:false, defaultCloseOperation:JFrame.DISPOSE_ON_CLOSE, pack:true, show:true) {
panel() {
boxLayout(axis:BXL.Y_AXIS)
panel(alignmentX:0f) {
flowLayout(alignment:FL.LEFT)
label text: bind { "Pomodoro time: " + model.timeStr }
}
panel(alignmentX:0f) {
flowLayout(alignment:FL.RIGHT)
button(action: action(name: 'STOP', defaultButton: true, mnemonic: 'S', closure: { model.timeStr = "stopped"; vars.ok = true }))
}
}
}