[Java]109 自製アプリのリファクタリング案

[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]

自製カラーアプリのリファクタリング案(改良案)を作成しました。

これまではTextFieldに保持されたカラーコードで選択色を管理していましたが、専用のクラスSelectColorを新設し変数(基本フォーマットは0x+hex)で管理することにしました。

これにより色フォーマットの変更に対応するコードが複雑にならずに済みそうです。

@startuml

package base {
    +class ColorSampleJP {
        +{static} gui : JFrame
        +{static} tabbedpane : JTabbedPaneEx
        +{static} tab1 : JPanel
        +{static} tab2 : JPanel
        +{static} tab3 : JPanel
        +{static} tab4 : JPanel
        +{static} tab5 : JPanel
        +{static} tab6 : JPanel
        +{static} layout : BorderLayout
        +{static} default_font : Font
        +{static} home : String
        -locale_lang : ResourceBundle

        -ColorSampleJP() : void
        +{static} main(String[] args) : void
        +{static} setLAF() : void
        +LAF_OS() : String[][]
        +{static} size(String key) : int
        +{static} makeDir() : void

    }
    
    '関連クラス(mainクラスで使用)
    +class PanelMake {
    }
    +class RegistTab {
    }
    +class SelectColor #00FFFF{
    }
    +class TabMake {
    }
}

package btnAction {
    +class ShowButtonAction #DDA0DD{
    }
    +class AdjustButtonAction {
    }
    +class AlphaButtonAction {
    }
    +class RadioButton1Action #DDA0DD{
    }
    +class RadioButton2Action #DDA0DD{
    }
    +class RadioButton3Action #DDA0DD{
    }
    +class RadioButton4Action #DDA0DD{
    }
    +class RegistButtonAction #DDA0DD{
    }
    +class TrushButtonAction {
    }
    +class FormatConverter {
    }

}

note left of SelectColor
    選択あるいは指定した色を管理する
end note

ColorSampleJP -- PanelMake
ColorSampleJP -left- RegistTab
ColorSampleJP -left- TabMake
TabMake -- SelectColor
SelectColor -- ShowButtonAction
SelectColor -- RadioButton1Action
SelectColor -- RadioButton2Action
SelectColor -- RadioButton3Action
SelectColor -- RadioButton4Action
SelectColor -- RegistButtonAction

left to right direction
PanelMake -- ShowButtonAction
PanelMake -- AdjustButtonAction
PanelMake -- AlphaButtonAction
PanelMake -- RadioButton1Action
PanelMake -- RadioButton2Action
PanelMake -- RadioButton3Action
PanelMake -- RadioButton4Action
RegistTab -- RegistButtonAction
RegistTab -- TrushButtonAction
AdjustButtonAction -- FormatConverter

@enduml

[Java]108 パッケージ別クラス図作成

[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]

自製カラーアプリについてパッケージ間の関係性をクラス図で表現しました。

mainクラスを含むbaseパッケージとbtnActionパッケージのクラス図を描いてみました。関係性が一目瞭然です。

@startuml

package base {
    +class ColorSampleJP66 {
        +{static} gui : JFrame
        +{static} tabbedpane : JTabbedPaneEx
        +{static} tab1 : JPanel
        +{static} tab2 : JPanel
        +{static} tab3 : JPanel
        +{static} tab4 : JPanel
        +{static} tab5 : JPanel
        +{static} tab6 : JPanel
        +{static} layout : BorderLayout
        +{static} default_font : Font
        +{static} home : String
        -locale_lang : ResourceBundle

        -ColorSampleJP66() : void
        +{static} main(String[] args) : void
        +{static} setLAF() : void
        +LAF_OS() : String[][]
        +{static} size(String key) : int
        +{static} makeDir() : void

    }
    
    '関連クラス(mainクラスで使用)
    +class PanelMake {
    }
    +class RegistTab {
    }
}

package btnAction {
    +class ShowButtonAction {
    }
    +class AdjustButtonAction {
    }
    +class AlphaButtonAction {
    }
    +class RadioButton1Action {
    }
    +class RadioButton2Action {
    }
    +class RadioButton3Action {
    }
    +class RadioButton4Action {
    }
    +class RegistButtonAction {
    }
    +class TrushButtonAction {
    }
    +class FormatConverter {
    }

}

