- PR -

外部プログラム出力を JTextPane に挿入時、すぐに反映されない。

1
投稿者投稿内容
でつ
会議室デビュー日: 2006/03/28
投稿数: 4
お住まい・勤務地: 東京
投稿日時: 2007-12-31 13:37
はじめまして、趣味のプログラマ「でつ」です。宜しくお願いします。
Swing の JTextPane を使った簡単なコンソールプログラムを作っています。
動作自体はうまくいっているのですが、外部プログラムの実行が完了するまで JTextPane
には反映されなくて困っています。同じタイミングで System.out.println() は逐一表示
され思うような動作となっています。
実行するプログラムは何でもよく、この例では単純に10回メッセージを表示し、毎回
1秒スリープするといった他愛もないものです。
また[Exec]ボタンを押すと外部プログラムを実行するのですが、これまたプログラムの
実行が終了するまで、凹んだままというのも気になります。
なにぶん、初めての Swing なので大きくはずしている可能性もありますが、よろしく
お願いします。

ExecCon.java ============== ここから
コード:
// ExecCon
import java.io.*;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.text.*;

public class ExecCon extends JFrame 
{
	private JTabbedPane base = new JTabbedPane();

	JTextPane Console   = new JTextPane();
	JScrollPane Scr = new JScrollPane(Console,
		JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	StyleContext sc = new StyleContext();
	StyledDocument doc = (StyledDocument)Console.getDocument();

	protected void initDoc( JTextPane pan ) { pan.setText("Console:"); }

public ExecCon()
{
	this.getContentPane().add(Scr);
	Console.setDocument( doc );
	initDoc( Console );

	try {
		initTab0();
		this.setVisible(true);

		this.addWindowListener(new WindowAdapter() {
			public void windowClosing( WindowEvent e ) { System.exit(0); }
		});
	} catch(Exception e) {
		e.printStackTrace();
	}
}

///////////////////////////////////////////////////////////
private void initTab0() throws Exception
{
	JButton	btnExec    = new JButton("Exec");
	this.getContentPane().setLayout(null);
	this.setSize(new Dimension(400, 300));
	this.setTitle( "Exec and Console sample");

	Scr.setBounds(new Rectangle( 5, 40, 380, 200 ));
	this.getContentPane().add(base, null);

	btnExec.setBounds(new Rectangle(5, 10, 65, 20));
	btnExec.addActionListener( new ActionListener() {
		public void actionPerformed( ActionEvent e ) { actExec("perl test.pl"); }
	} );

	this.add(btnExec,null);
}

// srings add to console
public void appendDoc( String str )
{
	try {
		System.out.println("appendDoc:"+str);
		doc.insertString( doc.getLength(), str + "\\\\n",
			sc.getStyle(StyleContext.DEFAULT_STYLE) );
		Console.setCaretPosition(doc.getLength());
	} catch (BadLocationException ble){
		System.err.println("Document style initalize error.");
	}
}

public void actExec( String cmd )
{
	try {
		Process proc = Runtime.getRuntime().exec(cmd, null,null);
		InputStream is = proc.getInputStream();
		InputStreamReader isrs =  new InputStreamReader(is);
		BufferedReader ib = new BufferedReader(isrs, 10);

		byte[] ibuff = new byte[100];
		int	ilen;

		while ( true ) {
			ilen = is.read(ibuff);
			if( ilen < 0 ) break;
			appendDoc( new String( ibuff, 0, ilen ) );
		}
		proc.waitFor();
	} catch(Exception ex ) {
		appendDoc( "Err. Command("+cmd+") Execution error");
		ex.printStackTrace();
	}
}

public static void main(String args[]) {
        ExecCon obj = new ExecCon();
}

} // end of Class


=========================== ここまで
補足:test.pl
コード:
#! /usr/bin/perl
$|=1;
for(0..9) {
        print "* $_ ==============================\\n";
        sleep(1);
}

わたなべ
大ベテラン
会議室デビュー日: 2007/12/09
投稿数: 123
お住まい・勤務地: 札幌
投稿日時: 2007-12-31 14:23
SwingのEDTについて調べてみてください。

結論と方針だけを述べます。
方針としては、actExecの処理をSwingWorkerなどの別スレッドで実行するように改良しなくてはなりません。
現在の実装では、ボタンのクリックイベントの間、Swingの処理がactExecで占有されてしまっているような感覚です。
Swingでイベントドリブンな処理を行い、その処理に時間が掛かる場合にはActionListenerには「イベントが発生したぜ!」というトリガーのみを実装し、実処理は別スレッドに委譲します。
そうすれば、Swingの描画にすぐに処理が戻る為、ボタンがへこんだままとならない、となるわけです。

「Swing EDT SwingWorker」あたりのキーワードで検索してみましょう。
でつ
会議室デビュー日: 2006/03/28
投稿数: 4
お住まい・勤務地: 東京
投稿日時: 2007-12-31 18:57
ありがとうございました、解決しました。
SwingWorker を用いる事でなんなく目的の状態になりました。
コード:
// ExecCon2
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.text.*;

public class ExecCon2 extends JFrame 
{
	private JTabbedPane base = new JTabbedPane();
	private SwingWorker<String, String> worker;
	JButton	btnExec    = new JButton(new actExec());

