可以@Bindable变量在不同线程中的变化可以反映出相应的UI元素吗?

时间:2011-12-26 13:48:39

标签: user-interface groovy bindable swingbuilder countdowntimer

我正在学习使用GUI实现倒计时器,显示时间减少。我正在使用Groovy的@Bindable,希望减少时间的更改可以自动显示在相应的UI标签中。

倒计时时间值的减少是在计时器线程中完成的,与UI线程分开。但是,在UI中没有更新倒数计时器。

在UI更新中正确倒计时的适当方法是什么?

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
                            }))
    }
  }
}

2 个答案:

答案 0 :(得分:2)

是的,它可以。 Nutshell:调用setTimeStr而不是直接设置属性。

绕过setter意味着@Bindable添加的代码都没有被执行,因此没有发送属性更改通知。

其他编辑包括小清理,去除噪音,缩短延迟以加快调试速度等等。

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 }))
    }
  }
}

答案 1 :(得分:0)

这是我通过研究Stackoverflow找到的解决方案。我改编自停止计时器的例子。关键是使用Swing Timer代替通用定时器,并使用Listener接口作为定时器值显示面板。

我以前使用@Bindable的尝试仍然有效,但它需要通过setTimeStr例程设置所有可绑定的timeStr。 (感谢Dave的帮助!)

Stackoverflow是学习的好地方。

这是代码。

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.DecimalFormat;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.Timer;

/** @following the example of http://stackoverflow.com/questions/2576909 */
/** adapted for count-down timer */
public class JTimeLabel extends JLabel implements ActionListener {

  private static final String Start = "Start";
  private static final String Stop = "Stop";
  private DecimalFormat df = new DecimalFormat("000.0");
  private Timer timer = new javax.swing.Timer(100, this);

  private int countDownMinutes = 25;
  private long countDownMillis = 25*60*1000;
  private long expireMillis = countDownMillis + System.currentTimeMillis();

  public JTimeLabel() {
    this.setHorizontalAlignment(JLabel.CENTER);
    this.setText(when());
  }

  public void actionPerformed(ActionEvent ae) {// this is for update the timer value
    setText(when());
  }

  public void start() { // reset the expiration time and start the timer
    expireMillis = countDownMillis + System.currentTimeMillis();
    timer.start();
  }

  public void stop() {
    timer.stop();
  }

  private String when() {// show count-down timer value
    if (expireMillis > System.currentTimeMillis()) {
      long remainingMillis = expireMillis - System.currentTimeMillis()
      int seconds = ((int) (remainingMillis / 1000))  % 60 ;
      int minutes =((int) (remainingMillis / (1000*60))) % 60;
      return (String.format("%02d:%02d", minutes, seconds))
    } else {// handle the completion of the count-down timer
      timer.stop ();
      return "00:00"
    }   
  } 

  private static void create() {
    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    final JTimeLabel jtl = new JTimeLabel();
    jtl.setFont(new Font("Dialog", Font.BOLD, 32));
    f.add(jtl, BorderLayout.CENTER);

    final JButton button = new JButton(Stop);
    button.addActionListener(new ActionListener() {
                               public void actionPerformed(ActionEvent e) {
                                 String cmd = e.getActionCommand();
                                 if (Stop.equals(cmd)) {
                                   jtl.stop();
                                   button.setText(Start);
                                 } else {
                                   jtl.start();
                                   button.setText(Stop);
                                 }

                               }
                             });
    f.add(button, BorderLayout.SOUTH);
    f.pack();
    f.setVisible(true);
    jtl.start();
  }

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