Re: [問題] GUI的動作寫在遞迴裡面
※ 引述《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
推
01/08 22:18, , 1F
01/08 22:18, 1F
→
01/11 23:55, , 2F
01/11 23:55, 2F
討論串 (同標題文章)
本文引述了以下文章的的內容:
完整討論串 (本文為第 4 之 4 篇):
java 近期熱門文章
3
14
PTT數位生活區 即時熱門文章