昨晚回去后还是觉得Component对象本身说的太简单,想来想去,觉得内容实在是太多,有必要补充两个续文说明Component的其它概念。今天介绍Swing组件paint方法的处理流程,这个流程能使我们理解许多Swing机制。明天续文讲述Swing事件处理器、双缓冲和布局管理器等原理。
=====================================
Swing组件的paint方法是内部接口方法,一般用户不要直接调用这个方法,它总是在事件调度线程中调用。一般说来除了系统刷新事件触发这个方法,Component的repaint也触发这个方法的调用。repaint方法常用于当组件状态发生变化时刷新界面使用。repaint方法是Swing中少数几个线程安全的方法,可以在任何线程中调用它。它的原理是往事件队列中post一个PAINT事件。由于事件队列的事件是被事件调度线程同步执行的,所以这个方法总是线程安全的。事件调度线程从PAINT事件中获取事件源组件,从系统申请到图形设备资源后,调用该组件的update方法。update是AWT时代遗留下来的产物,本意是AWT组件画好组件背景后,再调用paint方法画出组件的前景。Swing出现后这个方法就被弃用了,所有逻辑都转到paint方法里。Update只是简单地调用paint方法来完成组件的渲染。老的Java教材上经常可以看到,所谓repaint调度update方法,update接着调用paint方法,自定义组件需要重载paint方法等话语,就是因为这个历史造成的。
上篇文章中的MyButton的paint方法实现是一个非常老式的做法。现在JComponent的实现已经把paint方法改造成可以嵌套多重机制地方,这些机制包括层次渲染、边框、透明背景、双缓冲以及皮肤等。这些机制分别实现不同目的的组件提供了方便。
图形用户界面的组件按照其在组件树上的角色可以分为容器组件和叶组件。Swing模型把叶组件当作是特殊、没有子组件的容器组件,只是JComponent继承Container类,所有Swing组件继承JComponent的原因。
JComponent在paint方法中首先根据组件是否需要使用双缓冲,封装好图形设备对象,然后经过一番处理后调用paintComponent方法画出自身,然后调用paintBorder画出边框,最后调用paintChildren来完成子组件的渲染。
paintComponent意思是画出组件自身,不包括子组件。因此前一文章中的MyButton可以通过覆盖paintComponent方法来完成MyButton的重画。在JComponent实现中,JDK 6的paintComponent的代码为:
protected void paintComponent(Graphics g) {
if (ui != null) {
Graphics scratchGraphics = (g == null) ? null : g.create();
try {
ui.update(scratchGraphics, this);
}
finally {
scratchGraphics.dispose();
}
}
}
这个方法首先检测组件是否安装了UI Delegate,如果安装了就将渲染过程代理给UI Delegate。这儿是嵌入皮肤的地方。JDK 6中JComponent对应的UI Delegate的update方法缺省的实现是:
public void update(Graphics g, JComponent c) {
if (c.isOpaque()) {
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(),c.getHeight());
}
paint(g, c);
}
可以看出,背景透明机制在这儿实现。首先UI Delegate对象判断Component是否背景透明的,如果不是透明的,则使用背景色填充整个Component区域,然后调用paint(g, c)来完成组件在这种LookAndFeel种的渲染。了解了这些后,我们几乎就明白了Swing如何实现背景透明和如何切换皮肤。由于后面的文章还会对UI Delegate和皮肤机制详细描述,这儿就到此为止。
目前还不要求实现皮肤,在这种情况下只需要重载paintComponent方法就行了,如果需要背景透明机制,可以模仿上面代码,MyButton的paintComponent可以这样写:
public void paintComponent(Graphics g) {
if (isOpaque()) {
g.setColor(getBackground());
g.fillRect(0, 0, getWidth(), getHeight());
}
if(pressed){//按钮按下去了
//画出按下的样子
}else{
//画出抬起的样子
}
}
paintBorder意思是画出组件的边框。Swing所有组件都有边框的概念,就是说可以为任何组件添加各种边框,包括自定义的边框。JDK 6中JComponent的paintBorder的实现是这样的:
protected void paintBorder(Graphics g) {
Border border = getBorder();
if (border != null) {
border.paintBorder(this, g, 0, 0, getWidth(), getHeight());
}
}
非常直接,如果自己有border,就将画自己边框的任务代理给了这个border,并传给它图形设备和边界参数。Swing缺省提供了大量的各种各样的边框。同样可以定义自己的边框,实现方法就是继承Border类,Border类中有三个方法要实现,它们的含义如下:
public interface Border
{
//这儿是画出组件边框的地方。
void paintBorder(Component c, Graphics g, int x, int y, int width, int height);
//这儿是定义边框边界的地方,组件可以根据这信息,安排它的内容。
Insets getBorderInsets(Component c);
//边框的背景是不是透明的?不是透明的要负责画出边框的背景。是透明的使用组件的背景。
boolean isBorderOpaque();
}
这儿实现一个简单的红线边框作为演示:
public class RedLineBorder implements Border{
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height){
g.setColor(Color.red);//设置为红色
g.drawRect(x,y, width, height);//画出边框
}
public Insets getBorderInsets(Component c){
return new Insets(1,1,1,1); //四周都是1
}
public boolean isBorderOpaque(){
return false; //背景透明
}
}
paintChildren完成容器类组件的子组件的渲染。JDK缺省的实现是调用各个自组件的paint方法。一般来说不需要重载这个方法。如果想改变诸如组件Z-order遮挡顺序,可以覆盖这个方法,从相反顺序调用组件的paint方法。
到这儿我们对Swing的结构有了更深化的理解,UI Delegate机制也已经初露倪端。还有几个重要Swing Component概念或者机制没有讲,明天的续文再对它们做出说明。