- PR -

コンソール出力について

投稿者投稿内容
nagise
ぬし
会議室デビュー日: 2006/05/19
投稿数: 1141
投稿日時: 2007-05-24 15:00
あ。
もしかしたらrepaint()でよかったりしませんか?

# Swingをまじめに設計するのは難度が高いですね…。
toshi
会議室デビュー日: 2007/05/24
投稿数: 11
投稿日時: 2007-05-24 15:34
nagise様

> もしかしたらrepaint()でよかったりしませんか?

はい、実は私もそう思ってprivate void appendString()の中でrepaintやってみましたがダメでした.

public class JTextAreaOutputStream extends ByteArrayOutputStream {
private JTextArea jtextArea;
private Rectangle rectangle;

public JTextAreaOutputStream(JTextArea jtextArea){
super();
this.jtextArea=jtextArea;
this.rectangle=jtextArea.getBounds(null);
}

public synchronized void write(byte[] b, int off, int len){
super.write(b, off, len);
appendString();
}

public synchronized void write(byte[] b) throws IOException{
super.write(b);
appendString();
}

public synchronized void write(int b){
super.write(b);
appendString();
}
/**
* Append text to JTextArea and reset caret position
*/
private void appendString(){
jtextArea.append(this.toString());
int length=jtextArea.getDocument().getLength();
jtextArea.setCaretPosition(length);
jtextArea.repaint(rectangle);
this.reset();
}
}


toshi
会議室デビュー日: 2007/05/24
投稿数: 11
投稿日時: 2007-05-24 16:22
以下のようにやってみましたがうまくいきません.

1.メインウィンドウのJFrameを変更

public class xxxxFrame extends JFrame implements Runnable

2.コンストラクタでスレッドを生成

new Thread(this).start();

3.run()メソッドを実装

public void run(){
final JTextArea jtextAreaMain=this.jTextAreaMain;
while(true){
SwingUtilities.invokeLater(new Runnable() {
public void run(){
Rectangle rectangle = jtextAreaMain.getBounds(null);
jtextAreaMain.repaint(rectangle);
}
});
try{
Thread.sleep(1000);
System.out.println("Wakeup after sleep 1000ms.");
}
catch (InterruptedException e){
break;
}
}
}

4.各処理ステップの最後にはThread.yield()を入れる.

これを実行するとポツポツと"Wakeup after sleep 1000ms."のメッセージがメインウィンドウに表示されます.しかしパラメータウィンドウを開いて、実行ボタンを押すとこの"Wakeup after sleep 1000ms."のメッセージも止まってしまい、処理の終了後に一気にログと一緒に表示されます.なかなかうまくいきません.

unibon様
> 良くは見ていませんが、普通、長い処理中に描画をおこなうためには、
> その処理はマルチスレッドで動かさないといけません。
> (Thread や Runnable がキーワードになります。)

御指摘のとおりでした.元がLinuxのシェルで実行していたものをGUI化するということで簡単に考えていたのが間違いの元でした.しかし、「長い処理」を別スレッドで実行するようにした場合、その中で行っているSystem.out.println()をJTextAreaに入れるような処理をJTextAreaOutputStreamクラスでやってしまうと、飴@玉様御指摘のhttp://wisdom.sakura.ne.jp/system/java/swing/swing4.htmlにある、「一般的に Swing はシングルスレッド設計であり、他のスレッドが介入してはいけない
逆に言えば、Swing は常に一つのスレッドからのみアクセスすることができます」を破ってしまうように思えるのですがどうでしょうか?

以上

nagise
ぬし
会議室デビュー日: 2006/05/19
投稿数: 1141
投稿日時: 2007-05-24 16:52
とりあえず、invokeLaterの使い方がちょっと不自然ですね。
ループでまわしながら定期的に再描画しているようですが、
そこまでする必要はありません。

swingのイベント処理や描画処理というのはEDT(イベントディスパッチスレッド)
という単一のスレッドで処理されます。
各種処理をRunnableの形でキューに入れて先頭から取り出しながら
順次処理されているイメージなんですが、わかるでしょうか?

で、そのキューにinvokeLaterで処理を追加するわけです。
そうすることで、swingが裏でやっているイベント処理や描画と
同期が取られるという寸法です。(同期というとちょっと違いますか…)

提示されているコードが部分抜粋なので具体的な原因が検証できないのですが、
swingではステータスの変更はEDTでやるべし、というルールがあるのですが
それを外しているために、ステータスと描画状態が一致しないという
現象のように思います。

どの段階でずれが生じているかですが、
1.そもそもOutputStreamへの書き出しがflush()されていない
2.Streamの状態変更はされているが、描画がされていない
のどちらなのかをSystem.outを利用したデバッグで切り分けておくべきでしょう。
未記入
ぬし
会議室デビュー日: 2004/09/17
投稿数: 667
投稿日時: 2007-05-24 19:18
SwingUtilities.invokeLater() の説明は概ね正しいのですが、ここで SwingUtilities.invokeLater() を使えというのは半分 不正解ですよ。50点。

なぜかというと、ボタン押下によって開始された処理自体が イベントディスパッチスレッドで実行されていると推測されるからです。JButton.addActionListener() で登録したイベントリスナは、イベントディスパッチスレッドで実行されるのです。だから、この状態でキューにイベントを追加してもイベントは消費されない(ディスパッチされない)状態です。

