浅谈Swing绘画的处理过程

开发 后端
本文介绍Swing绘画的处理过程,以及介绍Swing开发人员在写绘画代码时应该理解的准则。

AWT和Swing绘画
在AWT中,对于重量级组件,在绘制时按照如下的调用进行:

1)因为系统触发而重绘:(说白了,就是指这种重绘不是人为的,不是我们自己写代码调用repaint()等函数进行重绘,而是系统觉得有必要进行重绘而进行的。)
◆《AWT》确定是一部分还是整个部件需要绘画。
◆《AWT》促使事件分派线程调用部件的paint()方法。

2)因为程序触发而重绘:(人为在消息响应函数中或其他地方强制进行重绘的操作)
◆《程序》确定是一部分还是全部部件需要重画以对应内部状态的改变。
◆《程序》调用部件的repaint(),该方法向《AWT》登记了一个异步的请求 -- 当前部件需要重画。
◆《AWT》促使事件分派线程去调用部件的update() 方法。
◆如果部件没有覆盖(override)update()方法,update()的默认实现会清除部件背景(如果部件不是“轻量级”),然后只是简单地调用paint()方法。

说明:不论是那种触发重绘的方式,均可以归结到paint()函数上来,那为什么对于程序触发方式还要有个中间步骤“update()”呢?这是为了让我们能够通过重写update()方法后,在里面进行我们想要的控制,也就是我们可以在这里做点文章。当然我们也可以覆盖paint()函数,但是有了 update()函数之后,我们就可以不干扰paint(),让其“全身心”的负责绘制,而在update()这个地方进行我们需要的控制。比如提到的只能用到重量级组件的“增量绘制”,就是首先由系统触发paint绘制,然后在这个基础上(也就是背景下),在鼠标左键的消息响应函数中调用 repaint,然后重写update函数,只是让update函数画新添加的内容,而不在update函数内部再调用paint函数了,这样就避开了 paint函数,也就是实现了所谓的“增量绘制”。不过需要说明的是,增量绘制只在一些特殊的GUI部件上好用,比如我们下面给的这个例子(就是刚用来描述“增量绘制”的那个例子)中就是用的Canvas类,该类直接继承于Component类,注意不是继承自Container类,因为在 Container类中又实现了自己的paint方法,有了新的机制,这就和我们上述讲的这一大套基于Component类的paint方法不一致了。

对于轻量级组件,都是继承于Container类的,“轻量级”部件需要一个处在容器体系上的“重量级”部件提供进行绘画的场所。当这个“重量级” 的“祖宗”被告知要绘制自身的窗体时,它必须把这个绘画的请求转化为对其所有子孙的绘画请求。这是由java.awt.Container的 paint()方法处理的,该方法调用包容于其内的所有可见的、并且与绘画区相交的轻量级部件的paint()方法。因此对于所有覆盖了paint()方法的Container子类(“轻量级”或“重量级”,Container的子类不一定都是轻量级组件哦,呵呵)都需要在函数的最后调用父类的paint 方法,即super.paint(g)。

最后,对于AWT绘制,给出以下准则:
◆对于大多数程序,所有的客户区绘画代码应该被放置在部件的paint()方法中。
◆通过调用repaint()方法,程序可以触发一个将来执行的paint()调用,不能直接调用paint()方法。
◆对于界面复杂的部件,应该触发带参数的repaint()方法,使用参数定义实际需要更新的区域;而不带参数调用会导致整个部件被重画。
◆因为对repaint()的调用会首先导致update()的调用,默认地会促成paint()的调用,所以重量级部件应该覆盖update()方法以实现增量绘制,如果需要的话(轻量级部件不支持增量绘制) 。
◆覆盖了paint()方法的java.awt.Container子类应当在paint()方法中调用super.paint()以保证子部件能被绘制。
◆界面复杂的部件应该灵活地使用裁剪区来把绘画范围缩小到只包括与裁剪区相交的范围。

Swing绘画的处理过程
Swing处理"repaint"请求的方式与AWT有稍微地不同,虽然对于应用开发人员来讲其本质是相同的 -- 同样是触发paint()。Swing这么做是为了支持它的RepaintManager API (后面介绍),就象改善绘画性能一样。在Swing里的绘画可以走两条路,如下所述:

(A) 绘画需求首先产生于一个重量级祖先(通常是JFrame、JDialog、JWindow或者JApplet):
1。事件分派线程调用其祖先的paint()
2。Container.paint()的默认实现会递归地调用任何轻量级子孙的paint()方法。
3。当到达第一个Swing部件时,JComponent.paint()的默认执行做下面的步骤:
◆如果部件的双缓冲属性为true并且部件的RepaintManager上的双缓冲已经激活,将把Graphics对象转换为一个合适的屏外Graphics。
◆调用paintComponent()(如果使用双缓冲就把屏外Graphics传递进去)。
◆调用paintBorder()(如果使用双缓冲就把屏外Graphics传递进去)。
◆调用paintChildren()(如果使用双缓冲就把屏外Graphics传递进去),该方法使用裁剪并且遮光和optimizedDrawingEnabled等属性来严密地判定要递归地调用哪些子孙的paint()。
◆如果部件的双缓冲属性为true并且在部件的RepaintManager上的双缓冲已经激活,使用最初的屏幕Graphics对象把屏外映像拷贝到部件上。

注意:JComponent.paint()步骤#1和#5在对paint()的递归调用中被忽略了(这里的JComponent指的是在paintChildren()函数中判断出的需要递归调用的组件,在步骤#4中介绍了),因为所有在swing窗体层次中的轻量级部件将共享同一个用于双缓冲的屏外映像。

