|
|
初次接触AWT的开发人员可以看看
PaintDemo example
,那里介绍了一个在AWT程序中怎样使用画图回调方法的例子。
一般情况下,程序应该避免把绘画代码放置在回调方法
paint()
的范围之外。为什么呢?因为
paint
方法之外的绘画代码可能会在不适合画图的时候被调用 — 例如,在部件变为可见之前或者已经在使用一个有效的
Graphics
。同时,不推荐在程序中直接调用
paint()
。
为了使能够由程序触发绘画操作,AWT提供了下面的
java.awt.Component
的方法,这样程序就可以提出一个异步的绘画请求:
public void repaint()
public void repaint(long tm)
public void repaint(int x, int y, int width, int height)
public void repaint(long tm, int x, int y,
int width, int height)
下面的代码显示了一个简单的鼠标监听器的例子,当鼠标按下和抬起的时候,使用
repaint()
来触发“假想按钮”的更新操作。
|
如果部件要呈现复杂的图形,就应该使用带参数的
repaint()
方法,通过参数来指定需要更新的区域。一个比较常见的错误是总是调用无参数的
repaint()
来提出重画请求,这个方法会重画整个部件,经常导致一些不必要的画图处理。
paint() vs. update()
为什么我们要区分绘画操作是”系统触发” 还是”程序触发”呢?因为在“重量级”部件上,AWT对这两种请求的在处理上稍有不同(“轻量级”的情况将在后面介绍),并且不幸的是与此相关的代码非常复杂,难以更改。
对于“重量级”部件,这两种方式的绘画产生于两条不同的途径,取决于是“系统触发”还是“程序触发”。
系统触发的绘画
下面介绍“系统触发”的绘画操作是怎么产生的:
-
AWT确定
是一部分还是整个部件需要绘画。 -
AWT促使事件分派线程调用部件的
paint()
方法。
程序触发的绘画
由程序触发的绘画的产生如下所示:
-
程序确定
是一部分还是全部部件需要重画以对应内部状态的改变。 -
程序调用部件的
repaint()
,该方法向AWT登记了一个异步的请求 -- 当前部件需要重画。 -
AWT促使事件分派线程去调用部件的
update()
方法。
注意
:
在最初的重画请求处理完成之前,如果在该部件上有多次对
repaint()
的调用,那么这些调用可以被合并成对
update()
的一次调用。决定什么时候应该合并多次请求的运算法则取决于具体的实现。如果多次请求被合并,最终被更新的区域将是所有这些请求所要求更新的区域的联合(union)。
-
如果部件没有覆盖(override)
update()
方法,
update()
的默认实现会清除部件背景(如果部件不是“轻量级”),然后只是简单地调用
paint()
方法。
因为作为默认的最终结果都是一样的(
paint()
方法被调用),很多开发人员完全不知道一个分离的
update()
方法的意义。确实,
默认的
update()
的实现最终会转回到对
paint()
方法的调用,然而,如果需要,这个更新操作的 “钩子(hook)”可以使程根据不同的情况来处理程序触发的绘画。程序必须这么设想,对
paint()
的调用意味着
Graphics
的裁剪区”损坏”了并且必须全部重画;然而对
update()
的调用没有这种含义,它使程序做
增量的
绘画。
如果程序希望只把要增加的内容敷盖于已存在于该部件的像素位之上,那么就使用增量画图操作。
UpdateDemo example
示范了一个利用
update()
的优点做增量绘画的程序。
事实上,大多数GUI部件不需要增量绘画,所有大部分程序可以忽略
update()
方法,并且简单地覆盖(override)
paint()
来呈现部件的当前状态。这就意味着不管“系统触发”还是“程序触发”,在大多数部件上的表现从其本质上讲是是等价的。
绘画与轻量级部件
从应用开发人员的观点看,“轻量级”的绘画API基本上和“重量级”一样(即,你只需要覆盖
paint()
方法,同样,调用
repaint()
方法去触发绘图更新)。然而,因为AWT的“轻量级”部件的框架全部使用普通Java代码实现,在轻量级部件上绘画机制的实现方式有一些微妙的不同。
“轻量级”部件是怎样被绘制的
“轻量级”部件需要一个处在容器体系上的“重量级”部件提供进行绘画的场所。当这个“重量级”的“祖宗”被告知要绘制自身的窗体时,它必须把这个绘画的请求转化为对其所有子孙的绘画请求。这是由
java.awt.Container
的
paint()
方法处理的,该方法调用包容于其内的所有可见的、并且与绘画区相交的轻量级部件的
paint()
方法。因此对于所有覆盖了
paint()
方法的Container子类(“轻量级”或“重量级”)需要立刻做下面的事情:
|
如果没有
super.paint()
,那么容器(container)的轻量级子孙类就不会显示出来(这是一个非常普遍的问题,自从JDK1.1初次引进“轻量级”部件之后)。
这种情况相当于注释掉了默认的
Container.update()
方法的执行,从而
不能
使用递归去调用其轻量级子孙类的
update()
或者
paint()
方法。这就意味着任何使用
update()
方法实现增量绘画的重量级
Container
子类必须确保其轻量级子孙在需要时,能够被它的递归操作所调用从而实现重画。幸运的是,只有少数几个重量级的容器(Container)需要增量绘图,所以这个问题没有影响到大多数的程序。
轻量级与系统触发型的画图
为轻量级部件实现窗体行为(显示、隐藏、移动、改变大小等)的轻量级框架的代码全部用Java代码写成。经常的,在这些功能的Java实现中,AWT必须明确地吩咐各个轻量级部件执行绘画(实质上讲这也是系统触发的绘画,尽管它不是源于
本地的
操作系统)。而轻量级框架使用
repaint()
方法来吩咐部件执行绘画,这是我们前面解释过的,将导致一个
update()
的调用而不是
直接地
对
paint()
的调用。因此,对于轻量级,系统触发型的画图操作可以遵循下面的两种途径:
-
系统触发的绘画要求
产生于本地系统
(例如,轻量级的重量级祖先第一次现身的时候),这导致对
paint()
的直接调用。 -
系统触发型的绘图要求
产生于轻量框架
(
例如
,轻量级部件的尺寸改变了),这导致对
update()
的调用,该方法进而默认地调用
paint()
。
简单地讲,这意味着轻量级部件在
update()
和
paint()
之间没有实质的差别,进一步讲这又意味着“增量的绘图技术”不能用到轻量级部件上。
轻量级部件与透明
因为轻量级部件”借用”了本属于其“重量级”祖先的屏幕,所以它们支持“透明”的特征。这样做是因为轻量级部件是从底往上绘画,因此如果轻量级部件遗留一些或者全部它们祖先的像素位而没有画,底层的部件就会”直接显示。”出来。这也是对于轻量级部件,
update()
方法的在默认实现将不再清除背景的原因。
LightweightDemo
例程示范了轻量级部件的透明特征。
“灵活巧妙地”绘画方法
当AWT尝试着使呈现部件的处理尽可能高效率时,部件自身
paint()
的实现可能对整体性能有重大的影响。影响这个处理过程的两个关键领域是:
- 使用裁剪区来缩小需要呈现的范围。
- 应用内部的版面布局信息来缩小对子部件的笼罩范围(仅适用于轻量级).。
如果你的部件很简单 — 比如,如果是一个按钮 — 那么就不值得花费气力去改善它的呈现属性,使它仅仅去绘画与修剪区相交的部分;不理会Graphics的裁剪区直接绘制整个部件反而更划算。然而,如果你创建的部件界面很复杂,比如文本部件,那么迫切需要你的代码使用裁剪信息来缩小需要绘图的范围。
更进一步讲,如果你写了一个容纳了很多部件的复杂的轻量级容器,其中的部件和容器的布局管理器,或者只是容器的布局管理器拥有布局的信息,那么就值得使用所知道的布局信息来更灵活地确定哪个子部件需要绘画。
Container.paint()
的默认实现只是简单地按顺序遍历子部件,检查它是否可见、是否与重换区域相交 — 对于某几个布局管理这种操作就显得不必要的罗嗦。比如,如果容器在100*100的格子里布置部件,那么格子的信息就可以用来更快得确定这10,000个部件中哪个与裁剪框相交,哪个就确实需要绘制。
AWT绘画准则
AWT为绘制部件提供了一个简单的回调API。当你使用它是,要遵循下面的原则:
-
对于大多数程序,所有的客户区绘画代码应该被放置在部件的
paint()
方法中。 -
通过调用
repaint()
方法,程序可以触发一个将来执行的
paint()
调用,不能直接调用
paint()
方法。 -
对于界面复杂的部件,应该触发带参数的
repaint()
方法,使用参数定义实际需要更新的区域;而不带参数调用会导致整个部件被重画。 -
因为对
repaint()
的调用会首先导致
update()
的调用,默认地会促成
paint()
的调用,所以重量级部件应该覆盖
update()
方法以实现增量绘制,如果需要的话(轻量级部件不支持增量绘制) 。 -
覆盖了
paint()
方法的
java.awt.Container
子类应当在
paint()
方法中调用
super.paint()
以保证子部件能被绘制。 - 界面复杂的部件应该灵活地使用裁剪区来把绘画范围缩小到只包括与裁剪区相交的范围。
在Swing中的绘画
Swing起步于AWT基本绘画模式,并且作了进一步的扩展以获得最大化的性能以及改善可扩展性能。象AWT一样,Swing支持回调绘画以及使用
repaint()
促使部件更新。另外,Swing提供了内置的双缓冲(double-buffering)并且作了改变以支持Swing的其它结构(象边框(border)和UI代理)。最后,Swing为那些想更进一步定制绘画机制的程序提供了
RepaintManager
API。
对双缓冲的支持
Swing的最引人注目的特性之一就是把对双缓冲的支持整个儿的内置到工具包。通过设置
javax.swing.JComponent
的”doubleBuffered”属性就可以使用双缓冲:
public boolean isDoubleBuffered()
public void setDoubleBuffered(boolean o)
当缓冲激活的时候,Swing的双缓冲机制为每个包容层次(通常是每个最高层的窗体)准备一个单独的屏外缓冲。并且,尽管这个属性可以基于部件而设置,对一个特定的容器上设置这个属性,将会影响到这个容器下面的所有轻量级部件把自己的绘画提交给屏外缓冲,而不管它们各自的”双缓冲”属性值
默认地,所有Swing部件的该属性值为
true
。不过对于
JRootPane
这种设置确实有些问题,因为这样就使所有位于这个上层Swing部件下面的所有部件都使用了双缓冲。对于大多数的Swing程序,不需要作任何特别的事情就可以使用双缓冲,除非你要决定这个属性是开还是关(并且为了使GUI能够平滑呈现,你需要打开这个属性)。Swing保证会有适宜的
Graphics
对象(或者是为双缓冲使用的屏外映像的
Graphics
,或者是正规的
Graphics
)传递给部件的绘画回调函数,所以,部件需要做的所有事情仅仅就是使用这个
Graphics
画图。本文的后面,在
绘制的处理过程
这一章会详细解释这个机制。
其他的绘画属性
为了改善内部的绘画算法性能,Swing另外引进了几个
JComponent
的相互有关联的属性。引入这些属性为的是处理下面两个问题,这两个问题有可能导致轻量级部件的绘画成本过高:
-
透明(Transparency)
: 当一个轻量级部件的绘画结束时,如果该部件的一部分或者全部透明,那么它就
可能
不会把所有与其相关的像素位都涂上颜色;这就意味着不管它什么时候重画,它底层的部件必须首先重画。这个技术需要系统沿着部件的包容层次去找到最底层的重量级祖先,然后从它开始、从后向前地执行绘画。 -
重叠的部件(Overlapping components)
: 当一个轻量级部件的绘画结束是,
如果
有一些其他的轻量级部件部分地叠加在它的上方;就是说,不管最初的轻量级部件什么时候画完,只要有叠加在它上面的其它部件(裁剪区与叠加区相交),这些叠加的部件必须也要部分地重画。这需要系统在每次绘画时要遍历大量的包容层次,以检查与之重叠的部件。
遮光性
在一般情况下部件是不透明的,为了提高改善性能,Swing增加了读写
javax.swing.JComponent
的
遮光(opaque)
属性的操作:
public boolean isOpaque()
public void setOpaque(boolean o)
这些设置是:
-
true
:部件同意在它的矩形范围包含的里所有像素位上绘画。 -
false
:部件不保证其矩形范围内所有像素位上绘画。
遮光(opaque)
属性允许Swing的绘图系统去检测是否一个对指定部件的重画请求会导致额外的对其底层祖先的重画。每个标准Swing部件的默认
(遮光)opaque
属性值由当前的视-感UI对象设定。而对于大多数部件,该值为
true
。
部件实现中的一个最常见的错误是它们允许
遮光(opaque)
属性保持其默认值
true
,却又不完全地呈现它们所辖的区域,其结果就是没有呈现的部分有时会造成屏幕垃圾。当一个部件设计完毕,应该仔细的考虑所控制的
遮光(opaque)
属性,既要确保透的使用是明智的,因为它会花费更多的绘画时间,又要确保与绘画系统之间的协约履行。
遮光(opaque)
属性的意义经常被误解。有时候被用来表示“使部件的背景透明”。然而这不是Swing对遮光的精确解释。一些部件,比如按钮,为了给部件一个非矩形的外形可能会把“遮光”设置为false,或者为了短时间的视觉效果使用一个矩形框围住部件,例如焦点指示框。在这些情况下,部件不遮光,但是其背景的主要部分仍然需要填充。
如
先前的
定义,遮光属性的本质是一个与负责重画的系统之间订立的契约。如果一个部件使用遮光属性去定义
怎样
使部件的外观透明,那么该属性的这种使用就应该备有证明文件。(一些部件可能更合适于定义额外的属性控制外观怎样怎样增加透明度。例如,
javax.swing.AbstractButton
提供
ContentAreaFilled
属性就是为了达到这个目的。)
另一个毫无价值的问题是遮光属性与Swing部件的
边框(border)
属性有多少联系。在一个部件上,由
Border
对象呈现的区域从几何意义上讲仍是部件的一部分。就是说如果部件遮光,它就有责任去填充边框所占用的空间。(然后只需要把边框放到该不透明的部件之上就可以了)。
如果你想使一个部件允许其底层部件能透过它的边框范围而显示出来 — 即,通过
isBorderOpaque()
判断border是否支持透明而返回值为
false
— 那么部件必须定义自身的遮光属性为false并且确保它不在边框的范围内绘图。
“最佳的”绘画方案
部件重叠的问题有些棘手。即使没有直接的兄弟部件叠加在该部件之上,也总是
可能
有非直系继承关系(比如”堂兄妹”或者”姑婶”)的部件会与它交叠。这样的情况下,处于一个复杂层次中的每个部件的重画工作都需要一大堆的树遍历来确保’正确地’绘画。为了减少不必要的遍历,Swing为
javax.swing.JComponent
增加一个只读的
isOptimizedDrawingEnabled
属性:
public boolean isOptimizedDrawingEnabled()
这些设置是:
-
true
:部件指示没有直接的子孙与其重叠。
-
false
: 部件不保证有没有直接的子孙与之交叠。
通过检查
isOptimizedDrawingEnabled
属性,Swing在重画时可以快速减少对交叠部件的搜索。
因为
isOptimizedDrawingEnabled
属性是只读的,于是部件改变默认值的唯一方法是在其子类覆盖(override)这个方法来返回所期望的值。除了
JLayeredPane,JDesktopPane
,和
JViewPort
外,所有标准Swing部件对这个属性返回
true
。
绘画方法
适应于AWT的轻量级部件的规则同样也适用于Swing部件 — 举一个例子,在部件需要呈现的时候就会调用
paint()
— 只是Swing更进一步地把
paint()
的调用分解为3个分立的方法,以下列顺序依次执行:
protected void paintComponent(Graphics g)
protected void paintBorder(Graphics g)
protected void paintChildren(Graphics g)
Swing程序应该覆盖
paintComponent()
而不是
覆盖
paint()
。虽然API允许这样做,但通常没有理由去覆盖
paintBorder()
或者
paintComponents()
(如果你这么做了,请确认你知道你到底在做什么!)。这个分解使得编程变得更容易,程序可以只覆盖它们需要扩展的一部分绘画。例如,这样就解决先前在AWT中提到的问题,因为调用
super.paint()
失败而使得所有轻量级子孙都不能显示。
SwingPaintDemo
例子程序举例说明了Swing的
paintComponent()
回调方法的简单应用。
绘画与UI代理
大多数标准Swing部件拥有它们自己的、由分离的观-感(look-and-feel)对象(叫做”UI代理”)实现的观-感。这意味着标准部件把大多数或者所有的绘画委派给UI代理,并且出现在下面的途径:
-
paint()
触发
paintComponent()
方法。 -
如果
ui
属性为non-null,
paintComponent()
触发
ui.update()。
-
如果部件的
遮光
属性为true,
ui.udpate()
方法使用背景颜色填充部件的背景并且触发
ui.paint()
。 -
ui.paint()
呈现部件的内容。
这意味着Swing部件的拥有UI代理的子类(相对于
JComponent
的直系子类),应该在它们所覆盖的
paintComponent
方法中触发
super.paintComponent()
。
|
如果因为某些原因部件
的扩展类不允许UI代理去执行绘画(是如果,例如,完全更换了部件的外观),它可以忽略对
super.paintComponent()
的调用,但是它必须负责填充自己的背景,如果
遮光(opaque)
属性为
true
的话,如前面在
遮光(opaque)
属性一章讲述的。
绘画的处理过程
Swing处理”repaint”请求的方式与AWT有稍微地不同,虽然对于应用开发人员来讲其本质是相同的 — 同样是触发
paint()
。Swing这么做是为了支持它的
RepaintManager
API (后面介绍),就象改善绘画性能一样。在Swing里的绘画可以走两条路,如下所述:
(A) 绘画需求产生于第一个重量级祖先(通常是
JFrame、JDialog、JWindow
或者
JApplet
):
-
事件分派线程调用其祖先的
paint()
-
Container.paint()
的默认实现会递归地调用任何轻量级子孙的
paint()
方法。 -
当到达
第一个
Swing部件时,
JComponent.paint()
的默认执行做下面的步骤:-
如果部件的
双缓冲
属性为
true
并且部件的
RepaintManager
上的双缓冲已经激活,将把
Graphics
对象转换为一个合适的屏外
Graphics
。 -
调用
paintComponent()
(如果使用双缓冲就把屏外Graphics传递进去)。 -
调用
paintChildren()
(如果使用双缓冲就把屏外Graphics传递进去),该方法使用裁剪并且
遮光
和
optimizedDrawingEnabled
等属性来严密地判定要递归地调用哪些子孙的
paint()
。 -
如果部件的
双缓冲
属性为
true
并且在部件的
RepaintManager
上的双缓冲已经激活,使用最初的屏幕
Graphics
对象把屏外映像拷贝到部件上。
注意:
JComponent.paint()
步骤#1和#5在对
paint()
的
递归
调用中被忽略了(由于
paintChildren()
,在步骤#4中介绍了),因为所有在swing窗体层次中的轻量级部件将共享同一个用于双缓冲的屏外映像。 -
如果部件的
(B) 绘画需求从一个
javax.swing.JComponent
扩展类的
repaint()
调用上产生:
-
JComponent.repaint()
注册一个针对部件的
RepaintManager
的异步的重画需求,该操作使用
invokeLater()
把一个
Runnable
加入事件队列以便稍后执行在事件分派线程上的需求。 -
该Runnable在事件分派线程上执行并且导致部件的
RepaintManager
调用该部件上
paintImmediately()
,该方法执行下列步骤:-
使用裁剪框以及
遮光
和
optimizedDrawingEnabled
属性确定“根”部件,绘画一定从这个部件开始(处理透明以及潜在的重叠部件)。 -
如果根部件的
双缓冲
属性为
true
,并且根部件的
RepaintManager
上的双缓冲已激活,将转换
Graphics
对象到适当的屏外
Graphics
。 -
调用根部件(该部件执行上述(A)中的
JComponent.paint()
步骤#2-4)上的
paint()
,导致根部件之下的、与裁剪框相交的所有部件被绘制。 -
如果根部件的
doubleBuffered
属性为
true
并且根部件的
RepaintManager
上的双缓冲已经激活,使用原始的
Graphics
把屏外映像拷贝到部件。
注意
:如果在重画没有完成之前,又有发生多起对部件或者任何一个其祖先的
repaint()
调用,所有这些调用会被折叠到一个单一的调用 回到
paintImmediately()
on
topmost
Swing部件 on which 其
repaint()
被调用。例如,如果一个
JTabbedPane
包含了一个
JTable
并且在其包容层次中的现有的重画需求完成之前两次发布对
repaint()
的调用,其结果将变成对该
JTabbedPane
部件的
paintImmediately()
方法的单一调用,会触发两个部件的
paint()
的执行。 -
使用裁剪框以及
这意味着对于Swing部件来说,
update()
不再被调用。
虽然
repaint()
方法导致了对
paintImmediately()
的调用,它不考虑”回调”绘图,并且客户端的绘画代码也
不会
放置到
paintImmediately()
方法里面。实际上,除非有特殊的原因,根本不需要超载
paintImmediately()
方法。
同步绘图
象我们在前面章节所讲述的,
paintImmediately()
表现为一个入口,用来通知Swing部件绘制自身,确认所有需要的绘画都能适当地产生。这个方法也可能用来安排同步的绘图需求,就象它的名字所暗示的,即一些部件有时候需要保证它们的外观实时地与其内部状态保持一致(例如,在
JScrollPane
执行滚定操作的时候确实需要这样并且也做到了)。
程序不应该直接调用这个方法,除非有合理实时绘画需要。这是因为异步的
repaint()
可以使多个重复的需求得到有效的精简,反之直接调用
paintImmediately()
则做不到这点。另外,调用这个方法的规则是它
必须由事件分派线程中的进程调用
;它也不是为能以多线程运行你的绘画代码而设计的。关于Swing单线程模式的更多信息,参考一起归档的文章
“Threads and Swing.”
RepaintManager
Swing的
RepaintManager
类的目的是最大化地提高Swing包容层次上的重画执行效率,同时也实现了Swing的’重新生效’机制(作为一个题目,将在其它文章里介绍)。它通过截取所有Swing部件的重画需求(于是它们不再需要经由AWT处理)实现了重画机制,并且在需要更新的情况下维护其自身的状态(我们已经知道的”dirty regions”)。最后,它使用
invokeLater()
去处理事件分派线程中的未决需求,如同在”Repaint Processing”一节中描述的那样(B选项).
对于大多数程序来讲,
RepaintManager
可以看做是Swing的内部系统的一部分,并且甚至可以被忽略。然而,它的API为程序能更出色地控制绘画中的几个要素提供了选择。
“当前的”RepaintManager
RepaintManager
设计 is designed to be dynamically plugged, 虽然 有一个单独的接口。下面的静态方法允许程序得到并且设置”当前的”
RepaintManager
:
public static RepaintManager currentManager(Component c)
public static RepaintManager currentManager(JComponent c)
public static void
setCurrentManager(RepaintManager aRepaintManager)
更换”当前的”RepaintManager
总的说来,程序通过下面的步骤可能会扩展并且更换
RepaintManager
:
RepaintManager.setCurrentManager(new MyRepaintManager());
你也可以参考
RepaintManagerDemo
,这是个简单的举例说明
RepaintManager
加载的例子,该例子将把有关正在执行重画的部件的信息打印出来。
扩展和替换
RepaintManager
的一个更有趣的动机是可以改变对重画的处理方式。当前,默认的重画实现所使用的来跟踪dirty regions的内部状态值是包内私有的并且因此不能被继承类访问。然而,程序可以实现它们自己的跟踪dirty regions的机制并且通过超载下面的方法对重画需求的缩减:
public synchronized void
addDirtyRegion(JComponent c, int x, int y, int w, int h)
public Rectangle getDirtyRegion(JComponent aComponent)
public void markCompletelyDirty(JComponent aComponent)
public void markCompletelyClean(JComponent aComponent) {
addDirtyRegion()
方法是在调用Swing部件的
repaint()
的之后被调用的,因此可以用作钩子来捕获所有的重画需求。如果程序超载了这个方法(并且不调用
super.addDirtyRegion()
),那么它改变了它的职责,而使用
invokeLater()
把
Runnable
放置到
EventQueue
,该队列将在合适的部件上调用
paintImmediately()
(translation: not for the faint of heart).
从全局控制双缓冲
RepaintManager
提供了从全局中激活或者禁止双缓冲的API:
public void setDoubleBufferingEnabled(boolean aFlag)
public boolean isDoubleBufferingEnabled()
这个属性在绘画处理的时候,在
JComponent
的内部检查过以确定是否使用屏外缓冲显示部件。这个属性默认为
true
,但是如果程序希望在全局范围为所有Swing部件关闭双缓冲的使用,可以按照下面的步骤做:
RepaintManager.currentManager(mycomponent).
setDoubleBufferingEnabled(false);
注意:因为Swing的默认实现要初始化一个单独的
RepaintManager
实例,
mycomponent
参数与此不相关。
Swing绘画准则
Swing开发人员在写绘画代码时应该理解下面的准则:
-
对于Swing部件,不管是系统-触发还是程序-触发的请求,总会调用
paint()
方法;而
update()
不再被Swing部件调用。 -
程序可以通过
repaint()
触发一个异步的
paint()
调用,但是不能直接调用
paint()
。 -
对于复杂的界面,应该调用带参数的
repaint()
,这样可以仅仅更新由该参数定义的区域;而不要调用无参数的
repaint()
,导致整个部件重画。 -
Swing中实现
paint()
的3个要素是调用3个分离的回调方法:-
paintComponent()
-
paintBorder()
-
paintChildren()
Swing部件的子类,如果想执行自己的绘画代码,应该把自己的绘画代码放在
paintComponent()
方法的范围之内。(
不要
放在
paint()
里面)。 -
-
Swing引进了两个属性来最大化的改善绘画的性能:
-
opaque
: 部件是否要重画它所占据范围中的所有像素位? -
optimizedDrawingEnabled
: 是否有这个部件的子孙与之交叠?
-
-
如故Swing部件的
(遮光)opaque
属性设置为
true
,那就表示它要负责绘制它所占据的范围内的所有像素位(包括在
paintComponent()
中清除它自己的背景),否则会造成屏幕垃圾。 -
把一个部件设置为
遮光(opaque)
同时又把它的
optimizedDrawingEnabled
属性设置为
false
,将导致在每个绘画操作中要执行更多的处理,因此我们推荐的明智的方法是同时使用透明并且交叠部件。 -
使用UI代理(包括
JPanel
)的Swing部件的扩展类的典型作法是在它们自己的
paintComponent()
的实现中调用
super.paintComponent()
。因为UI代理可以负责清除一个遮光部件的背景,这将照顾到#5. -
Swing通过
JComponent
的
doubleBuffered
属性支持内置的双缓冲,所有的Swing部件该属性默认值是
true
,然而把Swing容器的遮光设置为
true
有一个整体的构思,把该容器上的所有轻量级子孙的属性打开,不管它们各自的设定。 -
强烈建议为所有的Swing部件使用双缓冲。
-
界面复杂的部件应该灵活地运用剪切框来,只对那些与剪切框相交的区域进行绘画操作,从而减少工作量。
总结
不管AWT还是Swing都提供了方便的编程手段使得部件内容能够正确地显示到屏幕上。虽然对于大多数的GUI需要我们推荐使用Swing,但是理解AWT的绘画机制也会给我们带来帮助,因为Swing建立在它的基础上。
关于AWT和Sing的特点就介绍到这里,应用开发人员应该尽力按照本文中介绍的准则来撰写代码,充分发挥这些API功能,使自己的程序获得最佳性能。
Copyright 1994-2006 Sun Microsystems, Inc. |
|