Может ли изменение переменной @Bindable в другом потоке отражаться в соответствующем элементе пользовательского интерфейса?

Я учусь реализовывать таймер обратного отсчета с графическим интерфейсом, показывающим сокращение времени. Я использую Groovy @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.*  

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

person Yu Shen    schedule 26.12.2011    source источник
comment
Из-за отсутствия прямого ответа я нашел самый близкий пример на Java с таймером Swing, найденным в stackoverflow.com/questions/2576353/stop-a-stopwatch/   -  person Yu Shen    schedule 27.12.2011


Ответы (2)


Да, оно может. В двух словах: вызовите setTimeStr вместо того, чтобы устанавливать свойство напрямую.

Обход установщика означал, что ни один код, добавленный @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 }))
    }
  }
}
person Dave Newton    schedule 27.12.2011
comment
Другой вопрос: где я могу найти более официальную подробную документацию по технологии, связанной с @Bindable. Мне кажется, что при гуглении трудно найти исчерпывающую документацию по Groovy. Его можно рассматривать как приукрашивание Java, а не как первоклассный язык. Это трудно для людей, которые мало знают о Java, бремя состоит в том, чтобы хорошо разбираться в Java, а затем использовать Groovy. - person Yu Shen; 28.12.2011
comment
Я ищу учебник или руководство пользователя Groovy для тех, кто не программирует на Java. Спасибо. - person Yu Shen; 29.12.2011
comment
@YuShen Я думаю, что книга Groovy в действии, вероятно, ваш лучший выбор. - person Dave Newton; 29.12.2011

Вот решение, которое я нашел, изучая Stackoverflow. Я адаптировал на примере стоп-таймера. Ключевым моментом является использование Swing Timer вместо общего таймера и интерфейса Listener для панели отображения значения таймера.

Моя предыдущая попытка использовать @Bindable по-прежнему будет работать, но для этого требуется, чтобы все настройки связывались с timeStr через процедуру setTimeStr. (Благодаря помощи Дэйва!)

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();
                             }
                           });
  }
}
person Community    schedule 27.12.2011