🚀️

Swing 宣言的UI化計画!①

に公開

序章

JavaSwingが世に登場してから30年近く。既にデスクトップのGUIフレームワークとしてはすっかり影が薄くなってしまいましたが、その惨状は当時熱心に学んだ私からすると、とても寂しいものです。その後、新たなGUIフレームワークJavaFXが登場したものの、これを使ったアプリを見ることはほぼありません。気づけば標準SDKからも外されるという憂き目に合っていて、もはやJavaのGUIフレームワークが活躍できる場所は皆無、と言っても良いかもしれません(OracleのインストーラはまだJavaなんですかね?)。

そんな状況にある中、私は仕事で宣言的UIであるSwiftUIに触れ、それまでの命令的UI, もしくはXMLで組み立てる従来方式からGUI構築が新たな局面に突入したことを実感しました。そこで思い出したのがSwing。スペルもめちゃ似てるじゃないですか、Swiftと。もしSwingが宣言的UI化されれば、JAVAerの方々も手軽にGUIをいじれるようになるのではないか?と期待するところでしたが、公式からは何のアナウンスもなし。まぁ、冷静に考えてJavaの文法だと宣言的UI化するのは難しいところもあるのは分かります。なので、この記事では「もしもSwingが宣言的UI化されたら?」を実験的に試みようと思います。ただ、私自身はGUI技術の深いところまで精通しているワケではないので、温かい目で見守ってくれたら幸いです🤗

一応、以前の記事のリンクを貼っておきますが、「SwingUI 爆誕!」と題した記事で、簡単なサンプルを載せています。また記事の最後にお手製SwingUIで作成したアプリの動画も公開しています。

https://zenn.dev/spacehijackle/articles/2c701299df1fed

宣言的UI化するのに必要なこと

