: O. Yuanying

Swingでプログレスダイアログ

Swingで進捗ダイアログを出す必要性があったので書いてみた。

標準ライブラリに付属しているProgressMonitorやProgressMonitorInputStreamもまた、進捗ダイアログを出してくれるのだが、処理にどれだけ時間がかかるかわからない場合、ちょっと使いづらかったので。。

package jp.fraction.swing;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Frame;
import java.awt.HeadlessException;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author yuanying
 * 
 * @param <R>
 */
public class ProgressDialog<R> extends JDialog implements ActionListener {
	/**
	 * 
	 */
	private static final long serialVersionUID = -4556558246742108136L;

	private static final Log log = LogFactory.getLog(ProgressDialog.class);

	private static final String CANCEL_TEXT = UIManager
			.getString("OptionPane.cancelButtonText");

	final private FutureTask<R> result;

	final private String message;

	private boolean canceled;

	/**
	 * ProgressDialogをモーダルダイアログとして生成します。
	 * 
	 * @param owner このダイアログの親フレーム。
	 * @param title ダイアログのタイトル。
	 * @param message ダイアログに表示するメッセージ。
	 * @param call 実行する処理。
	 * @throws HeadlessException
	 */
	@SuppressWarnings("unchecked")
	public ProgressDialog(Frame owner, String title, String message,
			Callable<R> call) throws HeadlessException {
		super(owner, title, true);

		this.initialize(owner);

		InvocationHandler handler = new CallableHandler(call);
		Callable<R> p = (Callable<R>) Proxy.newProxyInstance(Callable.class
				.getClassLoader(), new Class[] { Callable.class }, handler);
		this.result = new FutureTask(p);
		this.message = message;
		new Thread(this.result).start();
		this.setVisible(true);
	}

	private void initialize(Frame owner) {
		Container contentPane = this.getContentPane();

		contentPane.setLayout(new BorderLayout());
		contentPane.add(this.createOptionPane(), BorderLayout.CENTER);

		this.pack();
		this.setLocationRelativeTo(owner);

		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent we) {
				if (ProgressDialog.this.result != null) {
					ProgressDialog.this.cancel();
				}
			}
		});
	}

	private JOptionPane createOptionPane() {
		JProgressBar bar = new JProgressBar();
		bar.setIndeterminate(true);
		Object[] messageList = { this.message, bar };
		JButton cancelBtn = new JButton(CANCEL_TEXT);
		cancelBtn.addActionListener(this);
		return new JOptionPane(messageList, JOptionPane.INFORMATION_MESSAGE,
				JOptionPane.DEFAULT_OPTION, null, new Object[] { cancelBtn },
				null);
	}

	public void actionPerformed(ActionEvent e) {
		this.cancel();
	}

	/**
	 * 
	 * @return
	 */
	public synchronized boolean isCanceled() {
		return canceled;
	}

	/**
	 * 
	 * 
	 */
	public synchronized void cancel() {
		if (this.result != null) {
			this.result.cancel(true);
		}
		this.setVisible(false);
		this.canceled = true;
	}

	/**
	 * 処理結果を返します。まだ処理が始まっていなかった場合、もしくは実行中に例外が発生した場合は例外を投げます。
	 * 
	 * @return
	 * @throws Exception
	 */
	public R getResult() throws Exception {
		return this.result.get();
	}

	private class CallableHandler implements InvocationHandler {

		private Callable<R> call;

		public CallableHandler(Callable<R> call) {
			this.call = call;
		}

		public Object invoke(Object proxy, Method method, Object[] args)
				throws Throwable {
			Exception e1 = null;
			Object rtn = null;
			try {
				rtn = method.invoke(this.call, args);
			} catch (Exception e) {
				e1 = e;
			}
			if ("call".equals(method.getName())) {
				log.debug("call is finished");
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						ProgressDialog.this.setVisible(false);
						// ProgressDialog.this.dispose();
					}
				});
			}
			if (e1 != null) {
				throw e1;
			}

			return rtn;
		}

	}

	/**
	 * モーダルダイアログを表示し、結果を返します。結果が途中でキャンセルされたり、計算中に例外が発生した場合は、例外を投げます。
	 * 
	 * @param <R>
	 * @param owner ダイアログの親フレーム。
	 * @param title ダイアログのタイトル。
	 * @param message ダイアログに表示するメッセージ。
	 * @param callback 実行するコールバック関数。
	 * @return
	 * @throws Exception
	 */
	public static <R> R execute(Frame owner, String title, String message,
			Callable<R> callback) throws Exception {
		ProgressDialog<R> dialog = new ProgressDialog<R>(owner, title, message,
				callback);

		return dialog.getResult();
	}
}