(B) 绘画需求从一个javax.swing.JCponent扩展类的repaint()调用上产生:
1。JComponent.repaint()注册一个针对部件的RepaintManager的异步的重画需求,该操作使用invokeLater()把一个Runnable加入事件队列以便稍后执行在事件分派线程上的需求。
 
2。该Runnable在事件分派线程上执行并且导致部件的RepaintManager调用该部件上paintImmediately(),该方法执行下列步骤:

◆使用裁剪框以及遮光和optimizedDrawingEnabled属性确定“根”部件,绘画一定从这个部件开始(处理透明以及潜在的重迭部件)。
◆如果根部件的双缓冲属性为true,并且根部件的RepaintManager上的双缓冲已激活,将转换Graphics对象到适当的屏外Graphics。
◆调用根部件(该部件执行上述(A)中的JComponent.paint()步骤#2-4)上的paint(),导致根部件之下的、与裁剪框相交的所有部件被绘制。
◆如果根部件的doubleBuffered属性为true并且根部件的RepaintManager上的双缓冲已经激活,使用原始的Graphics把屏外映像拷贝到部件。

注意:如果在重画没有完成之前,又有发生多起对部件或者任何一个其祖先的repaint()调用,所有这些调用会被折迭到一个单一的调用,即回到最上层(这里的层指的是那种Hierarchy,而不是展现给我们的最上面的那个图或者按钮)的SWing部件的paintImmediately(),调用它的repaint()。例如,如果一个JTabbedPane包含了一个JTable并且在其包容层次中的现有的重画需求完成之前两次发布对repaint()的调用,其结果将变成对该JTabbedPane部件的paintImmediately()方法的单一调用,会触发两个部件的paint()的执行。
 
这意味着对于Swing部件来说,update()不再被调用。

虽然repaint()方法导致了对paintImmediately()的调用,它不考虑"回调"绘图,并且客户端的绘画代码也不会放置到 paintImmediately()方法里面。实际上,除非有特殊的原因,根本不需要超载paintImmediately()方法。

Swing绘画准则
Swing开发人员在写绘画代码时应该理解下面的准则:
1。对于Swing部件,不管是系统-触发还是程序-触发的请求,总会调用paint()方法;而update()不再被Swing部件调用。
2。程序可以通过repaint()触发一个异步的paint()调用,但是不能直接调用paint()。
3。对于复杂的界面,应该调用带参数的repaint(),这样可以仅仅更新由该参数定义的区域;而不要调用无参数的repaint(),导致整个部件重画。
4。Swing中实现paint()的3个要素是调用3个分离的回调方法:
◆paintComponent()
◆paintBorder()
◆paintChildren()
Swing部件的子类,如果想执行自己的绘画代码,应该把自己的绘画代码放在paintComponent()方法的范围之内。(不要放在paint()里面)。
5。Swing引进了两个属性来最大化的改善绘画的性能:
◆opaque: 部件是否要重画它所占据范围中的所有像素位?
◆optimizedDrawingEnabled: 是否有这个部件的子孙与之交迭?
6。如果Swing部件的(遮光)opaque属性设置为true,那就表示它要负责绘制它所占据的范围内的所有像素位(包括在paintComponent()中清除它自己的背景),否则会造成屏幕垃圾。
7。如果一个部件的遮光性(opaque)和optimizedDrawingEnabled属性有一个被设置为false,将导致在每个绘画操作中要执行更多的处理,因此我们推荐的明智的方法是同时使用透明并且交迭部件。
8。使用UI代理(包括JPanel)的Swing部件的扩展类的典型作法是在它们自己的paintComponent()的实现中调用super.paintComponent()。因为UI代理可以负责清除一个遮光部件的背景,不过这一操作需要根据规则#5中的设定来决定。
9。Swing通过JComponent的doubleBuffered属性支持内置的双缓冲,所有的Swing部件该属性默认值是true,然而把Swing容器的遮光设置为true有一个整体的构思,把该容器上的所有轻量级子孙的属性打开,不管它们各自的设定。
10。强烈建议为所有的Swing部件使用双缓冲。
11。界面复杂的部件应该灵活地运用剪切框来,只对那些与剪切框相交的区域进行绘画操作,从而减少工作量。

【编辑推荐】

  1. 浅析Swing线程包括内容
  2. AWT或Swing混合环境中的事务处理
  3. Swing性能和Swing程序的学习
  4. 比较Swing和SWT的速度
  5. 浅谈Swing构造JList
责任编辑:佚名 来源: 机械工业出版社
相关推荐

2009-09-24 17:11:53

Hibernate处理

2011-02-21 13:26:47

Postfix邮件处理

2011-04-11 16:42:05

Oracle无法启动

2010-06-09 18:17:20

Postfix邮件

2010-06-02 18:00:05

Postfix邮件

2009-07-20 17:49:07

JSF请求处理

2013-06-20 10:17:34

Android应用

2009-07-16 16:01:55

EventQueue

2018-05-30 09:47:02

2009-07-15 14:29:24

构造JListSwing

2009-07-15 13:06:38

Swing组件

2009-07-14 18:28:58

Swing入门

2019-08-19 11:07:41

SQL数据库优化

2011-09-02 14:09:47

OracleDML命令

2009-07-15 09:59:11

Metal观感Swing

2009-07-17 12:44:01

NetBeans开发S

2009-07-16 12:58:50

Swing控件

2011-04-13 15:50:49

.htmHTTP请求处理

2021-02-01 09:00:34

Ceph octopu集群运维

2009-07-24 10:57:41

ASP.NET ISAIIS6
点赞
收藏

51CTO技术栈公众号