Swing框架之Renderer之一

Swing组件根据其所操作的数据类型分为两种,一种是标量数据类型的组件,一类是复合数据类型的组件。标量数据类型的组件操作的是基本类型的数据,如字符串、布尔、数字等,此类型组件包括JTextField、JCheckBox、JLabel、JButton等。复合数据类型的组件操作的是诸如向量、矩形和非线形等类型的数据。向量数据类型的组件有JComboBox、JList,矩形数据类型的组件有JTable,非线形数据类型的组件如JTree。

        为更形象地展现各种类型的数据,复合数据类型的组件往往采用标量数据类型组件来表现每种数据元素。比如JTable的某一列数据是字符串类型,那么该列的单元格往往用JLabel方式展现每个字符串;如果一列数据是布尔类型,那么该列的单元格往往用JCheckBox方式展现每个布尔值。

        如何实现复合数据类型的组件的渲染呢?最直接的是在paint方法中一个一个地根据数据类型画出每一个组件,但这种方法很显然代码复用率很低,大量重复了相应标量型组件的代码,代码的维护和同步会非常困难,也不容易实现皮肤切换。

        为解决此问题,Swing体系中提出了所谓渲染器(Renderer)的概念,其核心思想是使用接口,封装和复用已有标量型组件的渲染代码,降低代码重复率,提高组件的可扩展性。

        渲染器是如何工作的呢?我们以JTable的渲染代码来演示渲染器的工作原理。下面代码是从JTable的UI类javax.swing.plaf.basic.BasicTableUI中摘出的部分代码(JDK 6):

   //注:为清晰期间,只显示了关键代码
    public void paint(Graphics g, JComponent c) {
       ......
       //计算当前可显示区域的行列范围
       ......
        // 画出表格的分割线
        paintGrid(g, rMin, rMax, cMin, cMax);

        // 画出表格单元格的内容
    paintCells(g, rMin, rMax, cMin, cMax);

        //如果正在拖动列,画出正在被拖动的线
        paintDropLines(g);
    }
        paintCells实现所有单元格的渲染过程:

    private void paintCells(Graphics g, int rMin, int rMax, int cMin, int cMax) {
           ......
        for(int row = rMin; row <= rMax; row++) {
        cellRect = table.getCellRect(row, cMin, false);
                for(int column = cMin; column <= cMax; column++) {
                   //遍历表格的每一个单元格
                    .......
                    paintCell(g, cellRect, row, column);
                    cellRect.x += columnWidth;
               }
            }
          ......
    }
         渲染单元格的方法paintCell的实现:

    private void paintCell(Graphics g, Rectangle cellRect, int row, int column) {
        if (如果当前格是正在编辑的) {
             //重新设置编辑器的边框
             ......
        }
        else {
             //获取当前格的渲染器
            TableCellRenderer renderer = table.getCellRenderer(row, column);
            //配置当前的渲染器
            Component component = table.prepareRenderer(renderer, row, column);
            //使用渲染器来渲染当前表格
            rendererPane.paintComponent(g, component, table, cellRect.x, cellRect.y,
                                        cellRect.width, cellRect.height, true);
        }
    }

    public Component prepareRenderer(TableCellRenderer renderer, int row, int column) {
        Object value = getValueAt(row, column);
        ......
        //调用渲染器的配置方法配置并获取合适的渲染组件
    return renderer.getTableCellRendererComponent(this, value,
                                                  isSelected, hasFocus,
                                                  row, column);
    }
        如何使用渲染器返回的组件渲染当前的单元格呢?JTable在自己内部隐藏了一个所谓的CellRendererPane组件,该组件是一个“零实现”的容器组件。虽然被添加到JTable上,但它是不可见的,其paint和update方法都为空,仅仅作为临时容纳渲染组件的容器,目的是将渲染组件粘合到JTable组件树上,使得渲染组件有效化,以便使它们达到渲染前的正确状态。下面代码演示了CellRendererPane的概要结构:

public class CellRendererPane extends Container implements Accessible
{
    //构造函数
    public CellRendererPane() {
    super();
    //注意CellRendererPane的布局管理器为空,后面渲染时有用!
    setLayout(null);
    //不可见,使之不被显示在JTable上
    setVisible(false);
    }
   //零实现
    public void invalidate() { }
   //零实现
    public void paint(Graphics g) { }
   //零实现
    public void update(Graphics g) { }
   ......
   //下面是CellRendererPane的paintComponent方法:
    public void paintComponent(Graphics g, Component c, Container p, int x, int y, int w, int h, boolean shouldValidate) {
          ......
    if (c.getParent() != this) {//如果渲染组件c还没有添加当前CellRendererPane中
         //添加进去
        this.add(c);
    }
//构造函数将布局管理器设置为空,setBounds将渲染组件设置成相应的位置和尺寸< /p>
    c.setBounds(x, y, w, h);
//将渲染组件在JTable的组件树上有效化,目的是如果渲染组件是一个有内部结构的复合组件,比如一个JTextField 和一个JButton构成的一个复合框组件,该过程将会使内部组件进行布局,并递归此过程,使得该组件达到正常显示应该达到的效果。< /font>
    if(shouldValidate) {
        c.validate();
    }
//下面主要处理双缓冲问题,可略去
......
    //准备图形对象
    Graphics cg = g.create(x, y, w, h);
    try {
//调用渲染组件的重画方法渲染,注意由于cg其实是JTable的图形对象,因此其效果是将该组件渲染到JTable上相应单元格,从而达到了代码复用。
        c.paint(cg);
    }
           ......
    }
    ......
}

        渲染器的核心思想都体现在上面红色代码标注的部分。将JTable的图形对象传递给组件的paint的方法,产生的结果是将组件画到了JTable上。其实Swing打印的原理也大抵如此,只不过这儿的图形对象变成了打印机的图形对象。虽然大部分Swing组件都专门对打印进行了专门的处理(主要是因为有一些图形元素不希望被打印的,比如填充的内容往往不希望打印,可能是太耗墨了),但基本过程是一样的。

        渲染器的思想很像是摄像机、镜子等成像原理。作个比喻,如果你想获取某人的图像,一种方法是将此人一点点用笔画出来。另种方法是通过光线将此人照到镜子里或用照相机拍摄下来。其好处是不管是什么物体,都可以映射出来,具有很强的可扩展性。比如JTable中,表格中不仅可以使用JLabel、JCheckBox、JComboBox等简单组件作为渲染器,而且可以使用其它任何的Swing组件进行渲染,包括复杂的组件JTable(比如实现表格套表的风格)、自定义的组件(比如嵌入图片)。渲染器方法带来的好处不仅仅是组件代码的复用,更带来了无限的可扩展性!

        渲染器思想在Swing中有着广泛的应用。除利用它们实现JTable、JList、JTree和JComboBox等标准组件,还可以实现界面设计工具中属性页、类似UML设计图、类似于MS Excel风格的电子表格等更为复杂的界面组件,甚至IDE中常见的界面设计工具也是利用了渲染器的思想,它把整个组件树当作一个大渲染器,渲染出当前图形用户界面的设计效果。

        渲染器是Swing展现复杂数据结构的利器。但是Swing组件不仅仅被用作展现数据,通常还是编辑数据的地方。实际上纯粹展现数据的Swing组件很少,标准组件中也许只有JLabel。复合数据类型的组件往往使用渲染器原理实现组件的渲染,使用所谓in-place editor实现组件的编辑。渲染器Renderer和Editor的结合赋予了Swing强大的灵活性,JTable、等组件这两种原理结合的代表。后续文章将讲述in-place editor在Swing中的使用。

文章引用自:

你可能感兴趣的:(数据结构,C++,框架,swing,C#)