イベントリスナからはすぐに復帰しなさい。イベントリスナの中のコードを実行しているあいだイベントディスパッチスレッドが停止しています。GUI 再描画がおこらない GUI コンポーネントが操作できないという状態になります。(VB6と一緒)

イベントリスナから長時間かかる処理を呼び出す場合は、別のスレッドを起こしなさい。途中で GUI に何かを反映したかったら、別におこしたスレッドから SwingUtilities.invokeLater() を呼び出しなさい。

コード:
	JTextField t = new JTextField();
	JButton b = new JButton();
	b.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent e) {
			new Thread() {
				@Override
				public void run() {
					//長時間かかる処理はここに書く。
					final String progress = "hoge";
					
					//GUIを触るときは、invokeLater を使う。
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							t.setText(progress);
						}
					});
				}
			}.start();
		}
	});



こんな感じになります。1.6 以上なら SwingWorker を使ってもっとスマートに書けます。それから SwingUtilities.isEventDispatchThread() を使って、現在のコードがイベントディスパッチスレッドで実行されているかどうかを確認することができます。長時間かかるコードで、SwingUtilities.isEventDispatchThread() の値を見てみなさい。true を返していたら、コードの書き直しです。false を返していたなら、おめでとう。
toshi
会議室デビュー日: 2007/05/24
投稿数: 11
投稿日時: 2007-05-24 19:29
nagises様

御回答ありがとうございます.

>どの段階でずれが生じているかですが、
>1.そもそもOutputStreamへの書き出しがflush()されていない
>2.Streamの状態変更はされているが、描画がされていない
>のどちらなのかをSystem.outを利用したデバッグで切り分けておくべきでしょう。

1.については、PrintStreamの生成時に自動フラッシュを指定しているので、それを信じるしか無いように思えます.でやはり原因は2だと考えました.現在のプログラムは、処理の実行ボタンを押したとき、そのイベント処理の中で直接「長い処理」に入ってしまっています.またそもそも、invokeLater()はイベントディスパッチスレッドとは別のスレッドから描画を更新するためのものでした.

そこで、「長い処理」を別スレッドで行うように書き換えて、System.setOut(), System.setErrorで切り替えるクラス(ここで書いたJTexteAreaOutputStream)を別スレッドからのアクセスを前提として以下のように書き換えてみました.

public class JTextAreaOutputStream extends ByteArrayOutputStream {
private JTextArea jtextArea;
private AddToTextArea addTextArea;

public JTextAreaOutputStream(JTextArea jtextArea){
super();
this.jtextArea=jtextArea;
addTextArea=new AddToTextArea();
this.addTextArea.setJtextArea(this.jtextArea);
}

public synchronized void write(byte[] b, int off, int len){
super.write(b, off, len);
appendString();
this.reset();
}

public synchronized void write(byte[] b) throws IOException{
super.write(b);
appendString();
this.reset();
}

public synchronized void write(int b){
super.write(b);
appendString();
this.reset();
}
/**
* Append text to JTextArea and reset caret position
*/
private synchronized void appendString(){
this.addTextArea.setOutputStr(this.toString());
SwingUtilities.invokeLater(this.addTextArea);
}
}

class AddToTextArea implements Runnable{
private JTextArea jtextArea;
private String outputStr;
public void setJtextArea(JTextArea jtextArea){
this.jtextArea=jtextArea;
}
public void setOutputStr(String outputStr){
this.outputStr=outputStr;
}
public void run(){
jtextArea.append(outputStr);
int length=jtextArea.getDocument().getLength();
jtextArea.setCaretPosition(length);
}
}

結果ですが、なんともはやです.

処理ボタンを押すのと同時にメインウィンドウのテキストエリアには処理進行とほぼ同期してログが出力されるようになりました.問題は内容です.従来の最後に一気に出る場合と比較すると、メッセージが大幅に異なるようになってしまいました.改行ばかりやたらと出てしまったり、本来改行するメッセージが改行せずに出てしまったり、抜けるメッセージがあったりという具合です.

どうも初心者には手におえません.

#困った!困った!

以上
朝日奈ありす
大ベテラン
会議室デビュー日: 2007/05/02
投稿数: 189
お住まい・勤務地: 最北の地
投稿日時: 2007-05-24 19:48
1.System〜でコンソールに正常出力されているか?
2.FileWriterで書き出して、動作中に出力されているか?
等をみると吉
toshi
会議室デビュー日: 2007/05/24
投稿数: 11
投稿日時: 2007-05-24 20:21
いろいろと初心者にアドバイスをいただきどうもありがとうございます.

未記入様

> 長時間かかるコードで、SwingUtilities.isEventDispatchThread() の値を
> 見てみなさい。true を返していたら、コードの書き直しです。false を返
> していたなら、おめでとう。

別のスレッドで処理するようにしましたので良いはずですが、その処理の最初でSwingUtilities.isEventDispatchThread()==falseの場合は何もしないで戻るようにしました.処理は続行されたので、とりあえずこの問題はクリアされたと思います.

杏様

>1.System〜でコンソールに正常出力されているか?
>2.FileWriterで書き出して、動作中に出力されているか?

1. は試してみました.コード中のSystem.setOut(), System.setErr()をコメントアウトすると、DOS窓に全メーっセージが正常にパラパラと出力されます.

2.についてはごもっともです.試してみます.

以上

スキルアップ/キャリアアップ(JOB@IT)