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ファイルを実行すると、上と同様のウィンドウが表示されることが確認できる。