Skip to the content.

Autogui: 自動GUIバインディングライブラリ

English 日本語

AutoguiはJava/SwingのGUIアプリケーションをplain-old Javaオブジェクトから作り出すライブラリです。 基本的な仕組みは与えられたオブジェクトのクラス構造をリフレクションAPIを通じて解析し、 クラスに定義されたプロパティとアクションに紐づけられたSwingベースのコンポーネントを組み合わせてユーザーインターフェースを作り出します。

ライセンス

Apache License v2

ソースからのビルド

GitHubプロジェクトページ

git clone https://github.com/ppp-kohe/autogui.git
cd autogui

本プロジェクトはビルドにapache-mavenを利用し、Javaの下記のバージョンに依存します。

mvn package
  # このコマンドは target/autogui-1.7.jar を生成します。

本プロジェクトの主要部分はJDKクラス以外の他のライブラリに依存しません。 src/main/javaにあるソースファイル(及びsrc/main/resourcesにあるリソース)を手動でコンパイルすることもできます。

Mavenの利用

本ライブラリを他のapache-mavenのプロジェクトで利用する場合は、pom.xmlファイルに下記のdependencyセクションを挿入してください。

    <dependency>
        <groupId>org.autogui</groupId>
        <artifactId>autogui</artifactId>
        <version>1.7</version>
    </dependency>

本ライブラリはMaven Central Repositoryから取得できます: org.autogui:autogui

Maven Central

APIドキュメント

簡単な利用方法の説明: jshellでのサンプル

本ライブラリはJava 9以降にJDKに導入された標準のREPLツールであるjshellコマンド上で利用することができます。 利用するには、まず本ライブラリのjarファイルをクラスパスに含める必要があります。 jshellでは、/env -class-path <path/to/jar>コマンドを実行することで可能です。 jshellコマンドを起動したら、下記のコードのjshell>プロンプト行以降を貼り付けることで試すことができます。(JDK, Git, Mavenがインストールされている環境を前提としています。)

$ 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> 
class Hello {
   String value;
   void action() {
      System.out.println(value);
   }
}

/env -class-path target/autogui-1.7.jar

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”が出力されるはずです。

Hello

これは本ライブラリが、まず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ウィンドウを表示します:

ImageFlipDemo

この表示されたウィンドウは以下のGUIコンポーネントを持ちます:

FileRenameDemo

この例はライブラリのテーブル生成機能のデモアプリケーションです。

mvn test-compile exec:java -Dexec.classpathScope=test \
    -Dexec.mainClass=org.autogui.demo.FileRenameDemo

上記のコマンドは下図のようなGUIウィンドウを表示します:

ImageFlipDemo

利用方法としては、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);
            }
        }
    }
}

本ライブラリのコレクションオブジェクトからのテーブルコンポーネント生成機能は特に強力で、アプリケーションの適用範囲がとても広くなります。

@GuiIncludeを使ったStrictモード

本ライブラリをmainメソッドから実行するアプリケーションで利用する場合、GUIコンポーネントに対応するメンバーを限定する方が望ましいといえます。 @GuiIncludedAutGuiShell.get().showWindow(o) によってこの制限を得ることができます。

Strictモードは…

以下の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"; }
      }
  }

Strict mode

AutoGuiShellクラスのインスタンスメソッドshowWindowは生成対象のメンバーをorg.autogui.GuiIncluded アノーテーションが付加されたもののみに限定します。 AutoGuiShell.get().showWindow(o)から利用する場合、GUIに含めたいすべてのメンバー(クラス、フィールド、ゲッターメソッド、セッターメソッド、アクションメソッド)にアノーテーションをつける必要があります。

モジュールでの利用

Java 9以降に導入されたモジュールシステム上で本ライブラリを利用する場合、モジュールメンバーとして定義されたコードを本ライブラリに「公開(open)」する必要があります。これはライブラリがリフレクションAPIを通じて対象オブジェクトのコードにアクセスするためです。 名前付きモジュールにおいて、リフレクションAPIはopenされたメンバーのみに限定して利用できます。

