:
现在我们要做一个简单的界面。
包括一个进度条、一个输入框、开始和停止按钮。
需要实现的功能是:
当点击开始按钮,则更新进度条,并且在输入框内把完成的百分比输出(这里只做例子,没有真正去做某个工作)。
1 packagetest;2
3 importjava.awt.FlowLayout;4 importjava.awt.event.ActionEvent;5 importjava.awt.event.ActionListener;6
7 importjavax.swing.JButton;8 importjavax.swing.JFrame;9 importjavax.swing.JProgressBar;10 importjavax.swing.JTextField;11
12 public class SwingThreadTest1 extendsJFrame {13 private static final long serialVersionUID = 1L;14 private static final String STR = "Completed : ";15 private JProgressBar progressBar = newJProgressBar();16 private JTextField text = new JTextField(10);17 private JButton start = new JButton("Start");18 private JButton end = new JButton("End");19 private boolean flag = false;20 private int count = 0;21
22 publicSwingThreadTest1() {23 this.setLayout(newFlowLayout());24 add(progressBar);25 text.setEditable(false);26 add(text);27 add(start);28 add(end);29 start.addActionListener(newStart());30 end.addActionListener(newEnd());31 }32
33 private voidgo() {34 while (count < 100) {35 try{36 Thread.sleep(100);//这里比作要完成的某个耗时的工作
37 } catch(InterruptedException e) {38 e.printStackTrace();39 }40 //更新进度条和输入框
41 if(flag) {42 count++;43 progressBar.setValue(count);44 text.setText(STR + String.valueOf(count) + "%");45 }46 }47 }48
49 private class Start implementsActionListener {50 public voidactionPerformed(ActionEvent e) {51 flag = true;//设置开始更新的标志
52 go();//开始工作
53 System.out.println(Thread.currentThread().getName());54 }55 }56
57 private class End implementsActionListener {58 public voidactionPerformed(ActionEvent e) {59 flag = false;//停止
60 }61 }62
63 public static voidmain(String[] args) {64 SwingThreadTest1 fg = newSwingThreadTest1();65 fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);66 fg.setSize(300, 100);67 fg.setVisible(true);68 }69 }
View Code
运行代码发现,
现象1:当点击了开始按钮,画面就卡住了。按钮不能点击,进度条没有被更新,输入框上也没有任何信息。
原因分析:Swing是线程不安全的,是单线程的设计,所以只能从事件派发线程访问将要在屏幕上绘制的Swing组件。ActionListener的actionPerformed方法是在事件派发线程中调用执行的,而点击了开始按钮后,执行了go()方法,在go()里,虽然也去执行了更新组件的方法
progressBar.setValue(count);
text.setText(STR + String.valueOf(count) + "%");
但由于go()方法直到循环结束,它并没有返回,所以更新组件的操作一直没有被执行,这就造成了画面卡住的现象。
go方法也一直在事件派发进程上,和更新UI在一个进程中,在一个进程中就必然是顺序执行的了。
现象2:过了一段时间(go方法里的循环结束了)后,画面又可以操作,并且进度条被更新,输入框也出现了我们想看到的信息。
原因分析:通过在现象1的分析,很容易联想到,当go()方法返回了,则其他的线程(更新组件)可以被派发了,所以画面上的组件被更新了。
为了让画面不会卡住,我们来修改代码,将耗时的工作放在一个线程里去做。
1 packagetest;2
3 importjava.awt.FlowLayout;4 importjava.awt.event.ActionEvent;5 importjava.awt.event.ActionListener;6
7 importjavax.swing.JButton;8 importjavax.swing.JFrame;9 importjavax.swing.JProgressBar;10 importjavax.swing.JTextField;11
12 public class SwingThreadTest2 extendsJFrame {13 private static final long serialVersionUID = 1L;14 private static final String STR = "Completed : ";15 private JProgressBar progressBar = newJProgressBar();16 private JTextField text = new JTextField(10);17 private JButton start = new JButton("Start");18 private JButton end = new JButton("End");19 private boolean flag = false;20 private int count = 0;21
22 GoThread t = null;23
24 publicSwingThreadTest2() {25 this.setLayout(newFlowLayout());26 add(progressBar);27 text.setEditable(false);28 add(text);29 add(start);30 add(end);31 start.addActionListener(newStart());32 end.addActionListener(newEnd());33 }34
35 private voidgo() {36 while (count < 100) {37 try{38 Thread.sleep(10);39 } catch(InterruptedException e) {40 e.printStackTrace();41 }42 if(flag) {43 count++;44 System.out.println(count);45 progressBar.setValue(count);46 System.out.println(Thread.currentThread().getName());47 text.setText(STR + String.valueOf(count) + "%");48 }49 }50 }51
52 private class Start implementsActionListener {53 public voidactionPerformed(ActionEvent e) {54 System.out.println(Thread.currentThread().getName());55 flag = true;56 if (t == null) {57 t = newGoThread();58 t.start();59 }60 }61 }62
63 //执行复杂工作,然后更新组件的线程
64 class GoThread extendsThread {65 public voidrun() {66 //do something...
67 go();68 }69 }70
71 private class End implementsActionListener {72 public voidactionPerformed(ActionEvent e) {73 flag = false;74 }75 }76
77 public static voidmain(String[] args) {78 SwingThreadTest2 fg = newSwingThreadTest2();79 fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);80 fg.setSize(300, 100);81 fg.setVisible(true);82 }83 }
View Code
我们执行了程序,结果和我们想要的一样,画面不会卡住了。
那这个程序是否没有问题了呢?
我们自定义了一个线程GoThread,在这里我们完成了那些耗时的工作,可以看作是“工作线程”,
而对于组件的更新,我们也放在了“工作线程”里完成了。
在这里,在事件派发线程以外的线程里设置进度条,是一个危险的操作,运行是不正常的。(对于输入框组件的更新是安全的。)
只有从事件派发线程才能更新组件,根据这个原则,我们来修改我们现有代码。
1 packagetest;2
3 importjava.awt.FlowLayout;4 importjava.awt.event.ActionEvent;5 importjava.awt.event.ActionListener;6
7 importjavax.swing.JButton;8 importjavax.swing.JFrame;9 importjavax.swing.JProgressBar;10 importjavax.swing.JTextField;11 importjavax.swing.SwingUtilities;12
13 public class SwingThreadTest3 extendsJFrame {14 private static final long serialVersionUID = 1L;15 private static final String STR = "Completed : ";16 private JProgressBar progressBar = newJProgressBar();17 private JTextField text = new JTextField(10);18 private JButton start = new JButton("Start");19 private JButton end = new JButton("End");20 private boolean flag = false;21 private int count = 0;22
23 private GoThread t = null;24
25 private Runnable run = null;//更新组件的线程
26
27 publicSwingThreadTest3() {28 this.setLayout(newFlowLayout());29 add(progressBar);30 text.setEditable(false);31 add(text);32 add(start);33 add(end);34 start.addActionListener(newStart());35 end.addActionListener(newEnd());36
37 run = new Runnable() {//实例化更新组件的线程
38 public voidrun() {39 System.out.println(Thread.currentThread().getName());40 progressBar.setValue(count);41 text.setText(STR + String.valueOf(count) + "%");42 }43 };44 }45
46 private voidgo() {47 while (count < 100) {48 try{49 Thread.sleep(10);50 } catch(InterruptedException e) {51 e.printStackTrace();52 }53 if(flag) {54 count++;55 System.out.println(Thread.currentThread().getName());56 SwingUtilities.invokeLater(run);//将对象排到事件派发线程的队列中
57 }58 }59 }60
61 private class Start implementsActionListener {62 public voidactionPerformed(ActionEvent e) {63 flag = true;64 if (t == null) {65 t = newGoThread();66 t.start();67 }68 }69 }70
71 class GoThread extendsThread {72 public voidrun() {73 //do something...
74 go();75 }76 }77
78 private class End implementsActionListener {79 public voidactionPerformed(ActionEvent e) {80 flag = false;81 }82 }83
84 public static voidmain(String[] args) {85 SwingThreadTest3 fg = newSwingThreadTest3();86 fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);87 fg.setSize(300, 100);88 fg.setVisible(true);89 }90 }
View Code
解释:SwingUtilities.invokeLater()方法使事件派发线程上的可运行对象排队。当可运行对象排在事件派发队列的队首时,就调用其run方法。其效果是允许事件派发线程调用另一个线程中的任意一个代码块。
还有一个方法SwingUtilities.invokeAndWait()方法,它也可以使事件派发线程上的可运行对象排队。
区别:
下面这个是弹出个alert窗口。若用invokeAndWait(),那么打印一段文字将在你点击了OK buton之后才会执行,而如用invokeLater()则,立马后执行输出操作。
1 packagetest;2
3 importjava.lang.reflect.InvocationTargetException;4
5 importjavax.swing.JOptionPane;6 importjavax.swing.SwingUtilities;7
8 public classInvoke {9 public static voidmain(String[] args) {10 Runnable showModalDialog = newRunnable() {11 public voidrun() {12 JOptionPane.showMessageDialog(null, "No active shares found on this IP!");13 }14 };15 try{16 SwingUtilities.invokeAndWait(showModalDialog);17 System.out.println("sssssssssss");18 } catch(InterruptedException e) {19 //TODO Auto-generated catch block
20 e.printStackTrace();21 } catch(InvocationTargetException e) {22 //TODO Auto-generated catch block
23 e.printStackTrace();24 }25 }26 }
View Code