最近、Java 1.4 で Swing アプリを作った。Windows 用の Look&Feel (LaF) が、微妙にネイティブのと違っていて気持ち悪い。せめてフォントだけでも変えられたら、だいぶまともなのに。
というわけで、変えてみた (と書きつつ、以下のコード例は気まぐれで Java 6)。
BasicLookAndFeel サブクラスを作る
LaF を自作するには、javax.swing.LookAndFeel のサブクラスを作ればいい。カスタマイズする元の LookAndFeel を継承してもいいんだけど、非公開のクラスだったりするし、カスタマイズ対象は実行時に決められた方がいい。ので、LookAndFeel を継承するかたちで、既存の LookAndFeel に対するラッパーを作ればよさそうだ。
……と思ってたんだけど、LookAndFeel を直接継承したら、Java 1.4 で例外が起きる箇所があった。ので、BasicLookAndFeel を継承することにした。
public class OverriderLookAndFeel extends BasicLookAndFeel { private BasicLookAndFeel base;
こんな感じ。コンストラクタはお好みで。
getDefaults() をオーバーライド
次に、getDefaults() メソッドをオーバーライドして、値をすりかえる。
public UIDefaults getDefaults() { UIDefaults defaults = base.getDefaults(); for (Map.Entry entry : defaults.entrySet()) { String key = entry.getKey().toString(); if (key.toLowerCase().endsWith("font")) { if (entry.getValue() instanceof UIDefaults.ActiveValue) { UIDefaults.ActiveValue av = new OverridingActiveValue( (UIDefaults.ActiveValue)entry.getValue(), "MS UI Gothic", 12); entry.setValue(av); } } } return defaults; }
javax.swing.UIDefaults は、Hashtable のサブクラス (この継承関係が、時代を感じさせるなぁ……)。キーが String で、値に UIResource やら UIDfaults.ActiveValue やら UIDefaults.LazyValue やらが入っている。
ここでは、フォントのファミリとサイズだけ変えたかったので、キーの末尾が "font" または "Font" のエントリだけを拾って、UIDefaults.ActiveValue のラッパクラス (後述) ですりかえている。
ちなみに、キャストじゃなくて toString() で String にしているのは、Java 6u3 の Windows LaF だと動かなかったから。よく見たら、キーの中に StringBuffer が混じっていたのだった。これってありなんかな。
その他のメソッド
他のメソッドは、base に対して委譲する。Eclipse でいえば、メニューの「ソース」→「委譲メソッドの生成」を使うとらくちん。getID() あたりは自分で値を決めないといけないけど、クラス名でも返しておけばとりあえず動く。
public boolean isSupportedLookAndFeel() { return base.isSupportedLookAndFeel(); }
値もラッピング
UIDefaults に入っている値が Font とか Color なら話は簡単なんだけど、UIResource、あるいは、UIDefaults.ActiveValue や UIDefaults.LazyValue になっている。そこで、これらのクラスについてもラッパーが必要になる。
今回はフォントのファミリとサイズだけ変えたかったので、自作 LookAndFeel の中に、UIDefaults.ActiveValue のラッパーを作った。
private static final class OverridingActiveValue implements UIDefaults.ActiveValue { private UIDefaults.ActiveValue base; private String fontName; private int size; OverridingActiveValue(UIDefaults.ActiveValue base, String fontName, int size) { this.base = base; this.fontName = fontName; this.size = size; } public Object createValue(UIDefaults table) { Object o = base.createValue(table); if (o instanceof FontUIResource) { FontUIResource r = (FontUIResource)o; return new FontUIResource(fontName, r.getStyle(), size); } else { return o; } } }
だいたいこんな感じで、とりあえず動いた。これで、「フォントを設定し直した JButton を作るユーティリティメソッド」みたいなことをしなくてよくなった。めでたしめでたし。