本ライブラリのモジュール名はorg.autoguiです。以下の記述をmodule-info.javaに追加する必要があります:

  1. open修飾子を利用したいモジュール宣言に追加する。もしくは、exports(またはopens) 利用したいパッケージ to org.autogui; を追加する。
  2. 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 のプロパティはテキストフィールドに紐づきます。

  class Hello {
      String prop;
  }
  org.autogui.swing.AutoGuiShell.showLive(new Hello())

String text-field

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())

File text-field

Number spinner

数値をあらわす基本型 (byte, short, int, long, float and double) や java.lang.Number のサブタイプ、具体的には java.math.BigIntegerjava.math.BigDecimal は数値のスピナーに紐づきます。

  class Hello {
      int prop;
  }
  org.autogui.swing.AutoGuiShell.showLive(new Hello())

Number spinner

Boolean check-box

真偽値のboolean または java.lang.Boolean はチェックボックスに紐づきます。

  class Hello {
      boolean prop;
  } 
  org.autogui.swing.AutoGuiShell.showLive(new Hello())

Boolean check-box

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())

Enum combo-box

Image pane

java.awt.Image もしくはそのサブタイプは画像のプレビューが可能なペインに紐づきます。

  import java.awt.*;
  class Hello {
      Image prop;
  }
  org.autogui.swing.AutoGuiShell.showLive(new Hello())

Image pane

このペインはドラッグ&ドロップをサポートし、Altを押しながらマウスホイールスクロールにより拡大縮小が可能です。 コード上の画像の操作はjava.awt.image.BufferedImagejavax.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())

Image dragging

Document editor

java.lang.StringBuilder もしくは javax.swing.text.Document のサブタイプはテキストエディタに紐づきます。

  class Hello {
      StringBuilder prop = new StringBuilder();
  }
  org.autogui.swing.AutoGuiShell.showLive(new Hello())

Document editor

プロパティ値の変更(ドキュメントオブジェクトのアイデンティティの変更)はエディタのドキュメントを置き換えることになります。 したがって、このプロパティはユーザーの編集のために一貫した参照値を返す必要があります。

ドキュメントプロパティの適切な利用方法

テキストエディターのペインはプロパティの値に紐づいたテキスト内容の編集を反映します。

StringBuilderについては、そのオブジェクトをラップするDocumentオブジェクトが自動で生成されます。 そのドキュメントの編集に関する現在の実装は簡単なものになっており、巨大なサイズのテキストを編集する用途には適していません。

Swingのドキュメントとテキスト編集コンポーネント群は、どうやらどんなドキュメントの変更もテキスト編集コンポーネント内で起こることを仮定しているように見えます。 したがって、ユーザー定義のコードでStringBuilderDocumentのプロパティ値の内容を変種することは避けるべきです。

プロパティがjavax.swing.text.StyledDocument型のサブタイプの場合、もしくはStringBuilderの場合は、エディタ上で(ドキュメント全体の)スタイルをコンテキストメニューから変更することができます。

Document editor with styles

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())

Object pane

あるブジェクトのクラスは他のオブジェクトクラスのプロパティを持つことができ、それらは各オブジェクトに対応したサブペインとなります。

オブジェクトクラスのメンバーはアクションメソッドを含むことができ、それらはツールバーのボタンとなります。 これらのメソッドの条件としてその名前はget, is, setで開始せず、引数を1つも取らない必要があります。

そのようなアクションメソッドは(当然)オブジェクトのプロパティを読み書きすることができます。もし、アクションメソッドがオブジェクトのプロパティを変更した場合、メソッド実行後、本ライブラリのUIは自動的に変更されたプロパティを特定し、プロパティに紐づいたUIコンポーネントを更新します。

プロパティ定義

ユーザー定義のオブジェクトクラスはサブコンポーネントに紐づいたプロパティを持つことができます。 ここで、プロパティとは 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())

Properties

ゲッターメソッドは名前が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()を利用してください。)

アクションプロパティの組み合わせ

特別な追加規則として、オブジェクトペインでのアクションボタンとテキストフィールドを組み合わせることができます:

この時、文字列のプロパティに紐づいたテキストフィールはそのメソッドに対応するボタンを内包します。

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())

Action property combination

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())

Object tabbed-pane

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());

Embedded component

ユーザー定義の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())

Collection table

何らかのアクションメソッドからテーブルの表示を更新したい場合、リストのインスタンスを置き換える必要があります。 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())

Collection table with property columns

