Java+SwingでGUIアプリケーションを作ってみる
JavaとSwingを使い、JRE(Java Runtime Environment)をインストールしたコンピュータで実行可能なGUIアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版
>systeminfo OS 名: Microsoft Windows 8.1 Pro OS バージョン: 6.3.9600 N/A ビルド 9600 OS ビルドの種類: Multiprocessor Free システムの種類: x64-based PC プロセッサ: 1 プロセッサインストール済みです。 [01]: Intel64 Family 6 Model 69 Stepping 1 GenuineIntel ~1596 Mhz
JDK 8のインストール
まず、JDKのダウンロードページからWindows x64用のインストーラをダウンロードし、JDK 8をインストールする。
インストール後、PATH
環境変数にC:\Program Files\Java\jdk1.8.0_91\bin
を追加しておく。
コマンドプロンプトを起動し、次のコマンドが使えることを確認する。
>javac -version javac 1.8.0_91
Eclipseのインストール
次に、EclipseのダウンロードページからWindows 64 bit用のzipファイルをダウンロードし、適当なディレクトリに展開する。 続けて、eclipse.exeを起動するとコード一式を置くパス(workspace)の設定を求められるので、適当なディレクトリを設定し、「Use this as default and do not ask again」にチェックを入れてOKを押す。 Tutorialsの「Create a Hello World application」を選択し、簡単にEclipseの使い方を確認しておくとよい。
SwingでGUIアプリケーションを作ってみる
ここでは、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
まず、ツールバーの「New」→「Java Project」から「SHA1Calculator」プロジェクトを作成し、続けて「Create new visual classes」から「Swing」→「JFrame」を選択する。 クラス名を「SHA1Calculator」にしてOKを押すとファイルの雛形が作成されるので、続けて「Run」を押し、空のウィンドウが表示されることを確認する。
雛形を編集し、実際にコードを書いてみると次のようになる。 javax.swing.JTableなど、使用するクラスを新たにimportする必要があることに注意。 なお、EclipseではCtrl+Spaceで補完リストの表示、Ctrl+Shift+Fでコードの自動整形、Ctrl+Shift+Oで不要なimport文の削除を行うことができる。
import java.awt.BorderLayout; import java.awt.EventQueue; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.List; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTable; import javax.swing.SwingWorker; import javax.swing.TransferHandler; import javax.swing.border.EmptyBorder; import javax.swing.table.DefaultTableModel; public class SHA1Calculator extends JFrame { private JPanel contentPane; private DefaultTableModel model; /** * Launch the application. */ public static void main(String[] args) { EventQueue.invokeLater(new Runnable() { public void run() { try { SHA1Calculator frame = new SHA1Calculator(); frame.setVisible(true); } catch (Exception e) { e.printStackTrace(); } } }); } /** * Create the frame. */ public SHA1Calculator() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setBounds(100, 100, 600, 300); // expanded default frame size contentPane = new JPanel(); contentPane.setBorder(new EmptyBorder(5, 5, 5, 5)); contentPane.setLayout(new BorderLayout(0, 0)); setContentPane(contentPane); setTitle("SHA-1 Calculator"); // create scrollable JTable model = new DefaultTableModel(new String[]{"File Path", "sha1sum"}, 0); JTable table = new JTable(model); JScrollPane scrollPane = new JScrollPane(table); contentPane.add(scrollPane); // enable file drop contentPane.setTransferHandler(new DropFileHandler()); } private class DropFileHandler extends TransferHandler { @Override public boolean canImport(TransferSupport support) { // Check if dropped data is files return (support.isDrop() && support.isDataFlavorSupported(DataFlavor.javaFileListFlavor)); } @Override @SuppressWarnings("unchecked") public boolean importData(TransferSupport support) { if (!canImport(support)) { return false; } Transferable t = support.getTransferable(); List<File> files = null; try { files = (List<File>) t.getTransferData(DataFlavor.javaFileListFlavor); } catch (UnsupportedFlavorException | IOException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } for (File file : files) { String path = file.getAbsolutePath(); model.addRow(new String[]{path, "calculating..."}); ModelUpdater mu = new ModelUpdater(path, model.getRowCount()-1); mu.execute(); } return true; } } private class ModelUpdater extends SwingWorker<String, Long> { private String path; private Integer rowindex; public ModelUpdater(String path, Integer rowindex) { this.path = path; this.rowindex = rowindex; } @Override public String doInBackground() throws IOException { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); } catch (NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } // update digest with each 8MB chunk long totalBytes = (new File(path)).length(); long currentBytes = 0; try (FileInputStream istream = new FileInputStream(path)) { byte[] buf = new byte[8*1024*1024]; int n; while ((n = istream.read(buf, 0, buf.length)) != -1) { md.update(Arrays.copyOf(buf, n)); currentBytes += n; publish(100*currentBytes/totalBytes); } } catch (IOException e) { e.printStackTrace(); } byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } @Override protected void process(List<Long> values) { // only last one is effective String message = String.format("calculating %d%%...", values.get(values.size()-1)); model.setValueAt(message, rowindex, 1); } @Override protected void done() { try { model.setValueAt(get(), rowindex, 1); } catch (Exception ignore) { } } } }
上のコードでは、SHA1Calculatorクラスでウィンドウとコンポーネントを生成し、そのサブクラスのDropFileHandlerクラスでドラッグアンドドロップに対する処理を記述している。 また、SwingWorkerクラスを継承したModelUpdaterクラスでSHA-1ハッシュ値の計算を行っている。 Swingでハッシュ値計算などの時間がかかる処理を実行する場合、メインスレッドで実行すると画面描画がブロックしてしまうため、SwingWorkerを使ってバックグラウンドで実行させる必要がある。
コードを保存した後「Run」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。
計算されたハッシュ値を別の方法で調べたハッシュ値と照らし合わせることにより、正しく計算できていることが確認できる。
実行可能なJARファイルを作ってみる
上のコードをJREで実行可能なJARファイルにするには、Package Explorerのプロジェクト名(SHA1Calculator)を右クリックし、「Export」を選択する。 表示されるウィザードから「Java」→「Runnable JAR file」を選択し、configurationと出力先パスを指定してFinishを押すと、JARファイルが生成される。
生成されたJARファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。