用Swing编写敏捷的图形用户界面
副标题#e#
不敏捷的图形用户界面会低落应用措施的可用性。当以下现象呈现的时候,我们凡是说这个用户界面回响不敏捷。
不响应事件的现象;
没有更新的现象;
这些现象在很洪流平上与事件的处理惩罚要领相关,而在编写Swing应用措施的时候,我们险些一定要编写要领去响应鼠标点击按钮,键盘回车等事件。在这些要领中我们要编写一些代码,在运行时去触发一些行动。常见行动包罗查找,更新数据库等。在这篇文章中通过对一个实例的阐明,先容了一些根基观念,常见的错误以及提出了一个办理方案。
event-dispatching thread
我们必然要记着,事件响应要领的代码都是在event-dispatching thread中执行的,除非你启用另一个线程。
那么,什么是event-dispatching thread呢?单一线程法则:一旦一个Swing组件被实现(realized),所有的有大概影响或依赖于这个组件的状态的代码都应该在event-dispatching thread中被执行。而实现一个组件有两种方法:
对顶层组件挪用show(), pack(), 可能setVisible(true);
将一个组件加到一个已经被实现的容器中。
单一线程法则的来源是由于Swing组件库的大部门要领是对多线程不安详的。
为了支持单一线程模子,Swing组件库提供了一个专门来完成这些与Swing组件相关的操纵的线程,而这一线程就是event-dispatching thread。我们的事件响应要领凡是都是由这一线程挪用的,除非你本身编写代码来挪用这些事件响应要领。在这里初学者常常犯的一个错误就是在事件响应要领中完成过多的与修改组件没有直接接洽的代码。其最有大概的结果就是导致组件回响迟钝。好比以下响应按钮事件的代码:
String str = null;
this.textArea.setText("Please wait...");
try {
//do something that is really time consuming
str = "Hello, world!";
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.textArea.setText(str);
执行之后的结果就是按钮好像定住了一段时间,直到Done.呈现之后才弹起来。原因就是Swing组件的更新和事件的响应都是在event-dispatching thread中完成的,而事件响应的时候,event-dispatching thread被事件响应要领占据,所以组件不会被更新。而直到事件响应要领退出时才有大概去更新Swing组件。
为了办理这个问题,有人也许会试图通过挪用repaint()要领来更新组件:
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.repaint();
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
jTextArea1.setText(str[0]);
可是这一个要领没有起到预期的浸染,按钮仍然定住一段时间,在察看了repaint()要领的源代码之后就知道原因了。
PaintEvent e = new PaintEvent(this, PaintEvent.UPDATE,
new Rectangle(x, y, width, height));
Toolkit.getEventQueue().postEvent(e);
#p#副标题#e#
repaint()要领实际上是在事件行列里加了一个UPDATE的事件,而没有直接去重画组件,并且这一个事件只能期待当前的事件响应要领竣事之后才气被分派。因此只有绕过度派机制直接挪用paint要领才气到达目标。
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.paint(this.getGraphics());
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
jTextArea1.setText(str[0]);
这样却是实现了更新,可是还存在着以下的问题。固然从感受上,按钮已经弹起来了,可是在Done.呈现之前,我们却无法按下这个按钮。可以说按钮照旧定住了,只不外定在了弹起的状态。挪用重绘要领无法从基础上办理问题,因此我们需要寻求其他的要领。
利用多线程
有效的办理要领是利用多线程。首先看一看一个更好的办理方案,这一方案是在参考《Rethinking Swing Threading》的一个措施片断完成的:
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.repaint();
new Thread() {
public void run() {
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
jTextArea1.setText(str[0]);
}
});
}
}.start();
#p#分页标题#e#
在这个措施中,要耗费大量时间的操纵被放到另一个线程傍边,从而使事件响应要领能快速返回,event-dispatching thread就可以更新UI和响应其它事件了。留意到这个措施利用了invokeLater()要领。invokeLater()要领的浸染是让event-dispatching thread去运行拟定的代码。虽然也可以不利用invokeLater()要领,可是这样就违背了单一线程原则,同时带来了必然水平的相对多线程的不安详性。到此刻,办理方案好像是完美的了,可是我们看一看在本来的措施添加下面的代码,尽量我们凡是不这样做。
public void paint(java.awt.Graphics g) {
super.paint(g);
g.drawRect(1, 1, 100, 100);
}
我们会发明以前画的矩形被包围了一部门,原因是由于我们没用重画这一个矩形,因此在末了加上对repaint()要领的挪用。
final String[] str = new String[1];
this.jTextArea1.setText("Please wait...");
this.repaint();
new Thread() {
public void run() {
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
str[0] = "Done.";
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
jTextArea1.setText(str[0]);
repaint();
}
});
}
}.start();
假如你认为这段代码过于缺乏可读性,可以通过SwingWorker来简化编程的要领。可以通过实现一个construct()要领来实现耗费大量时间的操纵和重写finished()要领来完成组件更新的事情。
this.jTextArea1.setText("Please wait...");
final SwingWorker worker = new SwingWorker() {
public Object construct() {
try {
Thread.sleep(1000L);
}catch(InterruptedException e) {
e.printStackTrace();
}
return "Done.";
}
public void finished() {
jTextArea1.setText(getValue().toString());
repaint();
}
};
worker.start();
以上的编程方法可以称为同步方法。别的作者提出了一个通过动静机制来实现沟通成果的更清晰,可是需要编写更多代码的"异步"的要领。
结论
总之,我们在编写利用Swing组件的措施是要记着以下几点:
1、不要过多地占用event-dispatching thread;
2、与更新组件相关的代码要利用event-dispatching thread去执行;
3、要更新组件。
编写回响敏捷的图形用户界面还需要思量许多问题,以上只是最根基的一部门。接待有乐趣的读者来信举办接头。