コレクションテーブルの選択行に対するアクション

もし、型引数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())

Collection table with actions

テーブル要素の選択の管理

リストに対して生成されたテーブルは要素セルの選択を操作する機能として 現在、以下の2種類があります:

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())

Collection table with selection actions

上記の例で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[]>だった場合は{行番号,カラム番号}を意味します。

セルのサイズ変更

生成されたテーブルにはカラムと行の表示サイズを変更する機能があり、テーブルヘッダーのポップアップメニュー(右クリックで開くコンテキストメニュー)から利用できます。

Collection table with setting column size

名前の規則と@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())

Order of members

ゲッターやセッターのペアに付加されたアノーテーションの場合、片方につけられたものが適用されます。

@GuiIncluded(description=…)によるコンポーネントの説明文

@GuiIncluded(description=...)アノーテーションによりメンバーのツールチップメッセージを設定することができます。

   import org.autogui.GuiIncluded;
   class Hello {
       @GuiIncluded(description="component description") String prop;
   }

Property description

@GuiIncluded(keyStroke=…)によるキー割り当て

本ライブラリは自動的にメンバー名からショートカットキーを設定します。@GuiIncluded(keyStroke=...)アノーテーションによって明示的に設定することもできます。

   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())

Key-binding

アクションに対するショートカットキーの実行は対象となるアクションの実行になります。 プロパティに対するショートカットキーの実行は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… から保存された初期設定を編集することができます。

Preferences

このメニューは上記のような初期設定のウィンドウを表示します。画面には保存された初期設定がリストされます。

このリストには以下の初期設定が表示されます:

初期設定のリストには”Apply at Launch”というチェックボックスのカラムがあります。チェックが入った初期設定が起動時に適用されます。

初期設定ウィンドウのツールバーには DeleteDuplicate アクションがあり、これらは選択した初期設定を削除/複製することができます。 Update アクションは選択した初期設定を最新のUIコンポーネントの値により上書きします。 Apply アクションは即時に選択された初期設定をUIコンポーネントに適用します。 Write To File…Load From File… アクションは初期設定をJSONファイルに保存/読み込みすることができます。

本ライブラリの1.7以降では選択された初期設定を画面の右側のペインで直接編集することができます。

また、保存した初期設定は Object メニューの Apply Preferences メニューの項目にリストされ、選択することで適用できます。

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)。 また、初期設定ストアに保存できるように十分に小さいデータサイズである必要があります。

ロギング

生成されたウィンドウはステータスバーとログエントリを表示するリスト表示の機能を持っています。ロギングエントリは以下の種類があります:

文字列メッセージのログ

生成されたウィンドウは System.errSystem.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());

String message

進捗のログ

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());

Progress

進捗バーの右側に表示されるボタンにより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());

Progress

長時間のアクションと進捗バー

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())

Progress

外観の設定

現在のSwing GUI (Java23) は近年のOSの自動的なダークモードの切り替えに対応していません。

しかし、カスタムルック&フィール(LAF)のライブラリを使うことでダークモードをサポートすることができます。具体的には darklafflatlafがあります。

本ライブラリのデフォルトの起動プロセスではLAFを自動的に設定します。 autogui.lafプロパティにより設定するLAFを指定できます。JVMの起動オプションで-Dautogui.laf=...で設定可能です。このプロパティは下記の値をとります。なお対応するコードは UIManagerUtil.selectLookAndFeelFromSpecialName(String)です。

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
NimbusLookAndFeelCustomFlatLight NimbusLookAndFeelCustomFlatDark

MacOSではJVMのオプション-J-Dapple.awt.application.appearance=systemを追加することで、ダークモードのウィンドウタイトルバーを適用できます。

GNOME(デフォルトのUbuntuなど)でのHiDPI環境では環境変数GDK_SCALE=2を指定することで適切な表示が得られる場合があります。

Loading Flatlaf

本ライブラリのデフォルトのLAF指定は暗黙のうちにflatlaf (com.formdev:flatlaf:3.4.1により確認しています)が実行環境に含まれているかどうかをチェックし、OSの現在のテーマにあったルック&フィール(LAF)を自動的に適用します。このチェックと適用は単純にClass.forNameを使ってリフレクションAPIにより実現しています。

Progress