世にはSwiftの他にもKotlinFlutter(Dart)でも宣言的UIが採用されていますが、目指すのはSwiftUIです。これはデフォルト引数がサポートされていないJavaでは、それが前提のKotlinFlutterのようには記述できない、というのが一番の理由です。ちなみに私は現在Flutterでスマホアプリを作っていますが、とにかく色々と面倒なんです。余白を後から追加するのも地味に面倒なんですよね。ツールの機能を使えば良いのですが、ちょっとね…

  
  Widget build(BuildContext context) {
    return MaterialApp (
      home: Scaffold (
        appBar: AppBar (
          title: Text('Flutter サンプル'),
        ),
        body: Center (
          // Paddingウィジェットで子要素の周囲に余白を設定
          child: Padding (
            padding: EdgeInsets.all(8.0),
            child: Row (  // 垂直方向に並べる
              children: [
                Text (
                  'Flutter Sample',
                  style: TextStyle(fontSize: 18),
                ),
                Column (  // 水平方向に並べる
                  children: [
                    Text("Item1"),
                    Text("Item2"),
                  ],
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }

やっぱり、このような入れ子構造は見づらいです(何でインデントが2桁なんだ…)。それに子要素に一々childchildrenって引数のラベル指定を書かないといけないとか、私にはムダが多いように見えます。本当はFlutterで書くのを止めたいくらいなのですが、Hot Reloadが便利過ぎて… (にしても、末尾の)]の連続が気にならなかったの?設計者さん!)

まぁ、いいでしょう。ここでグチることでもありません。一方、SwiftUIは以下のような感じです。

    var body: some View
    {
        VStack  // 垂直方向に並べる
        {
            Text("SwiftUI Sample")
                .font(.title)
                .padding()  // 周囲に余白

            HStack  // 水平方向に並べる
            {
                Text("Item 1")
                Text("Item 2")
            }
        }
    }

.font().padding()は基本的にメソッドチェーンによって実現されています。メソッドチェーンとはStringBuilderで見る、アレです。

StringBuilder sb = new StringBuilder();
sb.append("一行目\n")
  .append("二行目\n")
  .append("三行目\n");

Swingでは基本的にボタン等のGUI部品はJComponentという上位クラスを継承していますが、このJComponentを各メソッドが返す、という操作を実現します。ちなみにStringBuilderappend(String)等のメソッドは、自身のクラスのメソッドを連続して呼び出せるようStringBuilderを返却していますが、これと同じ仕組みです。

それと前提として、もちろんSwingの基礎知識がある方が良いのですが、まぁとりあえずいいでしょう。必要があれば、都度説明します(たぶん)。

まずは縦横並びのレイアウトから

基本中の基本、レイアウトから実現していきます。レイアウトとは、GUI部品を並べていくベースとなるもので、Swingだと、例えばFlowLayoutBorderLayout等があります。前者はこのレイアウトを設定した後、ボタン等GUI部品を載せると横並びに並べてくれます(表示しきれなくなったら改行)。後者は中心をメインとし、その周りに上下左右にGUI部品を並べるレイアウトです。


OracleのBorderLayout説明チュートリアルより抜粋

このようにレイアウトはGUI部品を意図通りに並べるためのベースです。先に挙げたSwiftUIの例のように縦並び(垂直方向並び)、横並び(水平方向並び)にするのに適したSwingのレイアウトはBoxLayoutでしょう。


OracleのBoxLayout説明チュートリアルより抜粋

さて、GUI部品を並べるレイアウトは理解できたとして、それではそのレイアウトをどこに設定するのか?という疑問が生じるでしょう。基本的にSwingの作りはウィンドウを表すJFrameとその中に配置されるJPanelが存在します。大枠がJFrameで、その中で区画を区切るのがJPanelです(JPanel自体は単なる背景だけの存在であり、具体的には上図のグレー部分と考えてください)。

上記のBoxLayoutで例えると、フレーム枠がJFrameで、その中がJPanelであり、そのJPanelBoxLayoutが設定されることで、ボタンが縦に並んでいる(この例では区画は一つ)、と解釈すれば、とりあえず良いでしょう。ちなみに縦並びの中で横並びを実現するには途中のボタンをJPanelに入れ替え(JPanelもGUI部品のひとつ)、そのJPanel(この場合、区画は2つ)に横並びのBoxLayoutのレイアウトを設定し(下図、黄色部分)、その上にボタンを並べるわけなんですが、イメージできたでしょうか?


縦並びの中に横並びのBoxLayout

コンポーネントを独自に拡張

SwingのGUI部品はAPIではコンポーネントと呼ばれており、このコンポーネントを表すJComponentを各GUI部品クラスが継承している形になっています。このコンポーネントを継承する各GUI部品クラスを独自に拡張することで宣言的UI化させる目算です。もう少し具体的に説明すると、各GUI部品を継承したクラスを作成し、これに宣言的UI化に適合させるインターフェースを実装させることで、拡張したクラスを共通的に扱えるようにします。

https://github.com/spacehijackle/SwingUI_01/blob/main/app/src/main/java/com/swingui/widget/Widget.java

Widgetは各GUI部品が必ず実装しなくてはならない、逆に言えば、どんなGUI部品も対応できる基本的なメソッドを定義します。例えばpadding()はGUI部品の周囲に余白を作るメソッドであり、frame()は、そのGUI部品の幅、高さのサイズを決めます。ところでウィジェットという名称はLinux等のUNIX系OSで稼働するX WindowでGUI部品を指す単語です。Flutterでも同様に呼ばれていますね。概念的にコンポーネント≒ウィジェットであり、コンポーネントはSwingのGUI部品を表す一般的な通称、ウィジェットは目指すところのSwingUIにおけるGUI部品を表すこの記事(あるいはSwingUI)での呼称です。いずれにせよ、このWidgetで定義されているよう、各メソッドが自分自身を返却することがキモです。先にも触れたよう、メソッドチェーンを実現する仕組みになります。

では実際にこのWidgetを実装するGUI部品を作成します。まずはJPanelを拡張するPanelWTです。ちなみに末尾のWTWidgetを表します。

https://github.com/spacehijackle/SwingUI_01/blob/main/app/src/main/java/com/swingui/widget/PanelWT.java

今回の記事では、このPanelWTBoxLayoutでGUI部品を縦、もしくは横並びにすることが、ひとまずのゴールです。ちなみにJavaのバージョンは広く適用できるよう、Java8にしておきます。

さて、PanelWTですが、JPanelを継承し、Widgetを実装しています。先にもちょっと触れたよう、JPanelもGUI部品なので、Widgetを実装するのです。詳しい実装の話はまた次回以降に回しますが、今回使用するのはpadding(int), frame(int, int), background(UIValue)になります。これらの実装ですが、順に説明すると、padding(int)PanelWTの周囲に指定された数値(px)の余白を確保します。本来は上部のみとか、左右のみに余白を設定可能にするところですが、まずは一番単純なもので実装です。次にframe(int, int)ですが、これはPanelWTのサイズを固定サイズに設定するものになります。レイアウトに依存するので、必ずしも固定サイズになることを保証するものではありませんが、基本的にコンポーネントのサイズを幅(width)と高さ(height)で固定します。最後にbackground(UIValue)ですが、背景色を指定するものです。JPanelは単なる背景だけの存在、と先ほど述べましたが、この背景色を変えることができます。で、肝心の色指定ですが、Colorというその名の通り、色を表現するクラスを使います。ただし、直接Colorを指定するのではなく、UIValueというクラスを介して渡すようにします。このUIValueは次回以降説明します。とりあえず、background(UIValue)に色を渡すことで、その指定色に背景が塗られる、というように考えてください。

おっ、宣言的UIっぽいものが…

ここまでは、まだ宣言的UIの基礎工事みたいなもので、まだその最終的な姿がイメージできないと思いますが、お待たせしました。ようやく宣言的UIの姿を目撃する段に入りました。

この記事の目標はSwiftUIみたいな記述をすることで、宣言的UIを目指すことでしたよね。VStack,HStackで呼び出し、その内部に子コンポーネントを並べるのです。まずはVStackで説明します。

https://github.com/spacehijackle/SwingUI_01/blob/main/app/src/main/java/com/swingui/front/layout/VStack.java

Javaはインスタンスを作成するのに、newキーワードは必須ですが、宣言的UIには似合いません。そこでstaticメソッドにすることで、newキーワード記述を回避します。publicのメソッドof()が2つありますが、基本的にどちらも最後の引数(可変引数)が重要です。init()を見てください。子コンポーネントを複数指定することで、BoxLayoutでレイアウトしたPanelWT上に指定された子コンポーネントを並べていきます。ここでは縦方向です。
この実装により、VStack.of(JComponent...)を呼び出すことでコンポーネントのレイアウトを縦並びにし、そこにGUI部品を並べる、というSwiftUIの例で示したようなことが可能になるのです。

https://github.com/spacehijackle/SwingUI_01/blob/main/app/src/main/java/swingui_01/Startup.java

Startup#buildUpInVertical()を見てください。VStack.of()HStack.of()の組合せで、全体が縦並びで途中で横並び、が実現しています。VStackが外側のPanelWTであり、HStackが内側のPanelWTです。PanelWTはコンポーネントの継承者なので、VStack.of(JComponent...)の引数としてHStackを指定できるのです。ちなみにtext(String)は文字列を表示するコンポーネントを返しますが、これはいずれちゃんと宣言的UIのGUI部品として作成することにします。


縦並び VStack の中に横並び HStack を実現

VStack, HStackそれぞれに背景色を指定しています。これにより、それぞれのPanelWTの描画範囲が分かりやすくなっていると思います。前者がシアン、後者が黄色で塗り分けています。

またVStackpadding(int)指定することで周囲に余白が生じていることも確認できるでしょう。加えてframe(int, int)により、起動時のVStackの幅、高さも実現できているようです(フレームのサイズではないので、ご注意を)。もし、実行できる環境があれば、ウィンドウフレームを広げることで、それに合わせてPanelWTのそれぞれの領域が変化することも確認してみてください。ちなみにプロジェクトはVisual Studio Codeで作りました(gradle使用)。私は元来Eclipseをこよなく愛しているのですが、最近の若者はそのようなプログラマをEclipseおじさんと呼ぶそうです。まるで子供部屋おじさんと呼ばれているような気がするので、慣れないですがガマンです。

ついでに逆のHStackの中にVStackもありますので、その画像を乗っけておきます。


横並び HStack の中に縦並び VStack を実現

SwiftUIの仕様として、コンポーネントは基本的に中心に寄るよう設計されているので、これを踏襲しています。最後に同様な画面を本家のSwiftUIと比べてみましょう。


本家SwiftUIと比較!

SwiftUIは子コンポーネントのサイズに合わせて領域を調整するのに対し、SwingUIVStackは均等に領域を割り当てるので、領域のサイズに違いがあります。これはBoxLayoutの仕様なので仕方ないと割り切ります。まぁ、そんな感じでSwiftUIを目指しながらも、ゆるく目指すSwingUIと考えてください。キリがないですからね。

さいごに

次回以降は他のGUI部品も宣言的UIの仲間に加えていきます。また、SwiftUI@Stateのような、変数を変化させることでGUI部品の見た目を変えたりする機能も紹介するので、ご興味のある方は楽しみにお待ちください。では👋

追記 (2025.11.27)

SwiftUIの仕様として、コンポーネントは基本的に中心に寄るよう設計されているので、これを踏襲しています。

SwiftUIの仕様に倣って、コンポーネントが中心に寄るようにVStack,HStackを調整したのですが、少々問題が生じました。。。

ウィンドウ・フレームを大きくした時にリスト等、領域が増えた分、大きく見せたいコンポーネントが、そうならなくなりまして… で、最終的に中心に寄せることにこだわることを止めました。


ウィンドウの大きさに合わせてリスト表示が大きくなる例(見直し後)

調整を止めたことで、先にお見せした実行結果は以下のように位置が偏ってしまいますが、別の方法で調整可能な予定ですので、ひとまずはコレでいきたいと思います。


上部に寄ってしまった実行結果…

ちなみに現状、StartupクラスのVStack#frame(int, int)をコメントアウトすると、適切なコンポーネントサイズで、ウィンドウの中心に位置するようになっています(何度もGithubにコミットしちゃってるので、ならない方は落とし直してください… ごめんクサイ🥸)。


ウィンドウ中心にコンポーネントが配置(frame()メソッドはコメントアウト)

まぁ、見方によっては本家SwiftUIの結果により近くなった、とも言えるかも…😅

GitHubで編集を提案

Discussion