作者gp123 (George Peng)
看板java
标题Re: [问题] GUI的动作写在递回里面
时间Tue Jan 8 20:10:13 2013
※ 引述《sbrhsieh (十年~)》之铭言:
: 通常 GUI 回应不够及时(常见於提问中的说法是:中间变化略过直接显示最後的
: 状态),是由於在 UI Thread 做了耗时间的事,若这些事里头包含了操作 UI 组件
: 或变更组件资料/状态,就会出现『常见说法』中的情况。
: 这不是由於 UI Thread 的 priority 太低所致。
: 原帖主希望在一个递回函式的执行过程中由 UI 组件显示执行的状态(也许是每一个
: call stack 的参数或某些状态),要这样子做来帮助了解递回函式执行的流程(教学
: 用的程式?)势必要在递回函式中加入 delay,否则程式执行的速度很快,肉眼是
: 无法观察到过程中 UI 组件显示的即时资讯(人的反应太慢)。
: 下面是一个以递回函式来做倒数的 demo(对,很矫情的 demo),它显示出一般
: 要做一件耗时的事情又需要在过程中更新 UI 组件来显示其 progress 的常见做法。
: 这只是一个用来点出某些观念(我在上一篇的推文)的demo,所以暂时请不要讲究
: 这倒数精不精确或其他小细节。
: CountDownDemo.java(网页版: http://ideone.com/VEY1io )
: CountDown.java(网页版: http://ideone.com/edwtgI )
个人凭着久远的错误印象,在未经查证的状况下回覆。
很感谢sbrhsieh的回应,发现这问题,并提醒我将来要更谨慎。
以下参考自Java SE的官方文件 Swing's Threading Policy总结一下
Ref:
http://docs.oracle.com/javase/7/docs/api/javax/swing/package-summary.html
Swing元件并非thread-safe,
基本上所有更改Swing元件状态的动作都应该要在Swing Thread中完成,
且不能花太长的时间执行,以免拖垮Swing元件的重绘。
但因为actionListener等AWT/Swing的event handling通常预设在Swing Thread中执行,
所以一般我们不会查觉到这问题,
通常问题会发生在:
(1)当Dialog或Frame显示後,仍直接在原本的worker thread中
(即没有在event handler)中操作Swing component,
这可能会造成Swing元件不可预期的状态。
(2)操作Swing执行时间太久,这会拖垮元件重绘/反应的时间,
此即原PO者的问题所在。
如果要在长时间的工作中操作Swing元件,
最好将操作Swing元件的部分单独在Swing thread中执行,
sbrshieh的作法即是标准作法,
而这会牵涉到Thread的建立/执行/沟通,
另一种作法是使用SwingWorker(详情请见API说明),
可以省下自行操作执行绪的工作。
以下用SwingWorker改写原s大的倒数计时程式,
功能差不多(但改成只能倒数一次,以集中问题焦点),
在部份程式码加注与原写法的不同:
package swingworkertest;
import java.util.List;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingWorker;
public class CountDownDemo_SwingWorker {
public static void main(String[] args) {
JFrame frm = new JFrame("Demo for Java@ptt");
frm.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
final JLabel messageBar = new JLabel("", JLabel.CENTER);
messageBar.setBorder(BorderFactory.createLoweredBevelBorder());
messageBar.setFont(messageBar.getFont().deriveFont(48f));
frm.getContentPane().add(messageBar);
final JButton start = new JButton("开始倒数 10 秒");
frm.getContentPane().add(start, BorderLayout.SOUTH);
final CountDown counter = new CountDown(10, messageBar);
start.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
start.setEnabled(false);
counter.execute(); // 原为 counter.asyncStart(10);
// counter.get(); // 原为counter.syncStart(10);
}
});
frm.setSize(400, 400);
frm.setLocationRelativeTo(null);
frm.setVisible(true);
}
}
class CountDown extends SwingWorker<Integer,Integer> {
private int m_sec;
private JLabel m_message;
CountDown(int seconds, JLabel message) {
m_sec = seconds;
m_message = message;
}
@Override
protected Integer doInBackground() throws Exception {
count(m_sec);
return 0;
}
private void count(int timeInSecs)
throws InterruptedException {
if (timeInSecs <= 0) {
publish(0); // 原为 listener.onTick(0);
return;
}
publish(timeInSecs); // 原为 listener.onTick(timeInSecs);
Thread.sleep(1000);
count(timeInSecs - 1);
}
// 以下原为 counter.setTickListener(new CountDown.OnTickListener() {...}
@Override
protected void process(List<Integer> intermediate_data) {
if (!intermediate_data.isEmpty())
m_message.setText(String.format("%d 秒",
intermediate_data.get(intermediate_data.size()-1)));
}
}
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 140.114.88.25
1F:推 popcorny:推这篇... SwingWorker好用 01/08 22:18
2F:→ sbrhsieh:可以完全不改CountDown class直接套进SwingWorker里使用 01/11 23:55