Re: [問題] GUI的動作寫在遞迴裡面

看板java作者 (George Peng)時間13年前 (2013/01/08 20:10), 編輯推噓1(101)
留言2則, 2人參與, 最新討論串4/4 (看更多)
※ 引述《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
推這篇... SwingWorker好用
01/08 22:18, 1F

01/11 23:55, , 2F
可以完全不改CountDown class直接套進SwingWorker裡使用
01/11 23:55, 2F
文章代碼(AID): #1Gx0oe8r (java)
文章代碼(AID): #1Gx0oe8r (java)