Autogui: 自動GUIバインディングライブラリ
| English | 日本語 |
AutoguiはJava/SwingのGUIアプリケーションをplain-old Javaオブジェクトから作り出すライブラリです。 基本的な仕組みは、与えられたオブジェクトのクラス構造をリフレクションAPIを通じて解析し、 クラスに定義されたプロパティとアクションに紐づけられたSwingベースのコンポーネントを組み合わせてユーザーインターフェースを作り出します。
ライセンス
ソースからのビルド
git clone https://github.com/ppp-kohe/autogui.git
cd autogui
本プロジェクトはビルドにapache-mavenを利用し、Javaの下記のバージョンに依存します。
- 1.7- : Java 21以降
- 1.2- : Java 11以降
- -1.1x : Java 8以降
mvn package
# このコマンドは target/autogui-1.8.jar を生成します。
mvn install
# このコマンドはビルドしたライブラリをローカルの他のプロジェクトで利用できるように配置します
本プロジェクトの主要部分はJDKクラス以外の他のライブラリに依存しません。
src/main/javaにあるソースファイル(及びsrc/main/resourcesにあるリソース)を手動でコンパイルすることもできます。
Mavenの利用
本ライブラリを他のapache-mavenのプロジェクトで利用する場合は、pom.xmlファイルに下記のdependencyセクションを挿入してください。
<dependency>
<groupId>org.autogui</groupId>
<artifactId>autogui</artifactId>
<version>1.8</version>
</dependency>
本ライブラリはMaven Central Repositoryから取得できます: org.autogui:autogui.
APIドキュメント
簡単な利用方法の説明: jshellでのサンプル
本ライブラリはJava 9以降にJDKに導入された標準のREPLツールであるjshellコマンド上で利用することができます。
利用するには、まず本ライブラリのjarファイルをクラスパスに含める必要があります。
jshellでは/env -class-path <path/to/jar>コマンドによりクラスパスに含めることができます。
本ライブラリのjshellでの具体的な利用手順は以下のようになります。
- ターミナルからソースからのビルドで説明したように、
autoguiディレクトリ直下に移動し、ビルドを行います。 (JDK, Git, Mavenがインストールされている環境を前提としています。) jshellを起動します。/env -class-path target/autogui-1.8.jarを入力し、本ライブラリを利用可能にします。- 続けて以下のコードを入力します:
Helloクラスが定義されます。class Hello { String value; void action() { System.out.println(value); } } - さらに以下の3行を入力します: 本ライブラリを利用しウィンドウが表示されます。
import org.autogui.swing.*; Hello h = new Hello(); AutoGuiShell.showLive(h);
上記の手順のターミナル表示は概ね下記のようになります。
$ git clone https://github.com/ppp-kohe/autogui.git
$ cd autogui
$ mvn package
$ jshell
| Welcome to JShell -- Version 21.0.3
| For an introduction type: /help intro
jshell>
/env -class-path target/autogui-1.8.jar
class Hello {
String value;
void action() {
System.out.println(value);
}
}
import org.autogui.swing.*;
Hello h = new Hello();
AutoGuiShell.showLive(h);
上記のコードはまずHelloクラスを定義します。Helloクラスは1つのインスタンスフィールドとメソッドを定義しています。
その後、org.autogui.swing.AutoGuiShell.showLive(Object) の呼び出しを行います。これにより引数のオブジェクトからGUIウィンドウを生成して表示します。
生成されたウィンドウには”Value”とラベルのついたテキストフィールドと、”Action”とラベルのついたツールバーボタンが表示されているはずです。
テキストフィールドに文字列”hello, world”を入力し、ボタンをクリックすると、jshellのコンソールに”hello, world”が出力されるはずです。

これは本ライブラリが、まずvalueインスタンスフィールドからテキストフィールドを作り出していることを意味します。
このフィールドの型であるStringによってコンポーネントの種類はテキストフィールドになります。
ユーザーが生成されたウィンドウ上でテキストフィールドの文字列を編集した場合、本ライブラリは自動的に与えられたオブジェクトhのフィールドの値を入力された文字列に代入します。これはjsehll> h.valueを実行することで確認することができるでしょう。
また、メソッドactionはツールバーのボタンのアクションに紐づきます。
ボタンをクリックすると与えられたオブジェクトに対してそのメソッドが実行されます。
サンプルアプリケーション
src/test/java/autogui/demo以下にサンプルアプリケーションのソースコードがあります。
コマンドmvn test-compile exec:java -Dexec.classpathScope=test -Dexec.mainClass=...によって上記のディレクトリにあるコードを実行することができます。
ImageFlipDemo
下記のコードは以下のコマンドで実行できます:
mvn test-compile exec:java -Dexec.classpathScope=test -Dexec.mainClass=org.autogui.demo.ImageFlipDemo
この ImageFlipDemo.javaは多少意味のある実用的なサンプルです。
package org.autogui.demo;
import org.autogui.swing.AutoGuiShell;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.File;
public class ImageFlipDemo {
public static void main(String[] args) {
AutoGuiShell.showLive(new ImageFlipDemo());
}
BufferedImage image;
File output = new File("output.png");
void flipY() {
int w = image.getWidth();
int h = image.getHeight();
BufferedImage newImage = new BufferedImage(w, h, image.getType());
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
newImage.setRGB(x, h - y - 1, image.getRGB(x, y));
}
}
image = newImage;
}
void save() throws Exception {
if (output.exists()) {
System.err.println("File already exists: " + output);
} else {
ImageIO.write(image, "png", output);
}
}
}
このプログラムは下図のようなGUIウィンドウを表示します:

