概要

Summary

SwingアプリケーションのLookAndFeelagentを使って外部から切り替えてデバッグします。
LookAndFeelDebugAgent.png
LookAndFeelDebugAgent.png

ソースコード

ソースコード

package swinghelper;
#spanadd

#spanend
import java.awt.*;
#spandel
import java.awt.event.*;
#spanend
#spandel
import java.lang.instrument.*;
#spanend
#spanadd
import java.awt.event.ActionEvent;
#spanend
#spanadd
import java.lang.reflect.Constructor;
#spanend
#spanadd
import java.lang.reflect.InvocationTargetException;
#spanend
#spanadd
import java.util.Arrays;
#spanend
#spanadd
import java.util.Optional;
#spanend
#spanadd
import java.util.logging.Logger;
#spanend
import javax.swing.*;
#spanadd
import com.formdev.flatlaf.*;
#spanend

public class LookAndFeelDebugAgent {
  public static void premain(String args) throws Exception {
    EventQueue.invokeLater(new Runnable() {
      @Override public void run() {
        createAndShowGUI();
      }
  public static Component makeUI() {
    Box box = LookAndFeelUtils.createLookAndFeelBox();
    box.add(Box.createVerticalGlue());
    box.setBorder(BorderFactory.createEmptyBorder(5, 25, 5, 25));
    return box;
  }
#spanadd

#spanend
  public static void premain(String args) {
    EventQueue.invokeLater(() -> {
      JFrame frame = new JFrame("LookAndFeelDebugAgent");
      frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
      frame.getContentPane().add(makeUI());
      frame.pack();
      frame.setVisible(true);
    });
  }
  public static void createAndShowGUI() {
    ButtonGroup group = new ButtonGroup();
    Box box = Box.createVerticalBox();
    for(LookAndFeelEnum lnf : LookAndFeelEnum.values()) {
      JRadioButton rb = new JRadioButton(new ChangeLookAndFeelAction(lnf));
      group.add(rb); box.add(rb);
    }
    box.add(Box.createVerticalGlue());
    box.setBorder(BorderFactory.createEmptyBorder(5,25,5,25));
#spanadd
}
#spanend

    JFrame frame = new JFrame("LnF");
    frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    frame.getContentPane().add(box);
    frame.pack();
    frame.setVisible(true);
#spanadd
final class LookAndFeelUtils {
#spanend
  private static String lookAndFeel = UIManager.getLookAndFeel().getClass().getName();
#spanadd

#spanend
  private LookAndFeelUtils() {
    /* Singleton */
  }
  private static enum LookAndFeelEnum {
    Metal  ("javax.swing.plaf.metal.MetalLookAndFeel"),
    Mac  ("com.sun.java.swing.plaf.mac.MacLookAndFeel"),
    Motif  ("com.sun.java.swing.plaf.motif.MotifLookAndFeel"),
    Windows("com.sun.java.swing.plaf.windows.WindowsLookAndFeel"),
    GTK  ("com.sun.java.swing.plaf.gtk.GTKLookAndFeel"),
    Nimbus ("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
    private final String clazz;
    private LookAndFeelEnum(String clazz) {
      this.clazz = clazz;
#spanadd

#spanend
  public static Box createLookAndFeelBox() {
    Box box = Box.createVerticalBox();
    ButtonGroup buttonGroup = new ButtonGroup();
    for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
      AbstractButton b = makeButton(info);
      initLookAndFeelAction(info, b);
      box.add(b);
      buttonGroup.add(b);
    }
    public String getClassName() {
      return clazz;
    for (Class<?> clz : Arrays.asList(FlatLightLaf.class, FlatDarkLaf.class)) {
      String full = clz.getName();
      String name = full.substring(full.lastIndexOf('.') + 1);
      UIManager.LookAndFeelInfo info = new UIManager.LookAndFeelInfo(name, full);
      AbstractButton b = makeButton(info);
      initLookAndFeelAction(info, b);
      box.add(b);
      buttonGroup.add(b);
    }
    return box;
  }
  private static class ChangeLookAndFeelAction extends AbstractAction{
    private final String lnf;
    protected ChangeLookAndFeelAction(LookAndFeelEnum lnfe) {
      super(lnfe.toString());
      this.lnf = lnfe.getClassName();
      this.setEnabled(isAvailableLookAndFeel(lnf));
    }
    private static boolean isAvailableLookAndFeel(String lnf) {
      try{
        Class lnfClass = Class.forName(lnf);
        LookAndFeel newLnF = (LookAndFeel)(lnfClass.newInstance());
        return newLnF.isSupportedLookAndFeel();
      }catch(Exception e) {
        return false;
#spanadd

#spanend
  private static AbstractButton makeButton(UIManager.LookAndFeelInfo info) {
    boolean selected = info.getClassName().equals(lookAndFeel);
    return new JRadioButton(info.getName(), selected);
  }
#spanadd

#spanend
  public static void initLookAndFeelAction(UIManager.LookAndFeelInfo info, AbstractButton b) {
    String cmd = info.getClassName();
    b.setText(info.getName());
    b.setActionCommand(cmd);
    b.setHideActionText(true);
    b.addActionListener(e -> setLookAndFeel(cmd));
  }
#spanadd

#spanend
  private static void setLookAndFeel(String newLookAndFeel) {
    String oldLookAndFeel = lookAndFeel;
    if (!oldLookAndFeel.equals(newLookAndFeel)) {
      try {
        UIManager.setLookAndFeel(newLookAndFeel);
        lookAndFeel = newLookAndFeel;
      } catch (UnsupportedLookAndFeelException ignored) {
        Toolkit.getDefaultToolkit().beep();
      } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
        Logger.getGlobal().severe(ex::getMessage);
        return;
      }
      updateLookAndFeel();
    }
    @Override public void actionPerformed(ActionEvent e) {
      try{
        UIManager.setLookAndFeel(lnf);
      }catch(Exception ex) {
        ex.printStackTrace();
        System.out.println("Failed loading L&F: " + lnf);
      }
      for(Frame f:Frame.getFrames()) {
        if(f instanceof JFrame) {
          SwingUtilities.updateComponentTreeUI(f);
          f.pack();
        }
      }
  }
#spanadd

#spanend
  private static void updateLookAndFeel() {
    for (Window window : Window.getWindows()) {
      SwingUtilities.updateComponentTreeUI(window);
    }
  }
}

解説

Look And Feelを切り替えるためのパネルが一緒に起動し、対象アプリケーションのソースコードを変更することなく、LookAndFeelの変更をテストすることが出来ます。

Description

  • アプリケーションと同時にLook And Feelを切り替えるための別パネルを起動して、対象アプリケーションのソースコードを変更することなくLookAndFeelの変更がテスト可能
javaagentを使うので、JDK 1.6以上で以下のように起動します。
  • このサンプルではデフォルト以外のLook And FeelとしてFlatLightLafFlatDarkLafを追加して以下のようにBoot-Class-Pathを指定してコンパイルしている
    #spanend
    #spanadd
    <target name="package" depends="compile">
    #spanend
      <jar jarfile="${build.dir}/${name}-${version}.jar" basedir="${build.dest}">
        <manifest>
          <!--attribute name="Main-Class" value="${main.class}" /-->
          <!--attribute name="Agent-Class" value="${main.class}" /-->
          <attribute name="Premain-Class" value="${main.class}" />
          <attribute name="Class-Path" value="${runtime.classpath}" />
          <attribute name="Can-Retransform-Classes" value="true"/>
          <attribute name="Boot-Class-Path" value="flatlaf-3.5.jar" />
        </manifest>
      </jar>
    #spanadd
    </target>
    #spanend
    #spanadd
    
  • javaagentを使用するため、JDK 1.6以上で以下のように起動する
    #spandel
    java -javaagent:lnfagent.jar -jar example.jar
    #spanend
    #spanadd
    java -javaagent:lnfagent1.0.1.jar -jar example.jar
    #spanend
    

コメント

Reference

Comment