package shc.stopwatch;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.BorderFactory;
import javax.swing.SwingConstants;
import java.awt.event.ActionEvent;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.event.ActionListener;
public class StopWatch extends JFrame {
private static String LABEL_CONTENT_INIT = "00:00:00 0";
private static final long serialVersionUID = 1;
private JButton startButton = new JButton("Start");
private JButton resetButton = new JButton("Reset");
private long startupTime = System.currentTimeMillis();
private long pauseTime = startupTime;
private long pauseCount = 0;
private CountingThread cThread = new CountingThread();
private JLabel content = new JLabel(LABEL_CONTENT_INIT);
private ActionListener startButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent e){
//The Button is pressed initially,and cThread start
//updating the label.
if(cThread.isStoped){
pauseCount += (System.currentTimeMillis() - pauseTime);
cThread.isStoped = false;
startButton.setText("Pause");
}else{//Pressed again and Stop counting.
cThread.isStoped = true;
pauseTime = System.currentTimeMillis();
startButton.setText("Continue");
}
}
};
private ActionListener resetButtonListener = new ActionListener() {
public void actionPerformed(ActionEvent e){
cThread.isStoped = true;
pauseTime = startupTime;
content.setText(LABEL_CONTENT_INIT);
startButton.setText("Start");
pauseCount = 0;
}
};
public StopWatch(String title) {
super(title);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocation(200,200);
setResizable(false);
setupBorder();
setupButtonsPanel();
setupLabel();
startButton.addActionListener(startButtonListener);
resetButton.addActionListener(resetButtonListener);
cThread.start();
//content.setText(formatTime(345678));
}
private void setupBorder() {
JPanel contentPane = new JPanel(new BorderLayout());
contentPane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
this.setContentPane(contentPane);
}
private void setupButtonsPanel() {
JPanel buttonPane = new JPanel(new FlowLayout());
buttonPane.add(startButton);
buttonPane.add(resetButton);
add(buttonPane,BorderLayout.SOUTH);
}
private void setupLabel() {
content.setHorizontalAlignment(SwingConstants.CENTER);
content.setFont(new Font(content.getFont().getName(),
content.getFont().getStyle(),40));
add(content,BorderLayout.CENTER);
}
public static void main(String[] args) {
try{
UIManager.setLookAndFeel(UIManager.
getSystemLookAndFeelClassName());
}catch(Exception e){
e.printStackTrace();
}
StopWatch stopWatch = new StopWatch("Stop Watch");
stopWatch.pack();
stopWatch.setVisible(true);
}
private String formatTime(long elapse) {
int millis, sec, min, hour;
/*
millis = (int)(eclapse - eclapse / 1000 * 1000);
millis /= 100;
eclapse -= millis;
eclapse /= 1000;
sec = (int)(eclapse - eclapse / 60 * 60);
eclapse -= sec;
eclapse /= 60;
min =(int)(eclapse - eclapse / 60 * 60);
eclapse -= min;
hour = (int)(eclapse / 60);
*/
millis = (int)(elapse % 1000);
millis /= 100;
elapse /= 1000;
sec = (int)(elapse % 60);
elapse /= 60;
min = (int)(elapse % 60);
elapse /= 60;
hour = (int)(elapse % 60);
return String.format("%02d:%02d:%02d:%01d", hour,min,sec,millis);
}
private class CountingThread extends Thread {
public boolean isStoped = true;
private CountingThread(){
this.setDaemon(true);
}
public void run(){
while(true){
if(!isStoped){
long elapseTime = System.currentTimeMillis() - startupTime
- pauseCount;
content.setText(formatTime(elapseTime));
}
try{
sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
}
}
在这里,我主要从两个方面理解。
首先是程序界面。要显示一个“窗口”,选择JFrame类,因为它是Java图形中的顶层容器,其显示效果就是个“窗口”;所以自然要从JFrame类扩展一个子类,如代码第16行。下来要往“窗口”,也就是我们所扩展的StopWatch子类,里面添加一些元素。加一个文本标签来显示时间的变化,加两个按钮来控制时间的变化行为。代码第21、22、30创建两个按钮和一个标签的实例,我们不希望别人再扩展这个StopWatch类,就把这几个成员指定为私有的。
怎么把它们加入到JFrame里?在这里,创建一个面板(JPanel)实例(代码第72行),再把它设置为JFrame的内容窗格(代码第74行)。正如《Java核心技术 卷I》中所讲,JFrame类有几个窗格(pane)成员,我们经常与之打交道的是内容窗格,上面的元素最终是加到这个窗格的。代码第74行中的方法setContentPane就是把传入的容器类实例作为内容窗格(JPanel是Container的子类哦)。再添加其他东西,调用add方法即可。就像代码第80、86行那样,它们把装有按钮和标签的两个面板放在内容窗格的适当位置上,位置是由传给add方法的第二个参数决定的。
另外,创建面板时,我们两次(代码第72、77行)都指定了布局:布局用来决定组件在容器中放置的位置及大小,比如代码第77-79行,把两个按钮以流布局方式放在面板中,这样它们就依次横向排列。
以上这些都是在构造JFrame子类的时候做的动作,这样程序界面就做好了。代码第90行是确定程序运行时呈现出来的外观的(也就是《Java核心技术 卷I》中所用的术语“观感”),不加这行代码,在任何平台上,程序界面外观都一样;加上后,Windows下程序呈现的是Windows的界面风格,Ubuntu下呈现的是就是Ubuntu的风格。
代码58-60分别指定:点击窗口关闭按钮时的默认动作,窗口在屏幕上的位置,窗口是否可改变大小。代码第96行是把窗口的大小调整到较优的状态同时适应它包含的组件的布局,97行把这些都显示出来,这个不能忘,否则运行后什么都没有哦。
Java文档说Swing组件不是线程安全的,就这样建议:把89-97行放到一个Runnable的run方法里,再把它作为一个参数传给EventQueue.invokeLater或SwingUtilities.invokeLater,它们其中之一再放在main方法中。我试了一下前者,在这个例子里没有感觉到什么不同。但最好按文档做。