ColorSampleJP66 -- PanelMake
ColorSampleJP66 -left- RegistTab

left to right direction
PanelMake -- ShowButtonAction
PanelMake -- AdjustButtonAction
PanelMake -- AlphaButtonAction
PanelMake -- RadioButton1Action
PanelMake -- RadioButton2Action
PanelMake -- RadioButton3Action
PanelMake -- RadioButton4Action
RegistTab -- RegistButtonAction
RegistTab -- TrushButtonAction
AdjustButtonAction -- FormatConverter

@enduml

[Java]107 メインクラスのクラス図作成 : レイアウト変更

[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]

クラス図をより見やすくするために子クラスを右側に縦並びさせました。デフォルトの横並びを左へ90度回転させるので左側にあったクラスが下になります。書き順を変えても同じです。

まあ見やすくなったのでよしとします。

@startuml
package base {
    +class ColorSampleJP66 {
        +{static} gui : JFrame
        +{static} tabbedpane : JTabbedPaneEx
        +{static} tab1 : JPanel
        +{static} tab2 : JPanel
        +{static} tab3 : JPanel
        +{static} tab4 : JPanel
        +{static} tab5 : JPanel
        +{static} tab6 : JPanel
        +{static} layout : BorderLayout
        +{static} default_font : Font
        +{static} home : String
        -locale_lang : ResourceBundle

        -ColorSampleJP66() : void
        +{static} main(String[] args) : void
        +{static} setLAF() : void
        +LAF_OS() : String[][]
        +{static} size(String key) : int
        +{static} makeDir() : void

    }
    +class Tab1 {
        -Tab1(String LAF0) : void
    }
    +class Tab2 {
        -Tab2(String LAF0) : void
    }
    +class Tab3 {
        -Tab3(String LAF0) : void
    }
    +class Tab4 {
        -Tab4(String LAF0) : void
    }
    +class Tab5 {
        -Tab5(String LAF0) : void
    }
    +class Tab6 {
        -Tab6(String LAF0) : void
    }
    +class MyWindowsListener {
        +windowClosing(WindowEvent e) : void
    }

    left to right direction
    ColorSampleJP66 +-- Tab1
    ColorSampleJP66 +-- Tab2
    ColorSampleJP66 +-- Tab3
    ColorSampleJP66 +-- Tab4
    ColorSampleJP66 +-- Tab5
    ColorSampleJP66 +-- Tab6
    ColorSampleJP66 +-- MyWindowsListener

    '関連クラス(mainクラスのフィールド)
    +class JTabbedPaneEx {
    }
    
}

ColorSampleJP66 -ri- JTabbedPaneEx

@enduml

[Java]106 メインクラスのクラス図作成

[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]

自製カラーアプリのリファクタリングに際し、ソースコードの全体像を把握しやすくするため、クラス図の作成に着手しました。

VSCodeにPlantUMLを導入して使用しています。まずはメインクラスのクラス図を作成しました。入れ子になっているクラスは親クラスの外に出して記述し、+–で入れ子であることを表現します。

私にとっては十分複雑な作りになっていることを再認識しました。今までクラス図なしでよくメンテナンスできたものだと思います。