この表示されたウィンドウは以下のGUIコンポーネントを持ちます:
- 画像を表示するImageペイン: フィールド
BufferedImage imageから生成されます。画像ファイルをドラッグ&ドロップで入力データとして設定することができます。ドロップした画像は自動的にBufferedImageオブジェクトとして読み込まれペインに表示された後、フィールドに代入されます。 - アクションボタンFlip Y: メソッド
void flipY()から生成されます。画像をドロップして入力した後、ボタンをクリックすると画像がY方向で反転します。メソッドの実行で、生成された画像オブジェクトnewImageがimageフィールドに代入されます。メソッドの実行後、画像ペインは反転した画像を即座に表示するでしょう。 - ファイル名テキストフィールドOutput: フィールド
File outputから生成されます。このテキストフィールドは最初はフィールドの初期値”output.png”を表示します。テキストフィールドを編集するとフィールドの値が変更され、新しいFileを保持するようになります。 - アクションボタンSave: メソッド
void save()から生成されます。このアクションは反転した画像を新たなファイルとしてOutputフィールドに設定されたファイルへ書き出します。
FileRenameDemo
この例はライブラリのテーブル生成機能のデモアプリケーションです。
mvn test-compile exec:java -Dexec.classpathScope=test -Dexec.mainClass=org.autogui.demo.FileRenameDemo
上記のコマンドは下図のようなGUIウィンドウを表示します:

利用方法としては、Dirフィールドにディレクトリをドラッグ&ドロップで設定すると、Entriesテーブルにディレクトリの内容の一覧を表示します。
ソースコードは下記のようになっています:
package org.autogui.demo;
import org.autogui.GuiIncluded;
import org.autogui.swing.AutoGuiShell;
import java.io.File;
import java.util.*;
@GuiIncluded public class FileRenameDemo {
public static void main(String[] args) {
AutoGuiShell.get().showWindow(new FileRenameDemo());
}
File dir;
@GuiIncluded public File getDir() { return dir; }
@GuiIncluded public void setDir(File dir) {
boolean update =
dir != null && !Objects.equals(this.dir, dir);
this.dir = dir;
if (update && dir.isDirectory()) {
List<RenameEntry> es = new ArrayList<>();
int i = 0;
List<File> files = new ArrayList<>(
Arrays.asList(dir.listFiles()));
files.sort(Comparator.naturalOrder());
for (File file : files) {
es.add(new RenameEntry(file,
String.format("%03d-%s",
i, file.getName())));
++i;
}
entries = es;
}
}
List<RenameEntry> entries = new ArrayList<>();
@GuiIncluded public List<RenameEntry> getEntries() {
return entries;
}
@GuiIncluded public static class RenameEntry {
File file;
String newName;
public RenameEntry(File file, String newName) {
this.file = file;
this.newName = newName;
}
@GuiIncluded public File getFile() {
return file;
}
@GuiIncluded public String getNewName() {
return newName;
}
@GuiIncluded public void setNewName(String newName) {
this.newName = newName;
}
}
@GuiIncluded public void rename() {
for (RenameEntry e : entries) {
File newFile = new File(
e.getFile().getParentFile(), e.getNewName());
if (e.getFile().exists() && !newFile.exists()) {
e.getFile().renameTo(newFile);
}
}
}
}
- このデモにおいて、本ライブラリは
@GuiIncludedアノテーションがついたメンバからのみGUIコンポーネントを生成します。この利用形態をStrictモードと呼んでおり、AutoGuiShell.get().showWindow(...)によって起動します。 - ファイルパスフィールドDir: 文字列を編集すると
Fileオブジェクトを生成してsetDir(File)メソッドの引数に与えて呼び出します。このメソッドは引数のディレクトリからファイル一覧を取得し、RenameEntryオブジェクトとして生成してArrayListに追加します。RenameEntryはstaticなネストクラスで、Fileオブジェクトとインデックス番号を持つ新しいファイル名のプレフィックス文字列を保持します(String.format("%03d-%s",...)によって設定)。 - Entriesテーブル:
getEntries()メソッドで返されるListから生成されます。さらにこのテーブルのカラムはListの要素型である<RenameEntry>のFileとNew Nameプロパティから生成されます。 テーブルの各行はListオブジェクトの各要素と対応します。 ゲッターメソッドが返すListオブジェクトのアイデンティティが変化するとテーブルが変更されたとみなし、自動的にテーブルの行データの表示が更新されます。 - Renameボタン:
rename()メソッドから生成され、すべてのエントリのファイル名を更新します。
本ライブラリのコレクションオブジェクトからのテーブルコンポーネント生成機能は特に強力で、アプリケーションの適用範囲がとても広くなります。
@GuiIncludeを使ったStrictモード
本ライブラリをmainメソッドから実行するアプリケーションで利用する場合、GUIコンポーネントに対応するメンバーを限定する方が望ましいといえます。
@GuiIncluded と
AutGuiShell.get().showWindow(o) によってStrictモードと呼ぶ、この制限を有効にできます。
Strictモードは…
AutoGuiShell.get().showWindow(o)によって起動します。@GuiIncludedが付加されたpublicなクラス、プロパティ、メンバーのみを対象とします。
以下のJavaプログラムはStrictモードのサンプルアプリケーションです:
import org.autogui.GuiIncluded;
import org.autogui.swing.AutoGuiShell;
@GuiIncluded
public class Hello {
public static void main(String[] args) {
AutoGuiShell.get().showWindow(new Hello());
}
@GuiIncluded
public String hello;
@GuiIncluded
public void action() {
System.out.println(hello);
}
@GuiIncluded
public NonIncludedType label = new NonIncludedType();
//このプロパティはtoString()を表示するラベルになる
static class NonIncludedType {
public String toString() { return "non-included"; }
}
}

