groovy已经在SwingBuilder里为我们预设了很多swing组件,以使我们可以用下面这样的快捷形式来构建界面:
swingBuilder.frame( //属性集 ){ //子组件 }
对于仅仅使用swing的原生组件,这没有任何问题,但在实际开发中可能会遇到需要使用对原生组件扩展后的自定义组件(如一个继承了JPanel的MyPanel类)的情况,而这个类明显不可能出现在SwingBuilder的预设列表里,那这种情况下是不是就意味着不可以使用groovy为我们提供的便利,只能用回传统的java形式去构建界面呢?
答案是否定的。SwingBuilder虽然不会预设用户自定义的组件,但它提供了几个接口可以让用户把自定义的组件设置进去,这样就依然可以用groovy的快捷形式来构建界面了。这几个接口分别是:
public void registerFactory(String name, Factory factory)
public void registerBeanFactory(String theName, Class beanClass)
registerFactory()
registerFactory方法可以注册一个自定义组件,name指定了通过swingBuilder构建组件的名称,如JFrame在swingBuilder里预设的name是"frame",这就使得我们可以通过swingBuilder.frame()构建一个JFrame。
第二个参数接收一个实现了Factory接口的类,这个类需指明(实现)如何实例化自定义组件(newInstance),如何处理add进来的子组件(setChild)等一系列策略。通常并不需要直接实现Factory接口,groovy为我们提供了一个抽象类(AbstractFactory),我们应优先继承这个类,然后再按需挑相应的方法进行重写。下面来看一个如何使用这个接口的例子:
/** * 自定义了一个可以显示背景图片的面板 * @author keenlight * */ class ImagePanel extends JPanel{ /** * */ private static final long serialVersionUID = 1L private BufferedImage image public ImagePanel(BufferedImage image){ super() this.image = image } public ImagePanel(BufferedImage image, LayoutManager layout) { super(layout) this.image = image } public void paintComponent(Graphics g) { super.paintComponent(g) if(image != null){ g.drawImage(image, 0, 0, this) } } } /** * ImagePanel的Factory * @author keenlight */ class ImagePanelFactory extends AbstractFactory{ private BufferedImage image ImagePanelFactory(BufferedImage image){ this.image = image } @Override public Object newInstance(FactoryBuilderSupport builder, Object name, Object value, Map properties) throws InstantiationException, IllegalAccessException { def layout = properties.remove("layout") new ImagePanel(image, layout) } public void setChild(FactoryBuilderSupport builder, Object parent, Object child) { if (!(child instanceof Component) || (child instanceof Window)) { return } parent = parent as ImagePanel try { def constraints = builder.context.constraints if (constraints != null) { parent.add(child, constraints) if (child instanceof JComponent) { child.putClientProperty(LayoutFactory.DEFAULT_DELEGATE_PROPERTY_CONSTRAINT, constraints) } builder.context.remove('constraints') } else { parent.add(child) } } catch (MissingPropertyException mpe) { parent.add(child) } } }
定义好这些类后如何使其生效?只需一步:
def bgImage = ImageIO.read(new File(image_path)) swingBuilder.registerFactory("imagePanel", new ImagePanelFactory(bgImage))
注册好之后,ImagePanel组件便可像其他原生组件一样使用了(如上文的JFrame一样)。
registerBeanFactory()
registerBeanFactory是registerFactory的一种便利形式,调用此接口无需提供Factory,只需提供自定义类的类名即可。比如原生组件里的JPanel就是用这个接口注册的,SwingBuilder类中注册JPanel的源码如下:
registerBeanFactory("panel", JPanel)
这样就可以把JPanel注册为"panel"来使用了。其实registerBeanFactory这个方法里面一样也实例化了一个继承了AbstractFactory类,它会把调用这个方法注册的组件当做一个javaBean(方法名已经很明显了),所以它在继承AbstractFactory类时重写的newInstance方法里是直接返回调用class.newInstance()的实例化结果,class由第二个入参提供。所以对于那些在实例化时调用默认构造器就足够的组件,用registerBeanFactory()方法来注册会更为便利。当然,对于我上面定义的ImagePanel来说就不行了,因为ImagePanel类在实例化时需要接受一个图片对象,所以必须使用略麻烦一点的registerFactory()。
小结
SwingBuilder清晰简洁的表现形式可以提高swing开发效率,而有了注册自定义组件的接口我们也不必再在java传统形式与SwingBuilder之间纠结,因为所有以往用java编写的自定义组件都可以以SwingBuilder的形式来重用了。