	JTextPane Console   = new JTextPane();
	JScrollPane Scr = new JScrollPane(Console,
		JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
		JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
	StyleContext sc = new StyleContext();
	StyledDocument doc = (StyledDocument)Console.getDocument();

	protected void initDoc( JTextPane pan ) { pan.setText("Console:\\n"); }

public ExecCon2()
{
	this.getContentPane().add(Scr);
	Console.setDocument( doc );
	initDoc( Console );

	try {
		initTab0();
		this.setVisible(true);

		this.addWindowListener(new WindowAdapter() {
			public void windowClosing( WindowEvent e ) { System.exit(0); }
		});
	} catch(Exception e) {
		e.printStackTrace();
	}
}

///////////////////////////////////////////////////////////
private void initTab0() throws Exception
{
	this.getContentPane().setLayout(null);
	this.setSize(new Dimension(400, 300));
	this.setTitle( "Exec and Console sample");

	Scr.setBounds(new Rectangle( 5, 40, 380, 200 ));
	this.getContentPane().add(base, null);
	btnExec.setBounds(new Rectangle(5, 10, 65, 20));
	this.add(btnExec,null);
}

// srings add to console
public void appendDoc( String str )
{
	try {
		System.out.print("appendDoc:"+str);
		doc.insertString( doc.getLength(), str,
			sc.getStyle(StyleContext.DEFAULT_STYLE) );
		Console.setCaretPosition(doc.getLength());
	} catch (BadLocationException ble){
		System.err.println("Document style initalize error.");
	}
}

class actExec extends AbstractAction {
	public actExec() { super("run"); }

        public void actionPerformed(ActionEvent evt) {
	btnExec.setEnabled(false);

	worker = new SwingWorker<String, String>() {
	@Override public String doInBackground() {
		try {
			Process proc
				= Runtime.getRuntime().exec("perl test.pl", null,null);
			InputStream is = proc.getInputStream();
			InputStreamReader isrs =  new InputStreamReader(is);
			BufferedReader ib = new BufferedReader(isrs, 10);

			byte[] ibuff = new byte[100];
			int	ilen;

			while ( true ) {
				ilen = is.read(ibuff);
				if( ilen < 0 ) break;
				appendDoc( new String( ibuff, 0, ilen ) );
			}
			proc.waitFor();
		} catch(Exception ex ) {
			ex.printStackTrace();
		}
		appendDoc("Done");
		return "Done";
	}

        @Override public void done() {
		btnExec.setEnabled(true);
	}

        };	// End of worker
        worker.execute();
	// btnExec.setEnabled(true);	// for multiprocessing
        }
}

public static void main(String args[]) {
        ExecCon2 obj = new ExecCon2();
}

} // end of Class

Kazuki
ぬし
会議室デビュー日: 2004/10/13
投稿数: 298
投稿日時: 2008-01-01 14:08
SwingコンポーネントはEvent Dispatch Threadからじゃないと操作してはいけません。
今は、たまたまうまく動いててもいつか謎の落ち方をすることがあるかもしれません。

SwingWorkerだと、doInBackgroundでSwingの操作をするんじゃなくてpublishを呼び出して、processメソッドでSwingの表示更新をしたりします。

簡単な例ですが、↓のようになります。
コード:
        SwingWorker<Void, String> worker = new SwingWorker<Void, String>() {
            @Override
            protected Void doInBackground() throws Exception {
                for (int i = 0; i < 100; i++) {
                    publish("へろー:" + i + System.getProperty("line.separator"));
                    Thread.sleep(1000);
                }
                return null;
            }

            @Override
            protected void process(List<String> chunks) {
                Document doc = consolePane.getDocument();
                for (String line : chunks) {
                    try {
                        doc.insertString(doc.getLength(), line, null);
                    } catch (BadLocationException ex) {
                        Logger.getLogger(ConsoleFrame.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
                consolePane.setCaretPosition(doc.getLength());
            }
            
            
        };
        worker.execute();

1

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