AutoGuiShellクラスのインスタンスメソッドshowWindowは生成対象のメンバーをorg.autogui.GuiIncluded アノーテーションが付加されたもののみに限定します。 AutoGuiShell.get().showWindow(o)から利用する場合、GUIに含めたいすべてのメンバー(クラス、フィールド、ゲッターメソッド、セッターメソッド、アクションメソッド)にアノーテーションをつける必要があります。
モジュールでの利用
Java 9以降に導入されたモジュールシステム上で本ライブラリを利用する場合、モジュールメンバーとして定義されたコードを本ライブラリに「公開(open)」する必要があります。これはライブラリがリフレクションAPIを通じて対象オブジェクトのコードにアクセスするためです。 名前付きモジュールにおいて、リフレクションAPIはopenされたメンバーのみに限定して利用できます。
本ライブラリのモジュール名はorg.autoguiです。以下の記述をmodule-info.javaに追加する必要があります:
open修飾子を利用したいモジュール宣言に追加する。もしくは、exports(またはopens) <利用したいパッケージ> `to org.autogui;` を追加する。利用したいパッケージ>requires org.autogui;
//自分の module-info.java の例
open module your.module { //"open"を自分のモジュールyour.moduleに追加する, もしくは...
exports your.pack to org.autogui; //自分のパッケージyour.packを本ライブラリに公開する
//もしくは "opens your.pack to autogui;" でも可能
requires org.autogui; //自分のコードから本ライブラリにアクセスできるようにする
//なお、org.autogui はJDKの java.desktop, java.datatransfer, java.prefs モジュールに依存する
}
本ライブラリの初期のバージョン(-1.1)では自動モジュールでした。これはmodule-info.classを含まないjarで、Java 8以前との互換性を持っています。
この場合、本ライブラリのjarをクラスパスやモジュールパスの両方に追加することができます。しかし、自動モジュールはjlinkのモジュールに含めることができないという制限があります。
最近のバージョン(1.2-)では本ライブラリはmodule-info.classを含んでおり、Java 11以降の要求に対応しています。
この状態でもクラスパスとモジュールパスの両方に追加することができ、さらにjlinkによりカスタムイメージが生成できます。(一方で本ライブラリはリフレクションAPIに依存しているため、実際の製品に適用する場合は十分な考慮が必要です。)
対応する型とコンポーネント
- 基本的な値の型
- String text-field: (文字列テキストフィールド)
java.lang.String - File text-field: (ファイルパステキストフィールド)
java.nio.file.Pathまたはjava.io.File - Number spinner: (数値スピナー) 基本型の数値(
byte,short,int,long,float,double) またはjava.lang.Numberのサブタイプ - Boolean check-box: (ブーリアンチェックボックス)
booleanもしくはjava.lang.Boolean - Enum pull-down menu: (列挙プルダウン)
java.lang.Enumのサブタイプ (enumタイプ) - Image pane: (画像ペイン)
java.awt.Imageのサブタイプ - Document editor: (ドキュメントエディター)
java.lang.StringBuilderもしくはjavax.swing.text.Document - Embedded component: (埋め込みコンポーネント)
javax.swing.JComponentのサブタイプ
- String text-field: (文字列テキストフィールド)
- Object pane: (オブジェクトペイン) ユーザー定義のオブジェクト型で、複数のプロパティとアクションの組み合わせ
- オブジェクトのプロパティ:
T getP() {...},T p() {...}(1.2-) ,void setP(T) {...},T p;- もしすべてのメンバーが他のユーザー定義オブジェクトに関するものであれば、それらのオブジェクトはタブペインのタブに格納されます。
- アクションメソッド:
void m() {...}
- オブジェクトのプロパティ:
- Collection table: (コレクションテーブル)
java.util.Collection<E>のサブタイプ、 配列E[]。カラムは要素型Eから生成されます。- カラム
Eの値型- 文字列カラム:
java.lang.String - ファイルカラム:
java.nio.file.Pathまたはjava.io.File - 数値カラム: 基本型の数値、もしくは
java.lang.Numberのサブタイプ - ブーリアンカラム:
booleanもしくはjava.lang.Boolean - 列挙カラム:
java.lang.Enumのサブタイプ - 画像カラム:
java.awt.Imageのサブタイプ - 埋め込みコンポーネントカラム:
javax.swing.JComponentのサブタイプ
- 文字列カラム:
- Object rows: (オブジェクト行) ユーザー定義型のオブジェクトは、プロパティのメンバーによるカラムを組み合わせて生成する。
- 動的なコレクションテーブル: ネストした
Collection<Collection<E>>か多次元配列E[][]
- カラム
String text-field
java.lang.String のプロパティはテキストフィールドに紐づきます。
class Hello {
String prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

File text-field
java.nio.file.Path もしくは
java.io.File のプロパティはファイルパス操作が可能なテキストフィールドに紐づきます。
import java.io.File;
class Hello {
File prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

Number spinner
数値をあらわす基本型 (byte, short, int, long, float and double) や
java.lang.Number のサブタイプ、具体的には
java.math.BigInteger や java.math.BigDecimal は数値のスピナーに紐づきます。
class Hello {
int prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

Boolean check-box
真偽値のboolean または
java.lang.Boolean
はチェックボックスに紐づきます。
class Hello {
boolean prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

Enum pull-down menu
enum型 (
java.lang.Enum
のサブタイプ) はプルダウンメニューに対応づきます。 (編集テキストフィールドがないコンボボックス).
列挙型のメンバーがメニューアイテムになります。
enum HelloEnum {
Hello, World
}
class Hello {
HelloEnum prop = HelloEnum.Hello;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

Image pane
java.awt.Image もしくはそのサブタイプは画像のプレビューが可能なペインに紐づきます。
import java.awt.*;
class Hello {
Image prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

このペインはドラッグ&ドロップをサポートし、Altを押しながらマウスホイールスクロールにより拡大縮小が可能です。
コード上の画像の操作はjava.awt.image.BufferedImage と
javax.imageio.ImageIOが便利です。
import java.io.File;
import java.awt.image.*;
import javax.imageio.*;
class Hello {
private File file;
void setFile(File f) throws Exception {
file = f;
if (f != null && f.isFile()) prop = ImageIO.read(f);
}
File getFile() { return file; }
BufferedImage prop;
int getImageWidth() {
return prop == null ? 0 : prop.getWidth();
}
int getImageHeight() {
return prop == null ? 0 : prop.getHeight();
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

Document editor
java.lang.StringBuilder
もしくは
javax.swing.text.Document のサブタイプはテキストエディタに紐づきます。
class Hello {
StringBuilder prop = new StringBuilder();
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

プロパティ値の変更(ドキュメントオブジェクトのアイデンティティの変更)はエディタのドキュメントを置き換えることになります。 したがって、このプロパティはユーザーの編集のために一貫した参照値を返す必要があります。
ドキュメントプロパティの適切な利用方法
テキストエディターのペインはプロパティの値に紐づいたテキスト内容の編集を反映します。
StringBuilderについては、そのオブジェクトをラップするDocumentオブジェクトが自動で生成されます。
そのドキュメントの編集に関する現在の実装は簡単なものになっており、巨大なサイズのテキストを編集する用途には適していません。
Swingのドキュメントとテキスト編集コンポーネント群は、どうやらどんなドキュメントの変更もテキスト編集コンポーネント内で起こることを仮定しているように見えます。
したがって、ユーザー定義のコードでStringBuilderやDocumentのプロパティ値の内容を変種することは避けるべきです。
プロパティがjavax.swing.text.StyledDocument型のサブタイプの場合、もしくはStringBuilderの場合は、エディタ上で(ドキュメント全体の)スタイルをコンテキストメニューから変更することができます。

Object pane
ユーザー定義のオブジェクトのクラスのプロパティはそのクラスのメンバーを組み合わせたペインに紐づきます。
class Hello {
String prop1;
int prop2;
E prop3 = new E();
void action() {
System.out.printf("Hello: %s %d %s\n", prop1, prop2, prop3);
}
}
class E {
String prop;
void actionE() {
System.out.printf("E: %s\n", prop);
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

あるブジェクトのクラスは他のオブジェクトクラスのプロパティを持つことができ、それらは各オブジェクトに対応したサブペインとなります。
Action method
オブジェクトクラスのメンバーはアクションメソッドを含むことができ、それらはツールバーのボタンとなります。
これらのメソッドの条件としてその名前はget, is, setで開始せず、引数を1つも取らない必要があります。
そのようなアクションメソッドは(当然)オブジェクトのプロパティを読み書きすることができます。もし、アクションメソッドがオブジェクトのプロパティを変更した場合、メソッド実行後、本ライブラリのUIは自動的に変更されたプロパティを特定し、プロパティに紐づいたUIコンポーネントを更新します。
メソッド名からボタンのアイコンが設定される場合があります。これはあらかじめ用意された単語に対応するアイコン群から設定されます。
例えばaddという単語には「+」マークのアイコンを用意しており、addItem()のようなアクションメソッドに対応します。
また、単語には類義語(synonyms)が設定されており、insertやappendに対してもaddと同じアイコンが設定されます。
本ライブラリが提供するすべてのアイコンと対応する単語はsrc/test/java/org/autogui/demo/IconListDemo.javaを実行すると確認できます。
mvn test-compile exec:java -Dexec.classpathScope=test -Dexec.mainClass=org.autogui.demo.IconListDemo
プロパティ定義
ユーザー定義のオブジェクトクラスはサブコンポーネントに紐づいたプロパティを持つことができます。 ここで、プロパティとは 1) アクセス可能なフィールド もしくは 2) ゲッターとセッターメソッドのペア として定義されます。
class Hello {
String prop1;
private String prop2 = "hello";
String getProp2() { return prop2; } //read-only
private String prop3;
String getProp3() { return prop3; }
void setProp3(String p) {
prop3 = p;
System.err.println("updated " + prop3);
}
private boolean flag = true;
boolean isFlag() { return flag; } //is...() for boolean
void setFlag(boolean b) { flag = b; }
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

ゲッターメソッドは名前がgetまたはis(真偽値の場合)で開始し、引数を取らず、プロパティの型で値を返す定義です。
本ライブラリの1.2以降では T prop() メソッド(例えばString prop())がゲッターとして利用できるようになりました。
これはJava 14で導入されたレコード型をサポートするためです。
この規則は既存のアクションメソッドを意図せずプロパティにしてまう可能性があります(voidでない型を返す場合)。
アクションメソッドとして認識させたい場合アノーテーションの引数@GuiIncluded(action=true)を指定することで実現できます。
セッターメソッドは名前がsetで開始し、プロパティの値の型を1つとる定義です。
もしプロパティがゲッターメソッドのみで定義された場合、プロパティの値は読み出し専用になります。
セッターメソッド内のコードでは他のプロパティの値の変更を行うことができます。生成されたUIは自動的に変更されたプロパティを特定します。 このUIの更新を実現するため、ゲッターメソッドは頻繁に呼び出されます。
AutoCloseableサポート
ユーザー定義のオブジェクトクラスがjava.lang.AutoCloseableインターフェースを実装する場合、そのオブジェクトのclose()メソッドが対応するウィンドウを閉じた時に自動的に実行されます。
(なおAutoGuiShell.showLive(o) で生成されたウィンドウはウィンドウを閉じても自動的に呼び出しません。ウィンドウを完全に閉じるにはcleanUp()を利用してください。)
アクションプロパティの組み合わせ
特別な追加規則として、オブジェクトペインでのアクションボタンとテキストフィールドを組み合わせることができます:
- オブジェクトペインのクラスが
String <propName>プロパティを持っており、 - そのクラスが
<propName>Action()という名前のアクションメソッドを持つ場合
この時、文字列のプロパティに紐づいたテキストフィールはそのメソッドに対応するボタンを内包します。
import java.util.regex.*;
class PatternFind {
String text;
String search;
void searchAction() {
var m = Pattern.compile(search).matcher(text);
if (m.find()) {
System.out.printf("found: %d%n", m.start());
} else {
System.out.println("not found");
}
}
}
org.autogui.swing.AutoGuiShell.showLive(new PatternFind())

Object tabbed-pane
ユーザー定義のオブジェクトがプロパティとして他のユーザー定義オブジェクト型のメンバーしか持たない場合、そのオブジェクトはタブペインに紐づきます。そしてメンバーのユーザー定義オブジェクトから生成されたペインが各タブに対応します。
class Hello {
Tab1 tab1 = new Tab1();
Tab2 tab2 = new Tab2();
}
class Tab1 {
String prop;
}
class Tab2 {
int prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

Embedded component
javax.swing.JComponentのサブタイプはサブコンポーネントとして埋め込みのペインに紐づきます。
import java.awt.*;
import javax.swing.*;
class Hello {
private JComponent value;
JComponent getValue() {
if (value == null) {
value = new JPanel() {
{ setPreferredSize(new Dimension(300, 300)); }
protected void paintComponent(Graphics g) {
g.setColor(Color.white);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.blue);
g.drawString("hello, world", 30, 30);
}
};
}
return value;
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello());

ユーザー定義のUIコンポーネントを作る場合、Swingコンポーネントの規則に従う必要があります。すなわち、コンポーネントのコードは基本的にはイベント駆動スレッド上で実行される必要があります。 そして、フィールドの初期化はイベント駆動スレッドでないスレッド(例えばメインスレッド)で実行される可能性があり注意が必要です。
よって埋め込みコンポーネントのプロパティに対するユーザーコードはゲッターメソッドで定義し、返すコンポーネントをフィールドにキャッシュするというスタイルをとるべきです。 埋め込みコンポーネントのプロパティについて本ライブラリはイベント駆動スレッド内で実行します。プロパティから得られた埋め込みコンポーネントはオブジェクトペインのサブコンポーネントとして追加されます。
Swingアプリケーションでの埋め込み
本ライブラリによってオブジェクトから生成したGUIを別のSwingアプリケーションに埋め込みたい場合、org.autogui.swing.GuiSwingRootPane.createForObject(o).
が利用できます。
src/test/java/org/autogui/demo/ObjectEmbeddedDemo.javaを参照してください。
サンプル実行:
mvn test-compile exec:java -Dexec.classpathScope=test -Dexec.mainClass=org.autogui.demo.ObjectEmbeddedDemo
Collection table
java.util.Collection<E>のサブタイプもしくは配列型E[]のプロパティはテーブルペインに紐づきます。
テーブルのカラムは型引数Eから生成されます。
import java.util.*;
class Hello {
List<String> prop = new ArrayList<>();
void add() {
prop.add("hello " + prop.size());
prop = new ArrayList<>(prop);
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

何らかのアクションメソッドからテーブルの表示を更新したい場合、リストのインスタンスを置き換える必要があります。 prop = new ArrayList<>(prop);が最も簡単な方法です。
Object rows
Collection<E>の型引数Eがユーザー定義のオブジェクトクラスの場合、テーブルのカラムはそのクラスのプロパティから生成されます。
import java.util.*;
class Hello {
List<Item> prop = new ArrayList<>();
void add() {
prop.add(new Item("hello " + prop.size(), (int) (Math.random() * 100)));
prop = new ArrayList<>(prop);
}
}
class Item {
String name;
int num;
Item(String n, int i) { this.name = n; this.num = i; }
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

コレクションテーブルの選択行に対するアクション
もし、型引数Eのコレクションプロパティを定義するオブジェクトクラスがさらに別のjava.util.List<E>型の引数をとるメソッドを持つ場合、対応するテーブルはそのメソッドに紐づいたアクションボタンを持つツールバーを持つことになります。
このボタンはテーブル上で選択された行に対応する要素をリストとして引数に与えて
メソッドを実行します。
もし、コレクションの要素オブジェクトがアクションメソッドを持つ場合、その場合もテーブルがツールバーにメソッドに紐づいたボタンを持ちます。 このボタンはテーブル上で選択された行に対応するオブジェクトそれぞれに対してメソッドを実行します。
import java.util.*;
class Hello {
List<Item> prop = new ArrayList<>();
void add() {
prop.add(new Item("hello " + prop.size(), (int) (Math.random() * 100)));
prop = new ArrayList<>(prop);
}
void remove(List<Item> selectedItems) {
prop.removeAll(selectedItems);
prop = new ArrayList<>(prop);
}
}
class Item {
String name;
int num;
Item(String n, int i) { this.name = n; this.num = i; }
void update() { ++num; }
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

テーブル要素の選択の管理
リストに対して生成されたテーブルは要素セルの選択を操作する機能として 現在、以下の2種類があります:
- 選択要素を変更するためのアクションになる特別なメソッドを定義する: メソッドは変更後の要素のリストを返すようにします。
@GuiListSelectionUpdaterアノーテーションにより特定されます。 - テーブルUIから選択された要素を受け取るコールバックとなる特別なメソッドを定義する:
@GuiListSelectionCallbackアノーテーションによって特定されます。
import java.util.*;
import org.autogui.*;
class Hello {
List<Item> prop = new ArrayList<>();
void add() {
prop.add(new Item("Hello " + prop.size()));
prop = new ArrayList<>(prop);
}
@GuiListSelectionUpdater
List<Item> selectTop() {
return Arrays.asList(prop.get(0));
}
@GuiListSelectionCallback
void selected(List<Item> items) {
System.out.println("selected: " + items);
}
}
class Item {
String name;
Item(String n) { this.name = n; }
public String toString() { return "Item(" + this.name + ")"; }
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

上記の例でList<Item> selectTop()は@GuiListSelectionUpdaterを付加しています。このメソッドはアクションボタン”Select Top”を生成し、実行するとpropテーブルの選択要素をリストの先頭の要素に(その要素のリストを返すことで)変更します。
また、void selected(List<Item> items) は@GuiListSelectionCallback を付加しており、引数に要素のリストを受け取ります。このメソッドもアクションボタンになりますが、”*Selected”と先頭にマークが付き、propテーブルの選択の変更により自動で実行されます。
これらの特別なメソッドの対象となるテーブルは、要素の型、すなわち List<E>の型引数Eによって特定されます。もし2つ以上のテーブルが同一の要素型をこれらのメソッドで使っていたら、それらすべてのテーブルがメソッドにより操作されます。
追加の特別な機能として、これらのアノーテーションは真偽型の引数indexを受け取ることができます。このフラグのデフォルト値はfalseで、trueが指定されると、付加された特別なメソッドはList<Integer>を取ることを前提するようになります。この整数値は要素型Eの代わりに行番号を扱うようになります。さらにList<int[]>だった場合は{行番号,カラム番号}を意味します。
セルのサイズ変更
生成されたテーブルにはカラムと行の表示サイズを変更する機能があり、テーブルヘッダーのポップアップメニュー(右クリックで開くコンテキストメニュー)から利用できます。

- Set All Column Width to This : すべてのカラムのサイズを対象カラムと同じにします
- Auto Resize Column Width : 自動的にすべてのカラムサイズを設定します
- Row Height : 行の高さを設定を変更します
- Fixed Size : すべての行を特定の高さに設定します。
- Fit to Content : すべての行の高さをその行の内容から決定します。
名前の規則と@GuiIncluded(name=…)
プロパティやアクション、型の名前の表示はcamelCaseの規則により決定します。
class Hello {
String helloWorld; //=> "Hello World"
String helloWORLD; //=> "Hello WORLD"
}
@GuiIncluded(name=...)アノーテーションにより任意の名前を設定することもできます。
class Hello {
@GuiIncluded(name="class") String klass;
}
@GuiIncluded(index=…)による表示順の調整
コンポーネントの表示順は@GuiIncluded(index=...)アノーテーションにより調整できます。
指定がない場合、デフォルトではプロパティの名前から決定します。
import org.autogui.GuiIncluded;
class Hello {
@GuiIncluded(index=3) String prop1;
@GuiIncluded(index=2) String prop2;
private String prop3;
@GuiIncluded(index=1) String getProp3() { return prop3; }
void setProp3(String p) { prop3 = p; }
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

ゲッターやセッターのペアに付加されたアノーテーションの場合、片方につけられたものが適用されます。
@GuiIncluded(description=…)によるコンポーネントの説明文
@GuiIncluded(description=...)アノーテーションによりメンバーのツールチップメッセージを設定することができます。
import org.autogui.GuiIncluded;
class Hello {
@GuiIncluded(description="component description") String prop;
}

@GuiIncluded(keyStroke=…)によるキー割り当て
本ライブラリは自動的にメンバー名からショートカットキーを設定します。@GuiIncluded(keyStroke=...)アノーテーションによって明示的に設定することもできます。
keyStroke="none": 割り当てを行わないようにしますkeyStroke="<control>... <key>"control ::= shift | altkey ::= A | B | C |... | Z | 0 | 1 ... | 9
- 予約されたキー: これらのキーはコマンド(macOS)かコントロールキーとの組み合わせになります
Q: 終了,W: ウィンドウを閉じるshift R: 表示更新A: すべて選択,shift A: 選択解除Z: 取り消す,shift Z: やり直すO: 開く,S: 保存X: カット,C: コピー,V: ペーストalt O: JSONを開く,alt S: JSONを保存,alt X: JSONとしてカット,alt C: JSONとしてコピー,alt V: JSONをペースト,: 設定
import org.autogui.GuiIncluded;
class Hello {
@GuiIncluded(keyStroke="L") String prop;
@GuiIncluded(keyStroke="T") void action() {
System.out.println("action " + prop);
}
//"action2"の名前から自動的にキー割り当て: Shift+Cmd+A
void action2() {
System.out.println("action2");
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

アクションに対するショートカットキーの実行は対象となるアクションの実行になります。 プロパティに対するショートカットキーの実行はUI上で対象コンポーネントにフォーカスします。 また、コントロール + エンターキーはフォーカス中のコンポーネントのコンテキストメニューを表示します。
UI要素の能動的な更新
本ライブラリで生成されたGUIコンポーネントはデフォルトでは自動的に表示を更新します。これは、何かアクションが発生した後にプロパティにアクセスして、再表示が必要な変更があるかどうかを確認することで実現しています。
@GuiNotifierSetterアノーテーションの機能を使うことで、特定のGUI要素の能動的な更新が可能になります。
これは更新用のRunnableオブジェクトがユーザーコードに与えられます。
このアノーテーションはRunnableを引数にとる特別なセッターメソッドに付加します。
ユーザーコードでは与えられたRunnableを保持してrun()メソッドを呼ぶことで対象GUI要素が必要なときに再表示を行うことができます。
この対象GUI要素はメソッドのシグニチャset<YourPropertyName>Notifier(Runnable r) かアノーテーション引数@GuiNotifierSetter(target="yourPropertyName")から決定します。
import java.util.concurrent.*;
import org.autogui.*;
import java.time.*;
class Hello implements AutoCloseable {
private ScheduledExecutorService service;
private ScheduledFuture<?> task;
String prop;
private Runnable updater;
@GuiNotifierSetter
void setPropUpdater(Runnable r) { updater = r; }
Hello() {
service = Executors.newScheduledThreadPool(1);
task = service.scheduleWithFixedDelay(this::update, 0, 3, TimeUnit.SECONDS);
}
private void update() {
prop = Instant.now().toString();
if (updater != null) {
updater.run();
}
}
public void close() {
task.cancel(true);
service.shutdownNow();
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())
上記のサンプルはテキストフィールドを表示し、3秒ごとにそのテキスト表示を更新します。
更新はupdate()メソッドで行われます。そこではpropを時刻の文字列 Instant.now().toString() で更新し、Runnable#run()を呼び出すことでフィールドの変更を通知します。このRunnableはsetPropUpdater(Runnable)で設定された、本ライブラリから与えられるオブジェクトをupdaterとして保持します。
このセッターはset<YourPropertyName>Updaterの名前の規則に従いpropフィールドを更新対象として指定しています。
コンストラクタではupdate()メソッドが定期的に実行されるようスケジュールされます。これはScheduledExecutorService#scheduleWithFixedDelay(this::update, ...) によりScheduledFutureタスクとして生成します。
初期設定の管理
生成されたウィンドウは内包するサブコンポーネントに紐づくプロパティ値を保存して再利用する機能を持ちます。
この機能はjava.util.prefsを利用して実現します。
class Hello {
String prop;
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())
本ライブラリでは、割り当てられたコンポーネントを通じて編集されたプロパティの値が初期設定として自動的に保存されます。 生成されたウィンドウの Object メニューにある Preferences… から保存された初期設定を編集することができます。

このメニューは上記のような初期設定のウィンドウを表示します。画面には保存された初期設定がリストされます。
このリストには以下の初期設定が表示されます:
- Current : デフォルトの(名前のない)初期設定です。この初期設定の値はUI操作で更新されます。
- Empty : リセットのために値がないことを表現する擬似的な初期設定
- 保存された初期設定 : 保存された初期設定で、ツールバーの Save アクションから追加できます。このアクションを実行すると現在のUIコンポーネントから値と設定を収集して新たな初期設定として記録します。リスト上でダブルクリックにより名前を変更できます。
初期設定のリストには”Apply at Launch”というチェックボックスのカラムがあります。チェックが入った初期設定が起動時に適用されます。
初期設定ウィンドウのツールバーには Delete と Duplicate アクションがあり、これらは選択した初期設定を削除/複製することができます。 Update アクションは選択した初期設定を最新のUIコンポーネントの値により上書きします。 Apply アクションは即時に選択された初期設定をUIコンポーネントに適用します。 Write To File… と Load From File… アクションは初期設定をJSONファイルに保存/読み込みすることができます。
本ライブラリの1.7以降では選択された初期設定を画面の右側のペインで直接編集することができます。
また、保存した初期設定は Object メニューの Apply Preferences メニューの項目にリストされ、選択することで適用できます。

GuiPreferencesLoader クラスを利用することでGUIを使わないコードで初期設定の機能を利用することができます。
var h = new Hello();
org.autogui.base.mapping.GuiPreferencesLoader.get()
.withTypeBuilderRelaxed()
.withPrefsGetterByNameEquals("saved-prefs")
.apply(h);
h.prop //=> "Hello"
埋め込みコンポーネントのカスタム初期設定
カスタムした埋め込みコンポーネントを使う場合、本ライブラリの初期設定の機能を簡単に利用できます。
本機能を利用する場合、カスタムコンポーネントがGuiPreferences.PreferencesJsonSupport インターフェースを実装する必要があります。
このインターフェースは2つのメソッドの実装を要求します。それぞれ初期設定の生成と設定をJSONオブジェクトとして扱います。
この機能はプロパティメソッドの静的な戻り値型がインターフェースを満たしている場合に有効になります。したがってカスタムした型をメソッド戻り値型に指定する必要があります。
import org.autogui.base.mapping.GuiPreferences;
import javax.swing.*;
import java.util.*;
class Hello { //オブジェクトペインの型
MyPane pane;
MyPane getPane() { //カスタムした型の埋め込みコンポーネントを返す
if (pane == null) pane = new MyPane();
return pane;
}
}
class MyPane extends JPanel
implements GuiPreferences.PreferencesJsonSupport {
JTextField data = new JTextField(20);
MyPane() {
add(data);
}
public Map<String,Object> getPrefsJson() { //初期設定ストアに書き出すための値
System.err.println("save " + data.getText());
return Map.of("myItem", data.getText());
}
@SuppressWarnings("unchecked")
public void setPrefsJson(Object v) { //初期設定ストアから復元されたデータを設定
if (v instanceof Map<?,?>) {
data.setText(((Map<String,String>) v).getOrDefault("myItem", ""));
}
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())
getPrefsJson()で返されるオブジェクトは単純なJSONの型の組み合わせでなければなりません(Map、List、String、Number、Boolean)。
また、初期設定ストアに保存できるように十分に小さいデータサイズである必要があります。
コード上でのデフォルト設定値の提供
バージョン1.8から@GuiInitsというアノーテーションによっていくつかのコンポーネントの初期設定のデフォルト設定値をコード上で設定できるようになりました。
import org.autogui.GuiIncluded;
import org.autogui.GuiInits;
import org.autogui.base.annotation.*;
import org.autogui.swing.AutoGuiShell;
import javax.swing.text.*;
@GuiInits(window = @GuiInitWindow(width = 400, height = 300))
public class MyEditor {
DefaultStyledDocument doc = new DefaultStyledDocument();
@GuiInits(action = @GuiInitAction(confirm = true))
public void clear() throws Exception {
doc.replace(0, doc.getLength(), "", null);
}
}
org.autogui.swing.AutoGuiShell.showLive(new MyEditor())
上記のコードはテキストペインと”Clear”アクションボタンを持つウィンドウを表示しますが、ウィンドウの初期サイズが400 x 300に設定されます。そして”Clear”アクションボタンを押すと、アクション実行前に確認ダイアログを表示します。

これらはそれぞれクラスに付加された@GuiInits(window = ...)と、メソッドに付加された@GuiInits(action = ...)のデフォルト設定によるものです。
@GuiInitsアノーテーションはこの機能で設定可能なすべての項目をそのプロパティ(window = ...やaction = ...)として定義しています。実際に定義可能な設定の項目は@GuiInitsのプロパティに設定されるアノーテーション、org.autogui.base.annotationパッケージの@GuiInitWindowや@GuiInitActionに定義されています。
| プロパティ | 値型 | 付加対象 | 設定内容 |
|---|---|---|---|
tabbedPane |
@GuiInitTabbedPane |
parent type of object tabbed pane | タブ化抑制 |
splitPane |
@GuiInitSplitPane |
parent object pane type of splitted-components | 縦方向分割指定 |
window |
@GuiInitWindow |
root object pane type | サイズ |
table |
@GuiInitTable |
Collection table property | 行高さ指定、動的カラムリサイズ |
tableColumn |
@GuiInitTableColumn |
table column property | カラム幅、ソート順 |
tableColumnString |
@GuiInitTableColumnString |
table column string property | エンターキー確定/改行切替 |
numberSpinner |
@GuiInitNumberSpinner |
number property | フォーマット、最大、最小、ステップ設定 |
action |
@GuiInitAction |
action method | 確認ダイアログ表示 |
ロギング
生成されたウィンドウはステータスバーとログエントリを表示するリスト表示の機能を持っています。ログエントリは以下の種類があります:
- 文字列メッセージ
- 進捗バー
- 例外
文字列メッセージのログ
生成されたウィンドウは
System.err と
System.outを置き換えます。置き換えられたこれらのストリームからの出力はステータスバーとリストウィンドウにも表示されます。
この置き換えはGuiSwingLogManager#setupConsoleWithDefaultFlags()の実行で読み取られるシステムプロパティにより設定にできます。
-Dautogui.log.replaceErr=false -Dautogui.log.replaceOut=falseのようなVMオプションを追加することで無効にできます。
また、ユーザーコードからはGuiLogManagerを使って直接表示することもできます。
import org.autogui.base.log.*;
class Hello {
void action() {
System.err.println("message");
GuiLogManager.get().logString("string message");
GuiLogManager.get().log("composed message ", 1);
GuiLogManager.get().logFormat("format message %d", 2);
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello());

進捗のログ
GuiLogManagerからGuiLogEntryProgressを作り出すことで進捗バーを表示し、操作することができます。
ファクトリメソッドlogProgress(n)は最大カウントnによりエントリを作成します。
addValue(i) または
addValueP(p) (0…1.0の値を取る)
によりカウントを更新できます。
進捗バーはsetMessage(s)によってメッセージを表示することもできます。
import org.autogui.base.log.*;
class Hello {
void action() {
new Thread(this::m).start();
}
private void m() {
try (GuiLogEntryProgress p = GuiLogManager.get().logProgress(100)) {
for (int i = 0; i < 100; ++i) {
p.setMessage("next " + i).addValue(1);
Thread.sleep(1000);
}
} catch (InterruptedException ie) {
GuiLogManager.get().logError(ie);
}
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello());

進捗バーの右側に表示されるボタンによりlogProgress()を呼び出したThreadに割り込みを入れることができます。
割り込みにより、スレッドがブロックする間InterruptedExceptionが発生します。このブロックはThread.sleep(n)などで発生します。
ブロッキングなしの実行コードについてはコード上で明示的にThread.interrupted()を呼び出して割り込みが発生したかどうか明示的に確認する必要があります。
例外のログ
生成されたウィンドウはキャッチされなかった例外に対するハンドラを設定し、ログリストに例外の表示を行うことができます。GuiLogManager.get().logError(e)は明示的に与えられた例外オブジェクトを表示することができます。この表示はログリストで表示される要素となり、スタックトレースを展開することができます。
import org.autogui.base.log.*;
class Hello {
void action() {
GuiLogManager.get().logError(new RuntimeException("error1"));
throw new RuntimeException("error2");
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello());

長時間のアクションと進捗バー
GUIアクションの実行が極端に長くなった場合、(最大カウントのない)進捗バーを表示する独立したタスクとなります。停止ボタンは割り込みを発生させるので、
InterruptedException もしくは
Thread.interrupted()で確認できます。
class Hello {
void action() throws Exception {
for (int i = 0; i < 100; ++i) {
if (i % 10 == 0) System.err.println(i);
Thread.sleep(1000);
}
System.err.println("finish");
}
}
org.autogui.swing.AutoGuiShell.showLive(new Hello())

外観の設定
現在のSwing GUI (Java23) は近年のOSの自動的なダークモードの切り替えに対応していません。
しかし、カスタムルック&フィール(LAF)のライブラリを使うことでダークモードをサポートすることができます。具体的には darklaf や flatlafがあります。
本ライブラリのデフォルトの起動プロセスではLAFを自動的に設定します。
autogui.lafプロパティにより設定するLAFを指定できます。JVMの起動オプションで-Dautogui.laf=...で設定可能です。このプロパティは下記の値をとります。なお対応するコードは
UIManagerUtil.selectLookAndFeelFromSpecialName(String)です。
- 値設定なし、または空文字
""かdefault- 後述するflatlafロードを行を試す
- MacOSで外観がダークでなければ
system(システムデフォルトのLAF: AquaLookAndFeel)を適用する - 上記以外の場合
nimbus-flatを適用する
default-no-darklaf:defaultと同じだがflatlafロードは試さないmetal:javax.swing.plaf.metal.MetalLookAndFeelを適用するnimbus:javax.swing.plaf.nimbus.NimbusLookAndFeelを適用するnimbus-flat: 本ライブラリ独自のNimbusLookAndFeelCustomFlatを適用するnimbus-flat-light: 本ライブラリ独自のNimbusLookAndFeelCustomFlatのライトテーマを適用するnimbus-flat-dark: 本ライブラリ独自のNimbusLookAndFeelCustomFlatのダークテーマを適用するsystem: 現在のOSごとに異なるLAFdarklaf: 後述するflatlafロードを試すnone: LAFの設定を行いません
NimbusLookAndFeelCustomFlat
nimbus-flat で指定されたorg.autogui.swing.util.NimbusLookAndFeelCustomFlatは本ライブラリが独自に実装するLAF(1.7-)で、JDKに実装されているNimbusLookAndFeelをカスタムし、起動時のテーマによってライトテーマかダークテーマのどちらかを適用します。
テーマの判定はOSによって異なりますが、外部のコマンドを実行することで行います。なお、これらのスタイルはnimbus-flat-lightまたはnimbus-flat-darkの指定で明示的に指定できます。
nimbus-flat-light |
nimbus-flat-dark |
|---|---|
![]() |
![]() |
MacOSではJVMのオプション-J-Dapple.awt.application.appearance=systemを追加することで、ダークモードのウィンドウタイトルバーを適用できます。
GNOME(デフォルトのUbuntuなど)でのHiDPI環境では環境変数GDK_SCALE=2を指定することで適切な表示が得られる場合があります。
Loading Flatlaf
本ライブラリのデフォルトのLAF指定は暗黙のうちにflatlaf (com.formdev:flatlaf:3.6により確認しています)が実行環境に含まれているかどうかをチェックし、OSの現在のテーマにあったルック&フィール(LAF)を自動的に適用します。このチェックと適用は単純にClass.forNameを使ってリフレクションAPIにより実現しています。

コード上の外観の制御
AutoGuiShell クラスに定義されたメソッドを呼び出してLAFの設定を制御することができます。
例えばHelloクラスのオブジェクトを起動する前に下記のようなコードでLAF設定を制御することができます。
AutoGuiShell.get()
.withLookAndFeelDefaultWithoutProp() //プロパティの設定を無効にし, nimbus-flatまたはflatlafを利用する
.showWindow(new Hello());
AutoGuiShell.get()
.withLookAndFeelDefaultNoFlatlafWithoutProp() //プロパティの設定を無効にし, nimbus-flatを利用する
.showWindow(new Hello());
AutoGuiShell.get()
.withLookAndFeelSpecial("metal") //autogui.lafに"metal"を設定した場合と同様
.showWindow(new Hello());

