java委托设计模式之ComponentUI

来源:互联网 发布:陶瓷产品设计软件 编辑:程序博客网 时间:2024/06/05 18:34

Swing的体系结构被称为模型委托结构而不是模式-视图-控制器结构,Swing将每个组件(JComponent及其派生类)的视图和控制都封装到一个与其相对应的UI委托的对象之中(ComponentUI及其派生类的对象)。可插拔LAF的设计意味着实现组件展现(Look)和事件处理(Feel)的部分是被代理到独立的UI对象中的,这些UI对象是由当前的LAF提供的,它们可以被动态地修改。每个JComponent对象都有一个与其相对应的JComponentUI对象,例如与JButton相对应的UI delegate为ButtonUI,JSlider相对应的UI delegate为SliderUI,JComponent组件自身的绘制都是通过ComponentUI绘制的。

 

ComponentUI的方法描述了一个UI委托和使用它的组件之间进行通讯的基本原理。 下面给出了ComponentUI的几个重要方法:

◆staticComponentUI createUI(JComponent c):该方法通常用来返回UI委托的一个共享实例,该UI委托通过定义ComponentUI子类本身而定义。这个共享实例用于相同类型的组件之间的共享(例如,所有使用金属外观的JButtons共享同样的静态UI委托实例,默认情况下,该委托实例在javax.swing.plaf.metal.MetalButtonUI中定义。 

◆InstallUI(JComponentc):该方法在特定的组件上安装ComponentUI。通常会给组件和它的模型添加一个监听器,当状态发生改变时来通知UI委托进行视图的更新。 

◆Update(Graphics g, JComponent c):如果组件是不透明的,那么应该描绘它的背景并调用paint(Graphics g,JComponent C)方法。 

◆Paint(Graphics g, JComponent c):为了能够正确地描绘,该方法要从组件收集所有需要的信息以及可能的模型。 

 

下面详细叙述Swing类JComponent类通过UI绘制自身的过程:

窗口的绘制都是事件触发的,例如当窗口应用程序的启动,窗口的大小改变,窗口的平移,被覆盖部分重新恢复等都会触发窗口重绘,也就是调用JComponent或者其子类的paint()方法,这里需要注意,Container类负责管理所有的子窗口,例如JFrame窗口需要重绘时,它首先调用paintComponent()方法绘制自身,然后调用paintBorder()绘制边框,再调用paintChildren()方法绘制通过JComponent.add()方法放置在其上的组件。

 

下面我们来看下JComponent.paint()的介绍及其源代码:

This method actually delegates the work ofpainting to three protected methods: <code>paintComponent</code>,<code>paintBorder</code>,and <code>paintChildren</code>.

这三个方法调用是有次序的,首先画自身,然后画边框,最后才能画子件。

public void paint(Graphics g) {

       boolean shouldClearPaintFlags = false;

       if ((getWidth() <= 0) || (getHeight() <= 0)) {

           return;

       }

       Graphics componentGraphics = getComponentGraphics(g);

       Graphics co = componentGraphics.create();

       try {

           RepaintManager repaintManager = RepaintManager.currentManager(this);

           Rectangle clipRect = co.getClipBounds();

           int clipX;

           int clipY;

           int clipW;

           int clipH;

           if (clipRect == null) {

                clipX = clipY = 0;

                clipW = getWidth();

                clipH = getHeight();

           }

           else {

                clipX = clipRect.x;

                clipY = clipRect.y;

                clipW = clipRect.width;

                clipH = clipRect.height;

           }

           if(clipW > getWidth()) {

               clipW = getWidth();

           }

           if(clipH > getHeight()) {

                clipH = getHeight();

           }

           if(getParent() != null && !(getParent() instanceof JComponent)){

                adjustPaintFlags();

               shouldClearPaintFlags =true;

           }

           int bw,bh;

           boolean printing = getFlag(IS_PRINTING);

           if (!printing && repaintManager.isDoubleBufferingEnabled()&&

                !getFlag(ANCESTOR_USING_BUFFER)&& isDoubleBuffered() &&

                (getFlag(IS_REPAINTING) ||repaintManager.isPainting()))

           {

                repaintManager.beginPaint();

                try {

                    repaintManager.paint(this,this, co, clipX, clipY, clipW,

                                        clipH);

                } finally {

                    repaintManager.endPaint();

                }

           }

           else {

                // Will ocassionaly happen in1.2, especially when printing.

                if (clipRect == null) {

                    co.setClip(clipX, clipY,clipW, clipH);

                }

                if(!rectangleIsObscured(clipX,clipY,clipW,clipH)) {

                    if (!printing) {

                        paintComponent(co);

                        paintBorder(co);

                    }

                    else {

                        printComponent(co);

                        printBorder(co);

                    }

                }

                if (!printing) {

                    paintChildren(co);

                }

                else {

                    printChildren(co);

                }

           }

       } finally {

           co.dispose();

           if(shouldClearPaintFlags) {

                setFlag(ANCESTOR_USING_BUFFER,false);

               setFlag(IS_PAINTING_TILE,false);

                setFlag(IS_PRINTING,false);

                setFlag(IS_PRINTING_ALL,false);

           }

       }

}

前端部分代码确定了绘制区的边界坐标。主要看paint 方法中调用了 paintComponent()方法。protected void paintComponent(Graphics g) {

       if (ui != null) {

           Graphics scratchGraphics = (g == null) ? null : g.create();

           try {

                ui.update(scratchGraphics,this);

           }

           finally {

               scratchGraphics.dispose();

           }

       }

}

这里需要注意,Graphics类对象和剪裁区域(也就是需要画图的区域),画笔的颜色,字体等等有关。例如当JPanel调用自身的paint()方法时,它将与其自身相关的Graphics类对象g传递下去,因此只以特定的画笔等设置绘制它自身的区域。

这里最重要的是paintComponent()调用了ComponentUI.update()方法:

public void update(Graphics g, JComponentc) {

       if (c.isOpaque()) {

           g.setColor(c.getBackground());

           g.fillRect(0, 0, c.getWidth(),c.getHeight());

       }

       paint(g, c);

}

ComponentUI的update()方法首先清除整个区域(将整个组件的区域刷成背景色),然后调用ComponentUI.paint()方法

public void paint(Graphics g, JComponent c){},该方法是一个空方法,对于不同的子类有不同的实现方式,例如画按钮和画进度条完全是不一样的实现。

但是JComponent和ComponentUI是如何关联起来的呢?

我们在JComponent中找到如下语句:

/** The look and feel delegate for thiscomponent. */

protectedtransient ComponentUI ui;

也就是说,ComponentUI是管理组件的外观展示和感觉的,这里的感觉就是事件处理,也就是说组件的事件处理是由ComponentUI来做的,对于不同的组件,事件处理方式不同,任意组件都有默认的UI,也就是有默认的外观和事件处理。对于不同的Look and Feel,显示时的效果不同,所以有时我们称Look and Feel为皮肤。

通过上面的分析,我们知道,窗口在需要重绘的情况下,需要调用paint(),默认的paint()方法会调用paintComponent()方法,paintComponent方法会调用与其相对应的UI delegate的update()方法,该方法会调用UI delegate的paint()方法,至此,才真正的绘制组件,当然,组件的绘制方法会因为组件的不同而不同,因此,对于每一个JComponent组件,都有与其相对象的ComponentUI,例如JButton有ButtonUI,JPanel有PanelUI等。这样,组件自身的绘制就转移到了与其相对应的UI上了,这就是委托设计模式。

 

 

原创粉丝点击