JavaFXでGUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」では、JDKとEclipseをインストールし、Java+SwingによるGUIアプリケーションを作った。 SwingはJava 1.2から存在する標準のGUIライブラリであるが、Java 8からは新たな標準としてJavaFXへの置き換えが進められている。 ここでは、JavaFXを使い、同様のGUIアプリケーションを作ってみる。
環境
Windows 8.1 Pro 64 bit版、JDK 8、Eclipse Mars 2
>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
e(fx)clipseのインストール
EclipseでJavaFXプロジェクトを扱えるようにするために、JavaFX用のプラグインであるe(fx)clipseをインストールする。
まず、メニューの「Help」→「Install New Software」を選択する。 ウィザードが表示されたら、リポジトリとしてhttp://download.eclipse.org/efxclipse/updates-released/2.3.0/siteを追加し、「e(fx)clipse -IDE」にチェックを入れてNextを押す。 ユーザライセンスへの同意を求められるので、同意にチェックを入れてFinishを押すとインストールが行われる。
JavaFXでGUIアプリケーションを作ってみる
「Java+SwingでGUIアプリケーションを作ってみる」と同様に、ドラッグアンドドロップされたファイルのSHA-1ハッシュ値をテーブルに表示するアプリケーションを書いてみる。
まず、ツールバーから「New」→「Project」を開き、「JavaFX」→「JavaFX Project」を選択して「SHA1CalculatorFX」プロジェクトを作成する。
src/application/Main.java
にファイルの雛形が作成されるので、続けて「Run」を押し、空のウィンドウが表示されることを確認する。
JavaFXでは、メインとなるクラスはプロジェクト名に関係なくMainクラスとなる。 雛形を編集し、実際にコードを書いてみると次のようになる。
package application; 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 javafx.application.Application; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.concurrent.Task; import javafx.event.EventHandler; import javafx.scene.Scene; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.control.cell.TextFieldTableCell; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.stage.Stage; public class Main extends Application { private final TableView<FileInfo> table = new TableView<>(); @Override @SuppressWarnings("unchecked") public void start(Stage primaryStage) { try { BorderPane root = new BorderPane(); Scene scene = new Scene(root,600,400); // expanded default size scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm()); primaryStage.setScene(scene); primaryStage.setTitle("SHA-1 Calculator FX"); // TableView settings table.setEditable(true); table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY); TableColumn<FileInfo, String> filePathCol = new TableColumn<>("File Path"); filePathCol.setCellValueFactory(new PropertyValueFactory<>("path")); filePathCol.setCellFactory(TextFieldTableCell.forTableColumn()); TableColumn<FileInfo, String> sha1sumCol = new TableColumn<>("sha1sum"); sha1sumCol.setCellValueFactory(new PropertyValueFactory<>("sha1sum")); sha1sumCol.setCellFactory(TextFieldTableCell.forTableColumn()); table.getColumns().addAll(filePathCol, sha1sumCol); ObservableList<FileInfo> data = FXCollections.observableArrayList(); table.setItems(data); root.setCenter(table); // enable file drop scene.setOnDragOver(new DragOverHandler()); scene.setOnDragDropped(new DragDroppedHandler()); primaryStage.show(); } catch(Exception e) { e.printStackTrace(); } } public static void main(String[] args) { launch(args); } private class DragOverHandler implements EventHandler<DragEvent> { @Override public void handle(DragEvent event) { Dragboard db = event.getDragboard(); if (db.hasFiles()) { event.acceptTransferModes(TransferMode.COPY); } else { event.consume(); } } } private class DragDroppedHandler implements EventHandler<DragEvent> { @Override public void handle(DragEvent event) { Dragboard db = event.getDragboard(); boolean success = false; if (db.hasFiles()) { success = true; ObservableList<FileInfo> data = table.getItems(); for (File file : db.getFiles()) { FileInfo fileinfo = new FileInfo(file.getAbsolutePath(), ""); data.add(fileinfo); SHA1CalculationTask task = new SHA1CalculationTask(fileinfo); fileinfo.sha1sumProperty().bind(task.messageProperty()); new Thread(task).start(); } } event.setDropCompleted(success); event.consume(); } } class SHA1CalculationTask extends Task<Void> { private final FileInfo fileinfo; public SHA1CalculationTask(FileInfo fileinfo) { this.fileinfo = fileinfo; } @Override protected Void call() throws Exception { 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 String path = fileinfo.getPath(); 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; String message = String.format("calculating %d%%...", 100 * currentBytes / totalBytes); updateMessage(message); } } catch (IOException e) { e.printStackTrace(); } byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } updateMessage(sb.toString()); return null; } } public static class FileInfo { private final SimpleStringProperty path = new SimpleStringProperty(); private final SimpleStringProperty sha1sum = new SimpleStringProperty(); private FileInfo(String path, String sha1sum) { setPath(path); setSha1sum(sha1sum); } public String getPath() { return path.get(); } public void setPath(String newPath) { path.set(newPath); } public String getSha1sum() { return sha1sum.get(); } public void setSha1sum(String newSha1sum) { sha1sum.set(newSha1sum); } public SimpleStringProperty sha1sumProperty() { return sha1sum; } } }
JavaFXではデータバインディングの概念が取り入れられており、次のような特徴がある。
- TableViewにitemsとしてobservableArrayListを登録することで、リストへの変更が直接テーブルに反映される
- Tableの各セルはクラスのPropertyメンバで表現し、これをTaskクラスのPropertyにbindすることにより非同期での画面更新を行う
コードを保存した後「Run」を押して実行し、適当にファイルをドラッグアンドドロップした後のスクリーンショットを次に示す。
Swingと比較して、より現代的なUIデザインになっていることが見て取れる。
実行可能なJARファイルを作ってみる
JavaFXプロジェクトで実行可能なJARファイルを作成する際は、プロジェクトのExportではなくビルドツールのAntを用いる。
まず、build.fxbuildファイルを開き、表示されるフォームに対して次の例のように必須項目を入力する。
- Vendor name: inaz2
- Application title: SHA-1 Calculator FX
- Application version: 1.0.0
- Application class: application.Main
そして、右側にある「Generate ant build.xml and run」をクリックするとビルドが行われ、完了するとbuild/dist
にJARファイルが生成される。
生成されたJARファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。