@startuml
package base {
    +class ColorSampleJP66 {
        +{static} gui : JFrame
        +{static} tabbedpane : JTabbedPaneEx
        +{static} tab1 : JPanel
        +{static} tab2 : JPanel
        +{static} tab3 : JPanel
        +{static} tab4 : JPanel
        +{static} tab5 : JPanel
        +{static} tab6 : JPanel
        +{static} layout : BorderLayout
        +{static} default_font : Font
        +{static} home : String
        -locale_lang : ResourceBundle

        -ColorSampleJP66() : void
        +{static} main(String[] args) : void
        +{static} setLAF() : void
        +LAF_OS() : String[][]
        +{static} size(String key) : int
        +{static} makeDir() : void

    }
    +class Tab1 {
        -Tab1(String LAF0) : void
    }
    +class Tab2 {
        -Tab2(String LAF0) : void
    }
    +class Tab3 {
        -Tab3(String LAF0) : void
    }
    +class Tab4 {
        -Tab4(String LAF0) : void
    }
    +class Tab5 {
        -Tab5(String LAF0) : void
    }
    +class Tab6 {
        -Tab6(String LAF0) : void
    }
    +class MyWindowsListener {
        +windowClosing(WindowEvent e) : void
    }

    ColorSampleJP66 +-- Tab1
    ColorSampleJP66 +-- Tab2
    ColorSampleJP66 +-- Tab3
    ColorSampleJP66 +-- Tab4
    ColorSampleJP66 +-- Tab5
    ColorSampleJP66 +-- Tab6
    ColorSampleJP66 +-- MyWindowsListener
    
}
@enduml

[Java]105 VSCodeでPlantUMLによるクラス図作成

[M1 Mac, Big Sur 11.6.5, VSCode 1.67.1]

自製カラーアプリをリファクタリングするにあたり、コードの内容をクラス図で整理することにしました。

HomebrewからPlantUMLとGraphviz(クラス図作成に必要)をインストールしました。

brew install graphviz
brew install plantuml

VSCodeのsettings.jsonに以下を追記し、Graphvizのdotファイルをパス設定等します。

"plantuml.commandArgs": [
        "-DGRAPHVIZ_DOT=/opt/homebrew/Cellar/graphviz/3.0.0/bin/dot",
        "-Xmx2g",
        "-DPLANTUML_LIMIT_SIZE=16384",
    ]

とりあえずネットの作例から簡単なクラス図を表示してみました。

[Java]104 Makefileによるappファイル作成自動化

[M1 Mac, Big Sur 11.6.4]

これまで手動だったappファイル作成をMakefileにより自動化しました。

下記ファイルによりmakeコマンド一発でjarファイルを作成・署名し、appファイルを作成できます。これを応用してApple公証用zipファイル提出まで一気に実行することも可能でしょう。

# コンパイラ・ビルダ設定
COMPILER = javac
BUILDER = jar

# jarファイル設定
TARGET = test.jar
TARGETDIR = ../bin
MAIN_CLASS = Test

