2010-11-09 47 views
1

我有一個Swing程序,其中的工作是在非Swing線程中不斷完成的。它通常需要更新一個JTextPane - 通常每秒多次。我意識到setText()需要從事件派發線程內部調用,但我無法弄清楚如何使這種情況順利進行。在多線程Swing程序中頻繁調用setText()

以下最小的完整示例儘可能接近使用PipedInputStream/PipedOutputStream對,但似乎每隔一秒左右更新一次屏幕。我不確定需要這麼長時間。

import java.awt.event.*; 
import javax.swing.*; 
import java.io.*; 

public class TextTest extends JFrame { 
    private JTextPane out = new JTextPane(); 
    private PipedInputStream pIn = new PipedInputStream(); 
    private PrintWriter pOut; 

    public TextTest() { 
     try { 
      pOut = new PrintWriter(new PipedOutputStream(pIn)); 
     } 
     catch (IOException e) {System.err.println("can't init stream");} 

     add(new JScrollPane(out)); 
     setSize(500, 300); 
     setDefaultCloseOperation(EXIT_ON_CLOSE); 
     setVisible(true); 

     // Start a loop to print to the stream continuously 
     new Thread() { 
      public void run() { 
       for (int i = 0; true; i++) { 
        pOut.println(i); 
       } 
      } 
     }.start(); 

     // Start a timer to display the text in the stream every 10 ms 
     new Timer(10, new ActionListener() { 
      public void actionPerformed (ActionEvent evt) { 
       try { 
        if (pIn.available() > 0) { 
         byte[] buffer = new byte[pIn.available()]; 
         pIn.read(buffer); 
         out.setText(out.getText() + new String(buffer)); 
        } 
       } 
       catch (IOException e) {System.err.println("can't read stream");} 
      } 
     }).start(); 
    } 

    public static void main(String[] args) { 
     new TextTest(); 
    } 
} 

我在執行這個錯誤嗎?對於如何從EDT外部持續更新JTextPane,我有完全錯誤的想法嗎?

回答

4

setText()「方法是線程安全的,儘管大多數Swing方法都不是,請參閱How to Use Threads以獲取更多信息。

附錄:作爲參考,這裏有一些other approaches更新在EDT。另外需要注意的是javax.swing.Timer的動作事件處理程序在EDT上執行。這裏是我的變化:

import java.awt.event.*; 
import javax.swing.*; 
import java.io.*; 
import javax.swing.text.DefaultCaret; 

public class TextTest extends JFrame { 

    private JTextArea out = new JTextArea(); 
    private PipedInputStream pIn = new PipedInputStream(); 
    private PrintWriter pOut; 

    public TextTest() { 
     try { 
      pOut = new PrintWriter(new PipedOutputStream(pIn)); 
     } catch (IOException e) { 
      System.err.println("can't init stream"); 
     } 

     DefaultCaret caret = (DefaultCaret) out.getCaret(); 
     caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); 

     add(new JScrollPane(out)); 
     setSize(300, 500); 
     setDefaultCloseOperation(EXIT_ON_CLOSE); 
     setVisible(true); 

     // Start a loop to print to the stream continuously 
     new Thread() { 

      public void run() { 
       for (int i = 0; true; i++) { 
        pOut.println(i); 
       } 
      } 
     }.start(); 

     // Start a timer to display the text in the stream every 10 ms 
     new Timer(10, new ActionListener() { 

      public void actionPerformed(ActionEvent evt) { 
       try { 
        out.append(String.valueOf((char) pIn.read())); 
       } catch (IOException e) { 
        e.printStackTrace(); 
       } 
      } 
     }).start(); 
    } 

    public static void main(String[] args) { 
     new TextTest(); 
    } 
} 
+0

您的鏈接是404頁面。 – jjnguy 2010-11-09 21:53:39

+0

@jjnguy:我不知道爲什麼API鏈接是404-oracled,但版本7已經放棄了線程安全附帶條件;請考慮恢復您的答案,因爲這可能是更好的選擇。 – trashgod 2010-11-09 21:56:20

+0

我不知道!我也不確定它是否解決了這個問題 - 這兩個線程仍然以某種方式破壞對方。我嘗試切換到一個StringBuilder,並重復從工作線程調用setText(),但顯示器是閃爍的,並且JScrollPane從不滾動到底部。如果你願意,我可以發佈更新的代碼,但我認爲我仍然需要一個從EDT更新的解決方案。 – Etaoin 2010-11-09 22:15:04

2

但這似乎只更新屏幕每秒一次左右。我不確定需要這麼長時間。

System.out.println(pIn.available()); 

我添加上述聲明定時器的actionPerformed代碼。直到緩衝區達到1024字節,纔會發生什麼。所以不知何故,我猜你需要更改緩衝區大小。

此外,你不應該使用setText()。每次更改時重新創建文檔效率不高。

你可以使用:

out.replaceSelection(new String(buffer)); 

還是比較常見的方法是使用:

Document doc = textPane.getDocument(); 
doc.insertString("...", doc.getLength(), null); 

不要以爲insertString()方法是線程安全的,但replaceSelection()方法是。

編輯:

剛試過爲10,輸入流中的緩衝區大小播放和沖洗輸出流,並沒有任何區別,所以我想我不明白管道流。

+0

'JTextArea'和'append() '是另一種選擇。 – trashgod 2010-11-09 22:23:51

+0

謝謝,這看起來很有希望!奇怪的是,我嘗試將緩衝區大小調整爲1,作爲測試 - 更新以完全相同的速度進行,現在每更新一個字符。所以我不認爲這是緩衝區填滿。對不起,如果我揭露我的無知;我從來沒有嘗試過使用PipedInputStream/PipedOutputStream。 – Etaoin 2010-11-09 22:34:47

+0

@Etaoin,請參閱trashgod的更新以讀取沒有緩衝區的數據。 – camickr 2010-11-09 22:46:05

0

正確的教程鏈接,併發性和Swing看起來像它在這裏:Lesson: Concurrency in Swing

@camickr:setText不創建一個新文檔,它有效地做任何這樣的:

doc.replace(0, doc.getLength(), s, null); 

或本:

doc.remove(0, doc.getLength()); 
doc.insertString(0, s, null); 

我並不是說它很高效,但...

另一件事,setText做的是引起revalidate()repaint()將發行(setDocument呢,不過)。在撥打setText之後添加這兩個電話可能是值得的。

+0

實際上,這會創建一個新的Document,因爲您放棄了爲了表示Document而創建的Document結構(即Elements)。此外,可能與其關聯的任何屬性都會丟失。然後您需要重新分析整個文本以重建文檔結構。 – camickr 2010-11-09 22:37:57

2

您需要刷新printWriter的輸出,並且我建議在您的線程中稍微暫停一下,因爲它有一個緊密的for循環讓更新線程稍後踢一下。

pOut.println(i); 
pOut.flush(); 
try { 
     sleep(10); 
} catch (InterruptedException e) { 
} 

這會給出更流暢的流程。