Eclipse Foundation が管理保守している SWT (Standard Widget Toolkit) を用いると Java で GUI ツールが作成できます。簡単なサンプルコードを示します。
SWT: The Standard Widget Toolkit にアクセスして Releases / Stable というリンクからお使いの OS のものを選択してダウンロードします。"more..." というリンクをクリックすると 64bit など詳細な選択ができます。
解凍すると以下のものが同封されていることが分かります。
Eclipse を起動して新規 Java プロジェクトを作成します。
プロジェクト内に lib フォルダを新規作成します。先程解凍した swt.jar (または swt-debug.jar) および src.zip を lib 内にコピーします。swt-debug.jar を利用すると SWT クラス内部でブレークポイントが利用できたりしますがファイルサイズが大きくなってしまいます。通常は SWT クラス内を直接触ることはないため swt.jar で問題になりません。
パッケージ・エクスプローラにおいて swt.jar を右クリックしてビルドパスに追加します。
パッケージ・エクスプローラにおいて参照ライブラリー内の swt.jar を右クリックしてプロパティを開きます。Java ソースの添付を開き先程 lib にコピーした src.zip を参照して OK を押します。これで SWT クラスの Javadoc ドキュメントが利用できるようになりました。
プロジェクト内を右クリックするなどして新規に Java クラスファイルを追加します。「パッケージ swt, 名前 Main, メソッド・スタブ public static void main (String[] args) にチェック」という設定で新規作成してみました。更に以下のように編集します。
src/swt/Main.java
package swt;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Main {
public static void main(String[] args) {
Display display = new Display(); // アプリが OS に問い合わせる窓口
Shell shell = new Shell(display); // ウィンドウを表現するクラス
shell.setText("ウィンドウ名");
shell.open();
while(!shell.isDisposed()) { // 終了指令がない間は無限ループ
if(!display.readAndDispatch()) {
display.sleep(); // イベントが発生しない間はスリープ
}
}
display.dispose(); // リソースの解放
}
}
「右クリック → 実行 → Java アプリケーション」または「Alt + Shift + x, j」によって実行できます (Scala プロジェクトなどの場合は「エクスプローラ内を右クリック」→「実行」→「Scala アプリケーション」)。二回目以降は履歴が残るため「実行ボタンを押す」または「Ctrl + F11」でも実行可能です。完全に停止させるためにはコンソールタブの四角形の停止ボタンを押します。
「メニューバーのファイル」→「エクスポート」→「Java / 実行可能 JAR ファイル」→「次へ」と進み、以下のように設定して完了を押します。生成された swt-gui.jar をダブルクリックすると Java アプリケーションが起動できます。
生成される JAR に必須ライブラリーを抽出 (上から一番目) では swt.jar の内容が一旦展開されて、自分の class と一緒に jar に再度同封されます。
$ jar tf swt-gui.jar
META-INF/MANIFEST.MF
swt/
swt/HelloWorld.class ← 自分が作ったクラス
swt-awt-win32-4430.dll ← Windows 実行時用ネイティブライブラリー
swt-gdip-win32-4430.dll
swt-webkit-win32-4430.dll
swt-wgl-win32-4430.dll
swt-win32-4430.dll
swt-xulrunner-win32-4430.dll
swt-xulrunner24-win32-4430.dll
external.xpt
org/
org/eclipse/
org/eclipse/swt/
org/eclipse/swt/SWT.class ← SWT クラス
...
org/eclipse/swt/widgets/Widget.class
version.txt
生成される JAR に必須ライブラリーをパッケージ (上から二番目) では swt.jar は展開されずにそのまま同封されます。
$ jar tf swt-gui.jar
META-INF/MANIFEST.MF
org/
org/eclipse/
org/eclipse/jdt/
org/eclipse/jdt/internal/
org/eclipse/jdt/internal/jarinjarloader/
org/eclipse/jdt/internal/jarinjarloader/JIJConstants.class
org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader$ManifestInfo.class
org/eclipse/jdt/internal/jarinjarloader/JarRsrcLoader.class
org/eclipse/jdt/internal/jarinjarloader/RsrcURLConnection.class
org/eclipse/jdt/internal/jarinjarloader/RsrcURLStreamHandler.class
org/eclipse/jdt/internal/jarinjarloader/RsrcURLStreamHandlerFactory.class
swt/
swt/HelloWorld.class ← 自分が作ったクラス
swt.jar ← 展開されずにそのまま同封
生成される JAR の隣のサブフォルダーに必須ライブラリーをコピー (上から三番目) では swt.jar は同封されません。別のフォルダが作成されてそれを参照することで swt.jar を利用します。
$ jar tf swt-gui.jar
META-INF/MANIFEST.MF
swt/
swt/HelloWorld.class
$ ls swt-gui_lib/
swt.jar
$ jar xvf swt-gui.jar
$ cat META-INF/MANIFEST.MF
Manifest-Version: 1.0
Class-Path: . swt-gui_lib/swt.jar
Main-Class: swt.HelloWorld
ボタンやテキストはコントロールとよばれる種類のウィジェットです。親コンテナを指定して追加します。親コンテナの最上位は Shell です。
src/swt/Main.java
package swt;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
public class Main {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("MyJavaApp");
// ↓今回追加した部分
shell.setLayout(new RowLayout()); // レイアウト設定
Button button = new Button(shell, SWT.NULL); // 親コンテナは shell
button.setText("押してください");
Text text = new Text(shell, SWT.BORDER); // BORDER スタイル
// ビットフラグなので以下のように複数指定可能です:
// Text text = new Text(shell, SWT.BORDER | SWT.V_SCROLL);
// ↑今回追加した部分
shell.open();
while(!shell.isDisposed()) {
if(!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
src/swt/Main.java
package swt;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Main {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("MyJavaApp");
shell.setLayout(new FillLayout());
MySwtApp app = new MySwtApp();
app.createWidget(shell);
shell.open();
while(!shell.isDisposed()) {
if(!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
src/swt/MySwtApp.java
package swt;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Text;
public class MySwtApp {
private Text text;
private Button button;
private Label label;
public void createWidget(Composite parent) {
Composite composite = new Composite(parent, SWT.NULL);
composite.setLayout(new RowLayout(SWT.VERTICAL));
text = new Text(composite, SWT.BORDER);
button = new Button(composite, SWT.NULL);
button.setText("押してください");
label = new Label(composite, SWT.NULL);
label.setLayoutData(new RowData(100, -1));
button.addSelectionListener(new SelectionListener() { // イベントリスナーの登録
public void widgetSelected(SelectionEvent e) {
label.setText(text.getText());
}
public void widgetDefaultSelected(SelectionEvent e) {
}
});
}
}
メインのスレッドで生成したスレッドからは SWT の仕様上の制約からウィジェットを直接操作しての情報変更や情報取得ができません。ビルドは通りますがランタイムエラーが発生します。asyncExec (非同期) または syncExec (同期) を使用して操作する必要があります。
src/swt/Main.java
package swt;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class Main {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("ウィンドウ名");
shell.open();
MyThread thread = new MyThread(display, shell);
thread.start();
while(!shell.isDisposed()) {
if(!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
src/swt/MyThread.java
package swt;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class MyThread extends Thread {
Display display = null;
Shell shell = null;
MyThread(Display display, Shell shell) {
this.display = display;
this.shell = shell;
}
public void run() {
display.asyncExec(new InnerThread()); // 非同期
display.syncExec(new InnerThread()); // 同期
}
class InnerThread implements Runnable {
public void run() {
shell.setText("別のウィンドウ名");
}
}
}