註:根據yigemaser、JFML、CrazyJavar的建議更新,對三位的協助表示感謝!
在寫UI應用時,通常會在一些事件處理的過程中,尤其當這個處理比較耗時的時候,希望能夠及時把一些進度資訊顯示給使用者。這時通常大家都會使用一個文本控制項來顯示這些進度資訊。比如下面的程式中,有一個JTextPane和JButton,在JButton中的action事件中需要進行一些耗時的處理,例子程式中使用了Thread.sleep()使當前線程休眠3秒來類比耗時的操作。action事件處理分為3步,我們希望及時把當前的進度顯示在JTextPane上。
代碼如下:
- package bruce.test;
- import javax.swing.*;
- import java.awt.Container;
- import java.awt.BorderLayout;
- import java.awt.Dimension;
- import java.awt.event.WindowAdapter;
- import java.awt.event.ActionListener;
- import java.awt.event.ActionEvent;
- /**
- * 事件處理過程中UI的重新整理
- * @author Bruce
- * @version 1.0
- */
- public class TestUIUpdate2 {
- public TestUIUpdate2() {
- TestUIUpdate2Frame frame = new TestUIUpdate2Frame();
- frame.pack();
- frame.setVisible(true);
- }
- public static void main(String[] args) {
- new TestUIUpdate2();
- }
- }
- class TestUIUpdate2Frame extends JFrame {
- JTextPane pane = new JTextPane();
- JButton button = new JButton("action...");
- TestUIUpdate2Frame() {
- init();
- this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
- button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e){
- try {
- pane.setText("step one...");
- Thread.sleep(3000);
- pane.setText("\nstep two...");
- Thread.sleep(3000);
- pane.setText("\nfinished.");
- Thread.sleep(3000);
- }
- catch (InterruptedException ie) {
- //ignored
- }
- }
- });
- }
- private void init() {
- pane.setPreferredSize(new Dimension(300,200));
- Container content = getContentPane();
- content.setLayout(new BorderLayout());
- content.add(pane, BorderLayout.CENTER);
- content.add(button, BorderLayout.SOUTH);
- }
- }
但在實際運行過程中可以發現,點擊JButton後,JTextPane並不能及時更新,而是在整個JButton的action事件處理完畢後才能顯示出最後的資訊。為什麼會出現這種情況呢?因為在處理JButton的action事件程序中,雖然更新了JTextPane的內容,但由於JButton的事件處理是在當前main線程中運行,雖然JTextPane更新了內容,但沒有得到重新整理顯示的執行機會。
解決這個問題的方法非常簡單,只需要把JButton的action處理代碼放入一個新的線程,然後啟動這個線程。另外,由於Swing的操作大部分是非安全執行緒的,所以對Swing介面的重新整理也單獨放在一個線程,並調用SwingUtilities.invokeLater()執行。這樣action事件處理、更新JTextPane的介面和main主線程就分別運行在各自的線程中,都可以及時得到執行。JButton的
- actionPerformed(ActionEvent e)的處理代碼修改如下:
- [code] button.addActionListener(new ActionListener() {
- public void actionPerformed(ActionEvent e){
- try
- {
- new Thread() {
- public void run() {
- try {
- showMessage("step one...");
- Thread.sleep(3000);
- showMessage("\nstep two...");
- Thread.sleep(3000);
- showMessage("\nfinished.");
- Thread.sleep(3000);
- }
- catch (InterruptedException ie) {
- //ignored
- }
- }
- }.start();
-
- }
- catch (Exception ex)
- {
- ex.printStackTrace();
- }
- }
- });
showMessage方法如下:
- private void showMessage (final String msg) {
- SwingUtilities.invokeLater(new Runnable() {
- public void run() {
- pane.setText(pane.getText() + msg);
- }
- });
- }
大家可以測試回合觀察效果。這樣也使介面更加友好,因為如果不把action的處理代碼放在一個單獨的線程中,使用者點擊JButton後,介面就停止一切響應,直到action處理代碼執行完畢。大家可以擴充這種方法,允許使用者隨時停止該耗時的操作,使介面更加友好。