1. java中進度條不能更新問題的研究
感謝大佬:https://blog.csdn.net/smartcat86/article/details/2226681
為什么進度條在事件處理過程中不更新,而是在完成后,從0%調到100%?
分兩種情況:
1)在AWT事件線程中執行的操作
當 應用程序在事件線程中執行長時間的操作時,會阻塞正常的AWT事件處理,因此阻止了重繪操作的發生。這同常會在下列情況下發生:應用程序響應一個來自用戶 界面的請求時,在連接到一個按鈕或其他GUI組件的事件處理程序中執行任務,任務的內容可能會需要較長時間,使事件線程掛起,直至遠程系統發出答復為止。 當應用程序調用JProgressBar的setValue方法時,進度條可能更新期內部狀態並調用repaint,這樣做會把一個事件放置到AWT事件 隊列中。不幸的是,直至應用程序的事件處理程序完成其處理並把控制權返回到線程的事件處理循環,才能處理該事件。
可以通過調用JComponent的paintImmediately方法來這樣做,該方法有兩種形式:
public void paintImmediately(int x, int y, int width, int height);
public void paintImmediately(Rectangel rect);
例如:
Dimension d = bar.getSize();
Rectangel rect = new Rectangle(0,0, d.width, d.height);
…
bar.setValue(progressValue);
bar.paintImmediately(rect);
…
2)在另一個線程中執行的操作
如 果在一個單獨的線程中執行該操作,當調用進度條的setValue方法,它的更新不會出現任何問題,問題在於,后台線程必須調用JProgressBar 的setValue。而Swing組件只有在事件線程中才能安全的訪問。因此,從執行實際工作的線程調用setValue方法是不安全的!解決的方法是使 用SwingUtilites的invokeLater方法,讓AWT事件線程稍后進行setValue調用。
例如:
…
SwingUtilities.invokeLater(new Runnable() {
public void run() {
bar.setValue(value);
}
});
…
還有一種可能,不能再線程中改變swing組件,例如,不能從線程調用label.setText,但是可以使用EventQueue類的invokeLater和invokeAndWait方法,以便在事件調度線程中執行該調用程序。(From Core Java)
2. Swing 刷新組件java swing中兩大原則: 1. 不要阻塞UI線程 2. 不要在UI線程外的線程去操作UI控件
感謝大佬:https://blog.csdn.net/u010536134/article/details/51434568
Swing中事件處理和繪畫代碼都在一個單獨的線程中執行,這個線程就叫做事件分發線程。
java swing中兩大原則:
1. 不要阻塞UI線程
2. 不要在UI線程外的線程去操作UI控件
package com.test.loader;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LabelDemo extends JPanel implements ActionListener{
private static final long serialVersionUID = 1L;
private JLabel label2;
public LabelDemo() {
super(new GridLayout(2,1));
JButton b1 = new JButton("click me");
b1.addActionListener(this);
label2 = new JLabel("Label");
add(label2);
add(b1);
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("LabelDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new LabelDemo());
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable(){
@Override
public void run() {
for(int i = 0 ; i < 10 ; i ++){
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {}
final int x =i;
SwingUtilities.invokeLater(new Runnable(){
@Override
public void run() {
label2.setText(x + "");
}
});
}
}
}).start();
}
}
3.Java Swing GUI多線程之SwingUtilities.invokeLater和invokeAndWait
在Java中Swing是線程不安全的,是單線程的設計,這樣的造成結果就是:只能從事件派發線程訪問將要在屏幕上繪制的Swing組件。事件派發線程是 調用paint和update等回調方法的線程,它還是事件監聽器接口中定義的事件處理方法,例如,ActionListener中的 actionPerformed方法在事件派發線程中調用。
Swing是事件驅動的,所以在回調函數中更新可見的GUI是很自然的事情,比如,有一個按鈕被按下,項目列表需要更新時,則通常在與該按鈕相關聯的事件 監聽器的actionPerformed方法中來實現該列表的更新,從事件派發線程以外的線程中更新Swing組件是不正常的。
有時需要從事件派發線程以外的線程中更新Swing組件,例如,在actionPerformed中有很費時的操作,需要很長時間才能返回,按鈕激活后需 要很長時間才能看到更新的列表,按鈕會長時間保持按下的狀態只到actionPerformed返回,一般說來耗時的操作不應該在事件處理方法中執行,因 為事件處理返回之前,其他事件是不能觸發的,界面類似於卡住的狀況,所以在獨立的線程上執行比較耗時的操作可能更好,這會立即更新用戶界面和釋放事件派發 線程去派發其他的事件。
SwingUtilities類提供了兩個方法:invokeLate和invoteAndWait,它們都使事件派發線程上的可運行對象排隊。當可運行 對象排在事件派發隊列的隊首時,就調用其run方法。其效果是允許事件派發線程調用另一個線程中的任意一個代碼塊。
只有從事件派發線程才能更新組件。
程序示例:更新組件的錯誤方法
startButton.addActionListener(new ActionListener())
{
public void actionPerformed(ActionEvent e)
{
GetInfoThread t = new GetInfoThread(Test.this);
t.start();
startButton.setEnabled(false);
}
}
class GetInfoThread extends Thread
{
Test applet;
public GetInfoThread(Test applet)
{
this.applet = applet;
}
public void run()
{
while (true)
{
try
{
Thread.sleep(500);
applet.getProgressBar().setValue(Math.random() * 100);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
錯誤分析:在actionPerformed中,監聽器把按鈕的允許狀態設置為false,由於是在事件派發線程上調用 actionPerformed,所以setEnabled是一個有效的操作,但是在GetInfoThread中設置進度條是一個危險的做法,因為事件 派發線程以外的線程更新了進度條,所以運行是不正常的。
1、invokeLater使用
class GetInfoThread extends Thread
{
Test applet;
Runnable runx;
int value;
public GetInfoThread(final Test applet)
{
this.applet = applet;
runx = new Runnable()
{
public void run()
{
JProgressBar jpb = applet.getProgressBar();
jpb.setValue(value);
}
}
}
public void run()
{
while (true)
{
try
{
Thread.sleep(500);
value = (int) (Math.random() * 100);
System.out.println(value);
SwingUtilities.invokeLater(runx);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
2、invokeAndWait
與invoikeLater一樣,invokeAndWait也把可運行對象排入事件派發線程的隊列中,invokeLater在把可運行的對象放入隊列 后就返回,而invokeAndWait一直等待知道已啟動了可運行的run方法才返回。如果一個操作在另外一個操作執行之前必須從一個組件獲得信息,則 invokeAndWait方法是很有用的。
class GetInfoThread extends Thread
{
Runnable getValue,setValue;
int value,currentValue;
public GetInfoThread(final Test applet)
{
getValue=new Runnable()
{
public void run()
{
JProgressBar pb=applet.getProgressBar();
currentValue=pb.getValue();
}
};
setValue=new Runnable()
{
public void run()
{
JProgressBar pb=applet.getProgressBar();
pb.setValue(value);
}
}
}
public void run()
{
while(true)
{
try
{
Thread.currentThead().sleep(500);
value=(int)(Math.random()*100);
try
{
SwingUtilities.invokeAndWait(getValue);//直到getValue可運行的run方法返回后才返回
}
catch(Exception ex)
{
}
if(currentValue!=value)
{
SwingUtilities.invokeLater(setValue);
}
}
catch(Exception ex)
{
}
}
}
invokeLater和invoikeAndWait的一個重要區別:可以從事件派發線程中調用invokeLater,卻不能從事件派發線程中調用 invokeAndWait,從事件派發線程調用invokeAndWait的問題是:invokeAndWait鎖定調用它的線程,直到可運行對象從事 件派發線程中派發出去並且該可運行的對象的run方法激活,如果從事件派發線程調用invoikeAndWait,則會發生死鎖的狀況,因為 invokeAndWait正在等待事件派發,但是,由於是從事件派發線程中調用invokeAndWait,所以直到invokeAndWait返回后 事件才能派發。
actionPerformed();返回的時候事件派發線程才能派發線程,而在actionPerformed中使用invokeAndWait則會導致actionPerformed不能返回。所以也就無法派發invokeAndWait中的線程。
由於Swing是線程不安全的,所以,從事件派發線程之外的線程訪問Swing組件是不安全的,SwingUtilities類提供這兩種方法用於執行事件派發線程中的代碼
總結: GUI中多線調用方法應該使用:SwingUtilities.invokeLater和invokeAndWait 而不是普通情況下那樣應用.
看到很多地方講述Swing中的並發和多線程問題,感覺講的都不如Sun的教程,這里復述一下關鍵。Swing之所以和多線程緊密聯系在一 起是因為圖形界面編程中如果只采取順序編程(也就是你的代碼或任務依次執行),會出現很大的問題,比如你要編寫一個FTP客戶端,你不能讓文件下載的時 候,用戶界面死在那里,你既不能取消任務也不能和界面交互吧。所以有必要將耗時的任務,比如文件下載放到一個獨立的線程中處理,而讓用戶同時能夠干其他事 情。簡單來說,Swing中有三種線程:
啟動線程或者初始線程: 這個線程負責調用main方法,很多順序編程一開始就用的是這種線程。在Swing中啟動線程負責很少的事務,主要干兩件事情,第一件就是創建一個可運行 的對象(Runnable Object),這個可運行對象的任務比較重要,它負責初始化圖形界面,第二件就是將這個可運行對象安排到另外一個非常重要的線程,事件分派線程中執行。 第二件事情是通過SwingUtilies的invokeLater和invokeAndWait方法來實現的。幾乎所有的創建Swing組件和與 Swing組件交互的代碼都要在事件分派線程中執行。
事件分派線程:在Swing中負責事件處理的代碼需要在一個特定的線程中運行,這個線程就 是事件分派線程。大部分調用Swing方法的代碼也在這個線程中運行。原因是大部分Swing對象中的方法並不是線程安全的,所以需要這個特定的事件分派 線程來保證線程安全。當然也有部分swing對象中的方法指明是線程安全的,這些方法可以在任何線程中調用。你可以將事件分派線程中運行的代碼想象成一系 列短小的任務,大部分任務都是調用事件處理方法,例如ActionListener.actionPerformed()方法,其他任務可被程序代碼通過 SwingUtilities的invokeLater/invokeAndWait方法來安排。需要注意的是,在事件分派線程中的任務必須短小精悍,這 意味着這些任務能夠很快執行完畢,如果你發現有一個耗時的任務,那么你肯定出錯了,你會發現你的圖形界面經常被卡住,或者死掉了。對於耗時任務你需要另外 一個線程,例如工作線程(Worker Thread)來處理。判斷你的代碼時候運行在事件分派線程上的方法很簡單,使用 javax.swing.SwingUtilities.isEventDispatchThread()方法即可。
工作線程(Worker Thread)或者后台線程(Background Thread):你可以在這個線程中處理耗時任務。
如何使用線程
Java平台從開始就被設計成為多線程環境。在你的主程序執行的時候,其它作業如碎片收集和事件處理則是在后台進行的。本 質上,你可以認為這些作業是線程。它們正好是系統管理線程,但是無論如何,它們是線程。線程使你能夠定義相互獨立的作業,彼此之間互不干擾。系統將交換這 些作業進或出CPU,這樣(從外部看來)它們好象是同時運行的。
在你需要在你的程序中處理多個作業時,你也可以使用多個進程。這些進程可以是你自己創建的,你也可以操縱系統線程。
你進行這些多作業處理,要使用幾個不同的類或接口:
java.util.Timer類
javax.swing.Timer類
Thread類
Runnable接口
對於簡單的作業,通常需要重復的,你可以使用java.util.Timer類告訴它“每半秒鍾做一次”。注意:大多數系統例程是使用毫秒的。半秒鍾是500毫秒。
你希望Timer實現的任務是在java.util.TimerTask實例中定義的,其中運行的方法包含要執行的任務。這些在Hi類中進行了演示,其中字符串“Hi”重復地被顯示在屏幕上,直到你按Enter鍵。
import java.util.*;
public class Hi {
public static void main(String args[]) throws java.io.IOException {
TimerTask task = new TimerTask() {
public void run() {
System.out.println(“Hi”);
}
};
Timer timer = new Timer();
timer.schedule(task, 0, 500);
System.out.println(“Press ENTER to stop”);
System.in.read(new byte[10]);
timer.cancel();
}
}
Java Runtime Environment工作的方式是只要有一個線程在運行,程序就不退出。這樣,當取消被調用,沒有其它線程在運行了,則程序退出。有一些系統線程在運 行,如碎片收集程序。這些系統線程也被稱為后台線程。后台線程的存在不影響運行環境被關閉,只有非后台線程保證運行環境不被關閉。
Javax.swing.Timer類與java.util.timer類的工作方式相似,但是有一些差別需要注意。第一,運行的作業被 ActionListener接口的實現來定義。第二,作業的執行是在事件處理線程內部進行的,而不象java.util.Timer類是在它的外部。這 是很重要的,因為它關系到Swing組件集是如何設計的。
如果你不熟悉Swing,它是一組可以被Java程序使用的圖形組件。 Swing被設計程被稱為單線程的。這意味着對Swing類內部內容的訪問必須在單個線程中完成。這個特定的線程是事件處理線程。這樣,例如你想改變 Label組件的文字,你不能僅僅調用Jlabel的setText方法。相反,你必須確認setText調用發生在事件處理線程中,而這正是 javax.swing.Time類派的上用場的地方。
為了說明這第二種情況,下面的程序顯示一個增加的計數器的值。美半秒鍾計數器的數值增加,並且新的數值被顯示。
import javax.swing.;
import java.awt.;
import java.awt.event.*;
public class Count {
public static void main(String args[]) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container contentPane = frame.getContentPane();
final JLabel label = new JLabel("", JLabel.CENTER);
label.setFont(new Font(“Serif”, Font.PLAIN, 36));
contentPane.add(label, BorderLayout.CENTER);
ActionListener listener = new ActionListener() {
int count = 0;
public void actionPerformed(ActionEvent e) {
count++;
label.setText(Integer.toString(count));
}
};
Timer timer = new Timer(500, listener);
timer.start();
frame.setSize(300, 100);
frame.show();
}
}
上述程序的結果是:
[[The No.1 Picture.]]
萬 一你要做的不是一個簡單的重復作業,java.lang.Thread類就派上了用場。它允許你自己控制基本功能。通過創建Thread的一個子類,你可 以使你的系統脫離,並進行一個長時間運行的作業,如從網絡上讀取一個文件,而不阻礙你的其它程序的運行。這種長時間運行的作業將在run方法中定義。
第二種方式是創建Thread類的子類並在子類中實現run方法,或在實現runnable的類中實現run方法,並將這個實現傳遞給Thread的構造函數。
你可能會問有什么區別。Java編程語言僅支持單一繼承。如果你設計的調用是除了Thread以外的其它類,你可以是你的類實現Runnable,而它可以是你的作業被執行。否則,你定義Thread的子類來運行你的Run方法,在處理過程中不再添加其它操作。
對於創建Thread子類的第三種情況,下面的程序生成了一個新的線程來計算一個特定URL的字符數,這個URL是通過命令行傳遞進來的。在這進行過程 之中,實現Runnable的第四種情況被演示,打印出重復的消息。注意在實現Runnable的這后一種情況下,你必須提供重復消息的代碼。你必須同時 sleep,以分配時間並完成操作。在兩種情況下,與使用Timer相比較。這段程序的最后一部分包含有你從命令行讀取命令以觸發程序結束。注意在系統讀 取URL並打印消息的同時,你總可以按Enter鍵結束程序。
import java.io.;
import java.net.;
public class Both {
public static void main(String args[]) {
final String urlString = args[0];
final String message = args[1];
Thread thread1 = new Thread() {
public void run() {
try {
URL url = new URL(urlString);
URLConnection connection = url.openConnection();
InputStreamReader isr = new InputStreamReader(connection
.getInputStream());
BufferedReader reader = new BufferedReader(isr);
int count = 0;
while (reader.read() != -1) {
count++;
}
System.out.println("Size is : " + count);
reader.close();
} catch (MalformedURLException e) {
System.err.println("Bad URL: " + urlString);
} catch (IOException e) {
System.err.println(“I/O Problems”);
}
}
};
thread1.start();
Runnable runnable = new Runnable() {
public void run() {
while (true) {
System.out.println(message);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
}
}
}
};
Thread thread2 = new Thread(runnable);
thread2.start();
try {
System.out.println(“Press ENTER to stop”);
System.in.read(new byte[10]);
} catch (IOException e) {
System.out.println(“I/O problems”);
}
System.exit(0);
}
}
因為有多種方式來處理線程,你選用哪種技術取決於你和你面臨的條件。要成為一個有效的Java編程人員,盡管你通常不必學習Java編程語言的所有內容和核心庫,但是線程是一個例外。你越早了解線程如何工作和如何使用線程,你將越早了解Java程序如何工作和交互
4. Swing理解Swing中的事件與線程
感謝大佬:http://www.360doc.com/content/16/1017/23/31775152_599223057.shtml
talk is cheap , show me the code.
Swing中的事件事件驅動
所有的GUI程序都是事件驅動的。Swing當然也是。
GUI程序不同於Command Line程序,一個很大的區別是程序執行的驅動條件:命令行程序是接受用戶輸入的文本參數,對命令解析,然后通過類似switch的選擇來執行不同的功能模塊。而GUI程 序就不一樣了。GUI程序由界面元素組成,如Button,CheckBox,TextArea,等等。用戶操作不同的組件,就會引發不同的事件,然后, 程序編寫時注冊到UI組件上的事件處理程序得到調用,以此來和用戶交互。
事件Event
事件有點類似於異常:事件是事件類的對象,它攜帶了事件相關的信息,異常是異常類的對象,他攜帶了異常信息。無論是異常,還是事件
發生時,我們的程序都要事先寫好相應的代碼應對並處理。只不過,對於程序員來說,事件是正派的,而異常則是反派,誰也不希望自己的程序出現異常。
java中,所有的事件類都是EventObject類的子類,所有的事件都有一個成員字段:source用來保存事件源,即引發事件的對象。
public class EventObject implements java.io.Serializable { private static final long serialVersionUID = 5516075349620653480L; /* source保存 引發事件的對象的引用*/ protected transient Object source; public EventObject(Object source) { if (source == null) throw new IllegalArgumentException(‘null source’); this.source = source; } public Object getSource { return source; }
public String toString { return getClass.getName + ‘[source=’ + source + ‘]’; } }
Swing的事件機制由AWT提供,下面是Swing中常用的高級事件 ActionEvnet類的部分代碼。還有其他事件。
public class ActionEvent extends AWTEvent
{ public ActionEvent(Object source, int id, String command, long when, int modifiers) { super(source, id); this.actionCommand = command; this.when = when; this.modifiers = modifiers; }
//… }
事件源EventSource
異常,有引發異常的原因,事件,也有引發事件的對象,這就是事件源。誰引發了事件,誰就是事件源。
比如,Button被點擊時引發事件,Button就是事件源,JFrame 狀態變化時,JFrame也是事件源。Swing中所有的組件,都有感知自己被操作的能力。
Swing中,事件源一般是一些用戶組件,他們能感知用戶的操作,並引發相應的事件,最后通知對自己注冊的監聽器。
事件源都會提供事件的注冊接口,所有對某個組件的某個事件感興趣的其他代碼,都可以提前注冊到這個組件上,事件發生時,此組件就會調用相應的注冊的
事件處理程序。
下面是JButton的父類 AbstractButton的一個方法。
protected void fireActionPerformed(ActionEvent event) { // Guaranteed to return a non-null array Object listeners = listenerList.getListenerList; ActionEvent e = null; // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==ActionListener.class) { // Lazily create the event: if (e == null) { String actionCommand = event.getActionCommand; if(actionCommand == null) { actionCommand = getActionCommand; }
e
= new ActionEvent(AbstractButton.this, ActionEvent.ACTION_PERFORMED, actionCommand, event.getWhen, event.getModifiers); } ((ActionListener)listeners[i+1
]).actionPerformed(e);
} } }
監聽者Listener
監聽者(有的也叫偵聽器):實現了某個監聽接口的類對象。某個類實現了一個監聽器接口,它就是一個監聽者。
當事件發生時,並不是事件源處理事件,而是注冊在事件源的上的監聽器去處理。事件源只是通知監聽器,通知實質是調用所有監聽器對象按接口約定實現的的接口方法。
我們知道,對象實現了某個接口,就代表這個對象能做什么。同理,一個對象想成為監聽器,它就必須實現相應的監聽器接口,表明他有處理某個事件的能力。
監聽器實現了監聽接口,就必然要實現接口中定義的方法,用來應對事件。
所有的監聽器接口都必須擴展自EventListener,它是一個空接口。一個事件往往對應一個監聽者接口。
JComponnet類是所有Swing組件的父類。JComponnet 類中有一個 EventListenerList成員,它是一個表,用來存儲所有注冊的監聽者。那也就是說,所有的Swing組件內部都包含一個存儲監聽者的列表,這也是為什么能向Swing組件中注冊監聽器的本質。
public abstract class JComponent extends Container implements Serializable,TransferHandler.HasGetTransferHandler
{ /** A list of event listeners for this component. / protected EventListenerList listenerList = new EventListenerList; //… }
/*
EventListenerList類 這是一個用於保存監聽器的一個表類型。這個表可以存儲任何類型的EventListener,因為內部是用的一個Object數組存儲的。 / public class EventListenerList implements Serializable { protected transient Object listenerList = NULL_ARRAY; //獲取所有監聽者的數組 public Object getListenerList { return listenerList; }
/* * 返回監聽者的數量*/ public int getListenerCount { return listenerList.length/2; } /** 向監聽者列表中添加 “一對” 新的監聽者。其實是添加一個監聽者, 只不過對於一個監聽者需要保存2項:監聽者的類 t,和監聽者本身 l */ public synchronized void add(Class t, T l) { if (l==null) {return;} if (!t.isInstance(l)) { throw new IllegalArgumentException(‘Listener ’ + l +’ is not of type ’ + t); } if (listenerList == NULL_ARRAY) { //如果是第一次添加監聽者,則 new 一個Object 數組。 listenerList = new Object { t, l }; } else { int i = listenerList.length; Object tmp = new Object[i+2]; System.arraycopy(listenerList, 0, tmp, 0, i); tmp[i] = t; tmp[i+1] = l; listenerList = tmp; } } }
這個時候你再回去看事件源分塊中的那段代碼,是不是思路清晰許多了呢?
所以,事件源通知監聽者,實質是遍歷內部的監聽者表,將自己作為EventSorece,構造一個事件對象,並調用所有監聽者的事件處理程序時,將構造的事件對象傳遞過去。
如果你還是有點迷糊,下面通過一例子說明下。
下面是一個簡單的Swing程序。
監聽者:ButtonClickListener 類對象,它實現了監聽器接口。一般我們會使用匿名內部類完成監聽者的實例化,這里寫出成員內部類是為了更清晰。當使用addActionListener方法注冊后,ButtonClickListener對象就被存儲在Button對象內部的一個EventListenerList列表中了。
事件 :點擊Button時生成。
事件源:被點擊的Button對象。
import java.awt.Dimension;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;import javax.swing.SwingUtilities;public class SwingDrive { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable { @Override public void run { JFrame frame = new TestFrame(‘測試’); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); }}class TestFrame extends JFrame{ private static final int FRAME_WIDTH = 530; private static final int FRAME_HEIGHT = 360; /View*****/ private JPanel mainPanel = null; private JButton msgButton = null; private JLabel msgLabel = null; public TestFrame(String title) { super(title); initUI; } private void initUI { //內容面板 mainPanel = new JPanel; mainPanel.setPreferredSize(new Dimension(FRAME_WIDTH,FRAME_HEIGHT)); this.setContentPane(mainPanel); //按鈕 msgButton = new JButton(‘我是按鈕’); //監聽者表示對按鈕的點擊事件感興趣,於是注冊到按鈕上。 msgButton.addActionListener(new ButtonClickListener); //msg顯示文本 msgLabel = new JLabel; //將組建添加到窗體的內容面板中 this.add(msgButton); this.add(msgLabel); this.pack; } /監聽者,實現了監聽接口/ private class ButtonClickListener implements ActionListener { @Override public void actionPerformed(ActionEvent e) { msgLabel.setText(‘你點擊了按鈕’); } }}
還有一點疑問
who invoke the fireActionPerformed(ActionEvent event) method?
誰調用了JButton的fireActionPerformed方法呢?
如果你能想到這個問題,說明你已經開始深入了。這是Swing本身的機制,確切說是AWT提供的機制。一個Swing程序中會有一個toolkit線程不斷運行着,它監視用戶對組件的操作,當組件被點擊,獲取焦點,被最大化,狀態改變等,都會被toolkit線程發現,並將fireXXX發送帶EDT中執行,fireXXX的執行,又會導致所有監聽器的執行。
先不急,這涉及到Swing線程的知識,請往下看。
Swing中的線程
1、主線程,main方法,程序執行的入口。任何程序都必須有的。
2、初始化線程。創建和初始化圖形界面。
3、tookit線程:負責捕捉系統事件,如鼠標,鍵盤等。負責感知組件的操作,並將事件發通知EDT。
4、EDT線程:處理Swing中的各種事件。UI繪制,UI的修改操作,UI的繪制渲染.,監聽者的事件處理函數,等。所有的UI操作都必須在EDT線程中執行,不允許在其他線程中。
5、N個后台工作線程:處理耗時任務,如網絡資源下載,可能阻塞的IO操作。
初始化線程
public static void main(String [] args){ SwingUtilities.invokeLater(new Runnable{ public void run { //初始化線程邏輯代碼在這里執行 } }); }
Swing多線程的執行
圖畫完后,我才發現圖畫的有一問題:其中EDT線程和toolkit線程是循環線程,並沒有確切的執行終點,也就是不知道這2個線程什么時候執行任務到100%。只要Swing程序沒有結束,他們就一直工作,因為用戶可能在任何時候執行UI操作。
后台工作線程當執行完任務后就結束了。
一、不要在EDT線程中執行耗時的任務。
一旦EDT線程被阻塞,UI組件就不能及時渲染,更新,使得整個程序失去對用戶的響應。用戶體驗十分糟糕。
Swing本身是設計為單線程操作的,並非線程安全的.這就意味着:所有的UI操作都會必須在EDT線程中進行。內置的組件都是遵守這個約定的,比如一個JButton被按下時,它需要顯示為按下的狀態,那么,這個渲染為按下的狀態,就會以事件的形式發布到EDT線程中去執行。同樣,按鈕彈起時,需要渲染為普通狀態,也會引發事件,並在EDT中處理。
不要讓EDT干 ‘體力活’。很明顯,Swing中組件UI的更新,都會形成事件置於事件隊列,並等待EDT派發,也就是UI更新依賴EDT線程完成。如果你的事件處理程序太耗時了,那么,UI就很久得不到及時更新,造成界面假死現象。
下面這個程序中,用戶點擊下載按鈕后,真個界面都失去了響應,按鈕久久不能彈起,窗口也失去了響應,體驗很糟糕。
class BadFrame extends JFrame { public BadFrame { super; initUI; } private JButton downloadButton ; private JPanel mainPane ; private void initUI { mainPane = new JPanel; mainPane.setPreferredSize(new Dimension(430,250)); this.setContentPane(mainPane); downloadButton = new JButton(‘下載’); this.getContentPane.add(downloadButton); downloadButton.addActionListener(new ActionListener { @Override public void actionPerformed(ActionEvent e) { downloadMovie; } }); this.pack; } //模擬下載任務 private void downloadMovie { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace; } }}
二、不要在非EDT線程中訪問UI,操作UI組件。
Swing組件都不是線程安全的,只有把他們的操作限制在一個線程中,才能保證所有的UI的操作都符合預期。這個線程就是EDT線程。那么,怎樣將UI操作發送到EDT中執行呢?
通過以下之一。
1 SwingUtilities.invokeLater(new Runnable {2 3 @Override4 public void run {5 6 7 }8 });
9
1 SwingUtilities.invokeAndWait(new Runnable {2 3 @Override4 public void run {5 // TODO Auto-generated method stub6 7 }8 });
9
他們有什么區別?
SwingUtilities.invokeLater調用后立即返回。然后執行第9行后的代碼。其他線程和 invokeLater中的參數線程異步執行。互不阻塞。
SwingUtilities.invokeAndWait調用后,必須等到 線程對象 run方法在EDT中執行完了,才返回,然后繼續執行第9行后的代碼。
下面是一個簡單的例子:用戶輸入2個整數 start ,end,程序計算從start 累加到end 的結果。我依然使用了線程睡眠來模擬耗時任務。因為如果我使用更加貼近現實的例子的話,又會引出更多的知識點。
雖然簡單,但說明了如何讓Swing更好的工作。
import java.awt.Dimension;import java.awt.GridLayout;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JOptionPane;import javax.swing.JPanel;import javax.swing.JTextField;import javax.swing.SwingUtilities;public class Demo { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable { @Override public void run { MFrame frame = new MFrame; frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); }}class MFrame extends JFrame{ public MFrame { initUI; onButtonClick; } /Model/ private int start = 0; private int end = 0; private int result = 0; /View*/ private JButton calcButton = null; private JTextField startField = null; private JTextField endField = null; private JTextField resultField = null; private JPanel mainpane = null; private void initUI { calcButton =new JButton(‘計算’); startField = new JTextField; startField.setColumns(5); endField = new JTextField; endField.setColumns(5); resultField = new JTextField; resultField.setColumns(5); resultField.setEditable(false); mainpane = new JPanel(new GridLayout(1, 4,5,0)); mainpane.setPreferredSize(new Dimension(300,50)); mainpane.add(startField); mainpane.add(endField); mainpane.add(resultField); mainpane.add(calcButton); this.setContentPane(mainpane); this.setLocationRelativeTo(null); this.pack; } //為button注冊監聽者 private void onButtonClick { calcButton.addActionListener(new ActionListener { @Override public void actionPerformed(ActionEvent event) { Thread calcThread = new Thread(new Runnable { @Override public void run { try{ start = Integer.parseInt(startField.getText); end = Integer.parseInt(endField.getText); for (int i = start; i <=end; i++)="" {="" result="" +=“i;” 假設計算過程十分耗時,就像挖礦一樣。="" thread.sleep(500);="" }="" 耗時任務完成后了,通過swingutilities.invokelater將設置任務到ui的事件發送到edt線程中。="" swingutilities.invokelater(new="" runnable="" {="" @override="" public="" void="" run="" {="" resultfield.settext(result+’’);="" }="" });="" }="" catch(numberformatexception="" e)="" {="" joptionpane.showmessagedialog(mframe.this,="" ‘請輸入一個合法的整數’,="" ‘錯誤’,="" joptionpane.error_message);="" }="" catch="" (interruptedexception="" e)="" {="" system.out.println(‘計算時錯誤’);="" }="" }="" });="" wrok="" thread="" new="" end="" calcthread.start;="" 啟用任務線程="" }="" });="">更優雅的解決辦法:SwingWorker線程類
當Swing程序復雜后,自定義線程會讓代碼越來越龐大,不好理解。於是jdk1.6中引入了SwingWorker線程類,簡化了程序員的工作。今天就寫到這里,我會在以后的文章中介紹。😃
案例參考:
https://blog.csdn.net/hza419763578/article/details/80690689
https://blog.csdn.net/paullinjie/article/details/51728930
補充:http://www.360doc.com/content/19/1212/21/67887324_879362148.shtml