# jarファイル作成および署名,appファイル作成
$(TARGET):
	$(COMPILER) -d $(TARGETDIR) base/*.java -encoding utf-8
	cp -r ./images ../bin/images
	cp -r ./properties ../bin/properties
	cd $(TARGETDIR) && pwd && \
	$(BUILDER) cfe $@ base.$(MAIN_CLASS) \
		base/*.class \
		btn_action/*.class \
		xml/*.class \
		properties/*.* \
		images/*.png && \
	cp -f ../entitlements.plist entitlements.plist && \
	codesign --force --verify --verbose \
		--sign [mac-signing-key-user-name] \
		"test.jar" \
		--deep \
		--options runtime \
		--entitlements entitlements.plist \
		--timestamp
	cd .. && \
	jpackage --type app-image \
        --name test \
        --input bin \
        --main-jar test.jar \
        --icon bin/images/test.icns \
        --copyright "Copyright (C) 2021-2022" \
        --mac-signing-key-user-name [mac-signing-key-user-name] \
        --app-version "1.6.7" \
        --vendor [ベンダ名] \
        --runtime-image jre-mini

ウィジェット・ツールキットの検討

JavaのSwingでGUIアプリを作成する際、既存のLook and Feelを混在させるとその制約を受けて自由度が低くなるケースが多々あるため、言語にとらわれず新たなウィジェット・ツールキットを検討しています。

C++であればFLTKあたりが比較的容易に導入できそうなので、勉強を兼ねてSwingで開発中のアプリを移植できるか試しにいじってみることにしました。そこそこの外観で処理速度アップが目標です。

Swingの使いこなしの方もまだまだですし、並行してスキルアップに努めます。SynthLookAndFeelを使えばデフォルトのLook and Feelを無効化できるので、アプリの処理速度にこだわらなければこれで好きなように描画可能です。

[Java]103 Look and Feelの外観

これまで様々なLook and Feelを扱ってきましたが、Swingで作ったアプリの外観は美的観点から問題があるように思います。

デフォルトであるクロスプラットフォームL&FのMetalは青色が幅を利かせています。WindowsのTabbedPaneはbackgroundが薄い青色で塗りつぶされて見栄えは最悪です。

これを避けるためにコードの頭でUIManager.setLookAndFeelにてL&FをシステムL&Fに変更してみました。しかしWindowsのシステムL&Fも今ひとつです。特にJSliderのつまみは何故か透明で見れたもんじゃないです。加えてJButtonのマージンは横方向が無駄に大きく、フォントサイズをかなり小さくしないと”…”と表示されてしまいます。ここまで酷いとJavaでWindowsアプリを作る気にはならないでしょう。

MacのシステムL&FであるAquaは私の使う範囲ではJButtonのデザイン以外特に問題ないのですが、Windowsの場合はMetalもシステムL&Fもダメなので、結局JComponentを継承したクラスを必要なComponentに応じて自製しそれぞれ描画させています。

Swingの機能自体は素晴らしいというのに、肝心の見た目が悪ければプログラマとユーザーは離れてしまいます。新たなGUIライブラリの出現に期待したいところです。

[Java]102 JButtonの継承

JButtonのToolTipTextの位置を調整するため、これを継承したJButtonExクラスを作成しました。

コンストラクタも自動的に継承されるものと思っていましたが、きちんと書かないとエラーになります。

public static class JButtonEx extends JButton {
		public JButtonEx(String str) {
			setText(str);
		}
		public JButtonEx() {
		}
		@Override
		public Point getToolTipLocation(MouseEvent e) {
			Point po = e.getPoint();
			po.x += 20;
			po.y -= 10;
			return po;
		}
	}

[Java]101 UIDefaultsの内容を出力

Look and Feelの自製を見据え、UIDefaultsの内容を出力してみました。

コードはGitHubにあったものに加筆してkeyだけでなくvalueも出力できるようにしました。

以下のようになります。

import java.util.Enumeration;
import java.util.TreeSet;
import javax.swing.UIDefaults;
import javax.swing.UIManager;

public class ListUIDefaultsKeys {
	public static void main(String args[]) throws Exception {
		UIManager.LookAndFeelInfo looks[] = UIManager.getInstalledLookAndFeels();

		for (UIManager.LookAndFeelInfo info : looks) {
			System.out.println("Installed L&F "+info.getName());
		}

		TreeSet<String> lafDefaultKeys = new TreeSet<>();
		UIDefaults defaults = UIManager.getDefaults();

		Enumeration<Object> newKeys = defaults.keys();
		while (newKeys.hasMoreElements()) {
				lafDefaultKeys.add(newKeys.nextElement().toString());
		}

		System.out.println("================== UIDefaults Keys & Values ==================");
		
		int i = 1;
		for (String key : lafDefaultKeys) {
				try {
						String value = defaults.get(key).toString();
						System.out.println(String.format("%03d", i) + " " + key );
						System.out.println("    " + value);
				} catch (Exception e){
						e.printStackTrace();
				}
				i++;
		}
	}
}
--------------------------------------------------

出力の一部
--------------------------------------------------
================== UIDefaults Keys & Values ==================
001 AbstractButton.click.textAndMnemonic
    クリック
002 AbstractDocument.addition.textAndMnemonic
    追加
003 AbstractDocument.deletion.textAndMnemonic
    削除
004 AbstractDocument.redo.textAndMnemonic
    やり直し
005 AbstractDocument.styleChange.textAndMnemonic
    スタイル変更
006 AbstractDocument.undo.textAndMnemonic
    元に戻す
007 AbstractUndoableEdit.redo.textAndMnemonic
    やり直し
008 AbstractUndoableEdit.undo.textAndMnemonic
    元に戻す
009 AuditoryCues.allAuditoryCues
    [Ljava.lang.Object;@4ac68d3e
010 AuditoryCues.cueList
    [Ljava.lang.Object;@4ac68d3e
011 AuditoryCues.noAuditoryCues
    [Ljava.lang.Object;@3339ad8e
<以下略>

参考サイト