在官方的文章里:http://docs.oracle.com/javase/tutorial/uiswing/painting/step1.html 告诉我们要创建一个gui。
All Graphical User Interfaces require some kind of main application frame in which to display. In Swing, this is an instance ofjavax.swing.JFrame
. Therefore, our first step is to instantiate this class and make sure that everything works as expected. Note that when programming in Swing, your GUI creation code should be placed on the Event Dispatch Thread (EDT). This will prevent potential race conditions that could lead to deadlock. The following code listing shows how this is done.
import javax.swing.SwingUtilities; import javax.swing.JFrame; public class SwingPaintDemo1 { public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); } private static void createAndShowGUI() { System.out.println("Created GUI on EDT? "+ SwingUtilities.isEventDispatchThread()); JFrame f = new JFrame("Swing Paint Demo"); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); f.setSize(250,250); f.setVisible(true); } }
This creates the frame, sets its title, and makes everything visible. We have used the SwingUtilities
helper class to construct this GUI on the Event Dispatch Thread. Note that by default, a JFrame
does not exit the application when the user clicks its "close" button. We provide this behavior by invoking the setDefaultCloseOperation
method, passing in the appropriate argument. Also, we are explicity setting the frame's size to 250 x 250 pixels. This step will not be necessary once we start adding components to the frame.
事件分发线程:
Swing event-handling and painting code executes in a single thread, called the event-dispatching thread. This ensures that each event handler finishes executing before the next one executes and that painting isn't interrupted by events. To avoid the possibility of deadlock, you must take extreme care that Swing components and models are created, modified, and queriedonly from the event-dispatching thread.
Note: We used to say that you could create the GUI on the main thread as long as you didn't modify components that had already been realized. [PENDING: The following red text belongs in a footnote.] Realized means that the component has been painted onscreen, or is ready to be painted. The methodssetVisible(true)
andpack
cause a window to be realized, which in turn causes the components it contains to be realized. While this worked for most applications, in certain situations it could cause problems. Out of all the demos in the Swing Tutorial, we encountered a problem only in ComponentEventDemo. In that case, sometimes when you launched the demo, it would not come up because it would deadlock when updating the text area if the text area had not yet been realized, while other times it would come up without incident.To avoid the possibility of thread problems, we recommend that you use
invokeLater
to create the GUI on the event-dispatching thread for all new applications. If you have old programs that are working fine they are probably OK; however you might want to convert them when it's convenient to do so.
You have probably noticed that most of the tutorial's demos use a standardized main
method that calls the SwingUtilities
methodinvokeLater
to ensure that the GUI is created on the event-dispatching thread. Here is an example of the main
method from the FocusConceptsDemo example. We have also included the source for createAndShowGUI
— a private, static method called by eachmain
method where the creation of the GUI is handled.
/** * Create the GUI and show it. For thread safety, * this method should be invoked from the * event-dispatching thread. */ private static void createAndShowGUI() { //Make sure we have nice window decorations. JFrame.setDefaultLookAndFeelDecorated(true); //Create and set up the window. frame = new JFrame("FocusConceptsDemo"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //Create and set up the content pane. JComponent newContentPane = new FocusConceptsDemo(); newContentPane.setOpaque(true); //content panes must be opaque frame.setContentPane(newContentPane); //Display the window. frame.pack(); frame.setVisible(true); } public static void main(String[] args) { //Schedule a job for the event-dispatching thread: //creating and showing this application's GUI. javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { createAndShowGUI(); } }); }
Using the invokeLater Method
You can call
invokeLater
from any thread to request the event-dispatching thread to run certain code. You must put this code in therun
method of aRunnable
object and specify theRunnable
object as the argument toinvokeLater
. TheinvokeLater
method returns immediately, without waiting for the event-dispatching thread to execute the code. Here's an example of usinginvokeLater
:Runnable updateAComponent = new Runnable() { public void run() { component.doSomething(); } }; SwingUtilities.invokeLater(updateAComponent);
invokeLater必须放在run()方法体内。
Using the invokeAndWait Method
The
invokeAndWait
method is just likeinvokeLater
, except thatinvokeAndWait
doesn't return until the event-dispatching thread has executed the specified code. Whenever possible, you should useinvokeLater
instead ofinvokeAndWait
— it is very easy to cause a deadlock usinginvokeAndWait
. If you useinvokeAndWait
, make sure that the thread that callsinvokeAndWait
does not hold any locks that other threads might need while the call is occurring.Here's an example of using
invokeAndWait
:void showHelloThereDialog() throws Exception { Runnable showModalDialog = new Runnable() { public void run() { JOptionPane.showMessageDialog(myMainFrame, "Hello There"); } }; SwingUtilities.invokeAndWait(showModalDialog); }Similarly, a thread that needs access to GUI state, such as the contents of a pair of text fields, might have the following code:
void printTextField() throws Exception { final String[] myStrings = new String[2]; Runnable getTextFieldText = new Runnable() { public void run() { myStrings[0] = textField0.getText(); myStrings[1] = textField1.getText(); } }; SwingUtilities.invokeAndWait(getTextFieldText); System.out.println(myStrings[0] + " " + myStrings[1]); }
Using Threads to Improve Performance
When properly used, threads can be a powerful tool. However, you must proceed with caution when using threads in a Swing program. Despite the dangers, threads can be invaluable. You can use them to improve your program's perceived performance. And sometimes threads can simplify a program's code or architecture. Here are some typical situations where threads are used:
- To move a time-consuming initialization task out of the main thread, so that the GUI comes up faster. Examples of time-consuming tasks include making extensive calculations and blocking for network or disk I/O (loading images, for example).
- To move a time-consuming task out of the event-dispatching thread, so that the GUI remains responsive.
- To perform an operation repeatedly, usually with some predetermined period of time between operations.
- To wait for messages from other programs.
If you need to create a thread, you can avoid some common pitfalls by implementing the thread with a utility class such as
SwingWorker
or one of theTimer
classes. ASwingWorker
object creates a thread to execute a time-consuming operation. After the operation is finished,SwingWorker
gives you the option of executing some additional code in the event-dispatching thread. Timers are useful for performing a task either repeatedly or after a specified delay. If you need to implement your own threads, you can find information on doing so in Concurrency.You can use several techniques to make multi-threaded Swing programs work well:
For information and examples of using timers, see How to Use Swing Timers.
- If you need to update a component but your code isn't executing in an event listener, use one of the two
SwingUtilities
methods:invokeLater
(preferred) orinvokeAndWait
.- If you aren't sure whether your code is executing in an event listener, then you should analyze your program's code and document which thread each method is (and can be) called from. Failing that, you can use the
SwingUtilities.isEventDispatchThread()
method, which returns true if your code is executing in the event-dispatching thread. You can safely callinvokeLater
from any thread, butinvokeAndWait
throws an exception if it's called from the event-dispatching thread.- If you need to update a component after a delay (whether or not your code is currently executing in an event listener), use a timer to do so.
- If you need to update a component at a regular interval, use a timer.
Using the SwingWorker Class
The
Note: The implementation of theSwingWorker
class has been updated twice, most recently in February 2000. The first update (in January 1999) allowed programs to safely interrupt the worker thread. The most recent update (called "SwingWorker 3") was to fix a subtle threading bug that could cause aNullPointerException
.SwingWorker
class is implemented inSwingWorker.java
, which is not in the Swing release. To use theSwingWorker
class, you first create a subclass of it. The subclass must implement theconstruct
method so that it contains the code to perform your lengthy operation. When you instantiate yourSwingWorker
subclass, theSwingWorker
creates a thread but does not (as of SwingWorker 3) start it. You then invokestart
on yourSwingWorker
object to start the thread, which then calls yourconstruct
method.Here is an example of using a
SwingWorker
to move a time-consuming task from an action event listener into a background thread, so that the GUI remains responsive.//OLD CODE: public void actionPerformed(ActionEvent e) { ... //...code that might take a while to execute is here... ... } //BETTER CODE: public void actionPerformed(ActionEvent e) { ... final SwingWorker worker = new SwingWorker() { public Object construct() { //...code that might take a while to execute is here... return someValue; } }; worker.start(); //required for SwingWorker 3 ... }The value that
construct
returns can be any object. If you need to get the value, you can do so by invoking theget
method on yourSwingWorker
object. Be careful about usingget
. Because it blocks, it can cause deadlock. If necessary, you can interrupt the thread (causingget
to return) by invokinginterrupt
on theSwingWorker
.If you need to update the GUI when the time-consuming operation completes, you can do so either by using
get
(which is dangerous, as we noted) or by overriding thefinished
method in yourSwingWorker
subclass. Thefinished
method runs after theconstruct
method returns. Because thefinished
method executes in the event-dispatching thread, you can safely use it to update Swing components. Of course, you shouldn't put time-consuming operations in yourfinished
implementation.The following example of implementing
finished
is taken fromIconDemoApplet.java
. For a full discussion of this applet, including how it improves perceived performance by using background threads to load images, see How to Use Icons.public void actionPerformed(ActionEvent e) { ... if (icon == null) { //haven't viewed this photo before loadImage(imagedir + pic.filename, current); } else { updatePhotograph(current, pic); } } ... //Load an image in a separate thread. private void loadImage(final String imagePath, final int index) { final SwingWorker worker = new SwingWorker() { ImageIcon icon = null; public Object construct() { icon = new ImageIcon(getURL(imagePath)); return icon; //return value not used by this program } //Runs on the event-dispatching thread. public void finished() { Photo pic = (Photo)pictures.elementAt(index); pic.setIcon(icon); if (index == current) updatePhotograph(index, pic); } }; worker.start(); }For more examples of using
SwingWorker
, go to How to Monitor Progress. Also,TumbleItem.java
, which is discussed in How to Make Applets, uses both aSwingWorker
and aTimer
.
via:http://www.ime.uerj.br/javatutor/uiswing/misc/threads.html
官方解释:
public static void invokeLater(Runnable doRun)
invokeLater
call queues the Runnable
object doHelloWorld
on the event dispatching thread and then prints a message.
Runnable doHelloWorld = new Runnable() { public void run() { System.out.println("Hello World on " + Thread.currentThread()); } }; SwingUtilities.invokeLater(doHelloWorld); System.out.println("This might well be displayed before the other message.");If invokeLater is called from the event dispatching thread -- for example, from a JButton's ActionListener -- the doRun.run() will still be deferred until all pending events have been processed. Note that if the doRun.run() throws an uncaught exception the event dispatching thread will unwind (not the current thread).
Additional documentation and examples for this method can be found in How to Use Threads, in The Java Tutorial.
As of 1.3 this method is just a cover for java.awt.EventQueue.invokeLater()
.
Unlike the rest of Swing, this method can be invoked from any thread.
- See Also:
invokeAndWait(java.lang.Runnable)
- SwingUtilities
- A collection of utility methods for Swing
- csdn的一篇文章:
-
现在我们要做一个简单的界面。
包括一个进度条、一个输入框、开始和停止按钮。
需要实现的功能是:
当点击开始按钮,则更新进度条,并且在输入框内把完成的百分比输出(这里只做例子,没有真正去做某个工作)。
import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JProgressBar; import javax.swing.JTextField; public class SwingThreadTest1 extends JFrame { private static final long serialVersionUID = 1L; private static final String STR = "Completed : "; private JProgressBar progressBar = new JProgressBar(); private JTextField text = new JTextField(10); private JButton start = new JButton("Start"); private JButton end = new JButton("End"); private boolean flag = false; private int count = 0; public SwingThreadTest1() { this.setLayout(new FlowLayout()); add(progressBar); text.setEditable(false); add(text); add(start); add(end); start.addActionListener(new Start()); end.addActionListener(new End()); } private void go() { while (count < 100) { try { Thread.sleep(100);//这里比作要完成的某个耗时的工作 } catch (InterruptedException e) { e.printStackTrace(); } //更新进度条和输入框 if (flag) { count++; progressBar.setValue(count); text.setText(STR + String.valueOf(count) + "%"); } } } private class Start implements ActionListener { public void actionPerformed(ActionEvent e) { flag = true;//设置开始更新的标志 go();//开始工作 } } private class End implements ActionListener { public void actionPerformed(ActionEvent e) { flag = false;//停止 } } public static void main(String[] args) { SwingThreadTest1 fg = new SwingThreadTest1(); fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); fg.setSize(300, 100); fg.setVisible(true); } }
运行代码发现,
现象1:当点击了开始按钮,画面就卡住了。按钮不能点击,进度条没有被更新,输入框上也没有任何信息。
原因分析:Swing是线程不安全的,是单线程的设计,所以只能从事件派发线程访问将要在屏幕上绘制的Swing组件。ActionListener的actionPerformed方法是在事件派发线程中调用执行的,而点击了开始按钮后,执行了go()方法,在go()里,虽然也去执行了更新组件的方法
progressBar.setValue(count);
text.setText(STR + String.valueOf(count) + "%"); 还可以参看:http://www.cnblogs.com/youxin/archive/2012/04/20/2459557.html
但由于go()方法直到循环结束,它并没有返回,所以更新组件的操作一直没有被执行,这就造成了画面卡住的现象。
现象2:过了一段时间(go方法里的循环结束了)后,画面又可以操作,并且进度条被更新,输入框也出现了我们想看到的信息。
原因分析:通过在现象1的分析,很容易联想到,当go()方法返回了,则其他的线程(更新组件)可以被派发了,所以画面上的组件被更新了。
为了让画面不会卡住,我们来修改代码,将耗时的工作放在一个线程里去做。
import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JProgressBar; import javax.swing.JTextField; public class SwingThreadTest2 extends JFrame { private static final long serialVersionUID = 1L; private static final String STR = "Completed : "; private JProgressBar progressBar = new JProgressBar(); private JTextField text = new JTextField(10); private JButton start = new JButton("Start"); private JButton end = new JButton("End"); private boolean flag = false; private int count = 0; GoThread t = null; public SwingThreadTest2() { this.setLayout(new FlowLayout()); add(progressBar); text.setEditable(false); add(text); add(start); add(end); start.addActionListener(new Start()); end.addActionListener(new End()); } private void go() { while (count < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (flag) { count++; System.out.println(count); progressBar.setValue(count); text.setText(STR + String.valueOf(count) + "%"); } } } private class Start implements ActionListener { public void actionPerformed(ActionEvent e) { flag = true; if(t == null){ t = new GoThread(); t.start(); } } } //执行复杂工作,然后更新组件的线程 class GoThread extends Thread{ public void run() { //do something... go(); } } private class End implements ActionListener { public void actionPerformed(ActionEvent e) { flag = false; } } public static void main(String[] args) { SwingThreadTest2 fg = new SwingThreadTest2(); fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); fg.setSize(300, 100); fg.setVisible(true); } }
我们执行了程序,结果和我们想要的一样,画面不会卡住了。
那这个程序是否没有问题了呢?
我们自定义了一个线程GoThread,在这里我们完成了那些耗时的工作,可以看作是“工作线程”,
而对于组件的更新,我们也放在了“工作线程”里完成了。
在这里,在事件派发线程以外的线程里设置进度条,是一个危险的操作,运行是不正常的。(对于输入框组件的更新是安全的。)
只有从事件派发线程才能更新组件,根据这个原则,我们来修改我们现有代码。
import java.awt.FlowLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JProgressBar; import javax.swing.JTextField; import javax.swing.SwingUtilities; public class SwingThreadTest3 extends JFrame { private static final long serialVersionUID = 1L; private static final String STR = "Completed : "; private JProgressBar progressBar = new JProgressBar(); private JTextField text = new JTextField(10); private JButton start = new JButton("Start"); private JButton end = new JButton("End"); private boolean flag = false; private int count = 0; private GoThread t = null; private Runnable run = null;//更新组件的线程 public SwingThreadTest3() { this.setLayout(new FlowLayout()); add(progressBar); text.setEditable(false); add(text); add(start); add(end); start.addActionListener(new Start()); end.addActionListener(new End()); run = new Runnable(){//实例化更新组件的线程 public void run() { progressBar.setValue(count); text.setText(STR + String.valueOf(count) + "%"); } }; } private void go() { while (count < 100) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } if (flag) { count++; SwingUtilities.invokeLater(run);//将对象排到事件派发线程的队列中 } } } private class Start implements ActionListener { public void actionPerformed(ActionEvent e) { flag = true; if(t == null){ t = new GoThread(); t.start(); } } } class GoThread extends Thread{ public void run() { //do something... go(); } } private class End implements ActionListener { public void actionPerformed(ActionEvent e) { flag = false; } } public static void main(String[] args) { SwingThreadTest3 fg = new SwingThreadTest3(); fg.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); fg.setSize(300, 100); fg.setVisible(true); } }
解释:SwingUtilities.invokeLater()方法使事件派发线程上的可运行对象排队。当可运行对象排在事件派发队列的队首时,就调用其run方法。其效果是允许事件派发线程调用另一个线程中的任意一个代码块。
还有一个方法SwingUtilities.invokeAndWait()方法,它也可以使事件派发线程上的可运行对象排队。
他们的不同之处在于:SwingUtilities.invokeLater()在把可运行的对象放入队列后就返回,而SwingUtilities.invokeAndWait()一直等待知道已启动了可运行的run方法才返回。如果一个操作在另外一个操作执行之前必须从一个组件获得信息,则应使用SwingUtilities.invokeAndWait()方法。via:http://blog.csdn.net/bzwm/article/details/3895381
http://www.ime.uerj.br/javatutor/uiswing/misc/threads.html
http://docs.oracle.com/javase/tutorial/essential/concurrency