SwingコンポーネントをXPathで操作する
フレームやダイアログの中から特定の条件を満たすコンポーネントを見つけたり、トラバースできると便利だと思い、JDOMをベースにして以下のプログラムを作ってみました。
- SwingDOMBuilder
- SwingPath
Swingのコンポーネント階層をDOMに変換して、XPathを使えるようにするというものです。
使用例としては、メニュー項目をルックアップして実行したり
((JMenuItem) SwingPath.selectSingleComponent(
frame, "//JMenuItem[@text='上書き保存']")).doClick();
ボタンを全てdisabledにしたり
for (Component c : SwingPath.selectComponents(dialog, "//JButton")) { c.setEnabled(false); }
といったことができます。GUIテストの自動化とか、ロボット的な用途に使えるんじゃないかと思っています。
ソースは以下です。
SwingDOMBuilder.java
import java.awt.Component; import java.awt.Container; import javax.swing.AbstractButton; import javax.swing.JComponent; import javax.swing.JMenu; import org.jdom.Document; import org.jdom.Element; /** * @author Kaisei Hamamoto */ public class SwingDOMBuilder { public Document build(Component c) { Element root = doBuild(null, c); return new Document(root); } private Element doBuild(Element parent, Component c) { if (c == null) { return null; } Element elem = createElement(c); if (parent != null) { parent.addContent(elem); } if (c instanceof JMenu) { // JMenu は getComponents() でメニュー項目を取れないので特別扱い JMenu menu = (JMenu) c; for (int i = 0; i < menu.getItemCount(); i++) { doBuild(elem, menu.getItem(i)); } } else if (c instanceof JComponent) { JComponent jc = (JComponent) c; for (Component child : jc.getComponents()) { doBuild(elem, child); } } else if (c instanceof Container) { Container container = (Container) c; for (Component child : container.getComponents()) { doBuild(elem, child); } } return elem; } private Element createElement(Component c) { Element elem = new ComponentElement(c); // 基礎的なプロパティを属性化する storeAttribute(elem, "name", c.getName()); storeAttribute(elem, "enabled", c.isEnabled()); storeAttribute(elem, "visible", c.isVisible()); if (c instanceof AbstractButton) { AbstractButton button = (AbstractButton) c; storeAttribute(elem, "text", button.getText()); storeAttribute(elem, "selected", button.isSelected()); } return elem; } private void storeAttribute(Element elem, String name, Object value) { if (value != null) { elem.setAttribute(name, value.toString()); } } }
ComponentElement.java
import java.awt.Component; import org.jdom.Element; import org.jdom.Namespace; /** * @author Kaisei Hamamoto */ public class ComponentElement extends Element { private static final long serialVersionUID = -3715167149549579682L; private Component component; public ComponentElement(Component component) { this.component = component; setName(component.getClass().getSimpleName()); setNamespace(Namespace.NO_NAMESPACE); } public Component getComponent() { return component; } }
SwingPath.java
import java.awt.Component; import java.util.ArrayList; import java.util.List; import org.jdom.JDOMException; import org.jdom.xpath.XPath; /** * @author Kaisei Hamamoto */ public class SwingPath { private static Object wrapContext(Object context) { if (context instanceof Component) { return new SwingDOMBuilder().build((Component) context); } else { return context; } } public static Component selectSingleComponent(Object context, String path) { try { Object node = XPath.selectSingleNode(wrapContext(context), path); if (node instanceof ComponentElement) { return ((ComponentElement) node).getComponent(); } else { return null; } } catch (JDOMException e) { throw new IllegalArgumentException(e); } } public static List<Component> selectComponents(Object context, String path) { try { List<Component> result = new ArrayList<Component>(); for (Object node : XPath.selectNodes(wrapContext(context), path)) { if (node instanceof ComponentElement) { result.add(((ComponentElement) node).getComponent()); } } return result; } catch (JDOMException e) { throw new IllegalArgumentException(e); } } }