使用计时器改变 UI

Use timers to change UI

我试图制作一个每秒更新一次的简单时钟,以降低 CPU 的成本。我最初尝试使用 Thread.sleep(),但是当在 GUI 的循环中使用时,它基本上冻结了整个东西并导致它崩溃,尽管控制台中没有显示任何特定错误。

我查找了以特定时间间隔发送命令的其他方法并遇到了 Timer,所以我尝试使用它。现在我有这个代码。

package clock;

import java.util.Timer;
import java.util.TimerTask;

import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormAttachment;

public class SystemClock {
    Text labelHours;
    Text labelMinutes;
    Text labelSeconds;
    Text labelMeridian;
    TimerTask time = new TimerTask(){
        public void run(){
            pullClock();
        }
    };
    boolean pause=false;
    Timer play = new Timer();
    long delay = 1000;
    long period = 1000;
    protected Shell shlClock;

    /**
     * Launch the application.
     * @param args
     */
    public static void main(String[] args) {
        try {
            SystemClock window = new SystemClock();
            window.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Open the window.
     */
    public void open() {
        Display display = Display.getDefault();
        createContents();
        shlClock.open();
        shlClock.layout();
        while (!shlClock.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }

    /**
     * Create contents of the window.
     */
    protected void createContents() {
        shlClock = new Shell();
        shlClock.setSize(205, 118);
        shlClock.setText("Clock");
        shlClock.setLayout(null);

        Label labelColonHoursMinutes = new Label(shlClock, SWT.NONE);
        labelColonHoursMinutes.setBounds(64, 10, 9, 15);
        labelColonHoursMinutes.setText(":");

        Label labelColonMinutesSeconds = new Label(shlClock, SWT.NONE);
        labelColonMinutesSeconds.setBounds(96, 10, 9, 15);
        labelColonMinutesSeconds.setText(":");

        labelMinutes = new Text(shlClock, SWT.NONE | SWT.CENTER);
        labelMinutes.setBounds(71, 10, 19, 15);
        labelMinutes.setEditable(false);
        labelMinutes.setText("00");

        labelHours = new Text(shlClock, SWT.NONE | SWT.CENTER);
        labelHours.setEditable(false);
        labelHours.setBounds(40, 10, 18, 15);
        labelHours.setText("00");

        labelSeconds = new Text(shlClock, SWT.NONE | SWT.CENTER);
        labelSeconds.setBounds(101, 10, 25, 15);
        labelSeconds.setEditable(false);
        labelSeconds.setText("00");

        Button btnRefresh = new Button(shlClock, SWT.NONE);
        btnRefresh.setBounds(58, 36, 75, 25);
        btnRefresh.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent e) {
                    pause= !pause;
                    if (pause)
                        try{
                            play.scheduleAtFixedRate(time, delay, period);
                        }catch(Exception ex){

                        }
                    else
                        play.cancel();
                }
        });
        btnRefresh.setText("Pause/Play");

        labelMeridian = new Text(shlClock, SWT.NONE);
        labelMeridian.setBounds(125, 10, 25, 15);
        labelMeridian.setEditable(false);
        labelMeridian.setText("AM");

    }


public void pullClock(){
        long currentTime = System.currentTimeMillis();
        int hours;
        int minutes;
        int seconds;
        String hoursStr;
        String minutesStr;
        String secondsStr;
        String meridian;            
            currentTime/=1000;
            seconds = (int)currentTime%60;
            currentTime/=60;
            minutes = (int)currentTime%60;
            currentTime/=60;
            hours = (int)currentTime%24;
            hours -=4;

            switch(hours){
                case -4: hoursStr="8"; meridian = "PM"; break;
                case -3: hoursStr="9"; meridian = "PM"; break;
                case -2: hoursStr="10"; meridian = "PM"; break; 
                case -1: hoursStr="11"; meridian = "PM"; break;
                case  0: hoursStr="12"; meridian = "AM"; break;
                case 12: hoursStr="12"; meridian = "PM"; break;
                case 13: hoursStr="1"; meridian = "PM"; break;
                case 14: hoursStr="2"; meridian = "PM"; break;
                case 15: hoursStr="3"; meridian = "PM"; break;
                case 16: hoursStr="4"; meridian = "PM"; break;
                case 17: hoursStr="5"; meridian = "PM"; break;
                case 18: hoursStr="6"; meridian = "PM"; break;
                case 19: hoursStr="7"; meridian = "PM"; break;
                default: hoursStr=String.valueOf(hours); meridian = "AM";
            }
            secondsStr=String.valueOf(seconds);
            minutesStr=String.valueOf(minutes);
            labelHours.setText(hoursStr);
            labelMinutes.setText((minutes<10) ? "0"+minutesStr:minutesStr);
            labelSeconds.setText((seconds<10) ? "0"+secondsStr:secondsStr);
            labelMeridian.setText(meridian);
        }
    }

这会导致 org.eclipse.swt.SWTException 并使程序崩溃。

我正在使用 Eclipse,它很容易获得 SWT gui 编辑器来构建程序,这似乎是问题的一部分。我的问题本质上是是否有办法在仍然使用 SWT 的同时修复它,如果没有,如果我使用 JFrame?

您要查找的内容在 SWT 中称为 Display#timerExec()。该方法接受一个可运行对象,它将在经过给定的毫秒数后执行一次。

display.timerExec( 1000, new Runnable() {
  public void run() {
    if( !shlClock.isDisposed() {
      pullClock();
      display.timerExec( 1000, this );
    }
  }
}

在上面的代码片段中,当实际工作完成时,runnable 会重新安排 itslef (display.timerExec( 1000, this );)。

为了取消已经安排好的运行,调用display.timerExec( -1, runnable )

使用其他答案中提到的 display.timerExec 可能是最好的,但您也可以在计时器任务中使用 Display.syncExec

TimerTask time = new TimerTask() {
    public void run() {
        Display.getDefault().synchExec(new Runnable() {
           public void run() {
              pullClock();
           }
        });
    }
};

或者如果您使用的是 Java 8,您可以将其简化为:

TimerTask time = new TimerTask() {
    public void run() {
        Display.getDefault().synchExec(() -> pullClock());
    }
};