Chinaunix首页 | 论坛 | 博客
  • 博客访问: 182903
  • 博文数量: 21
  • 博客积分: 1395
  • 博客等级: 中尉
  • 技术积分: 321
  • 用 户 组: 普通用户
  • 注册时间: 2005-04-07 13:57
文章分类

全部博文(21)

文章存档

2012年(1)

2011年(4)

2010年(12)

2009年(2)

2008年(2)

我的朋友

分类: Java

2010-12-31 09:52:29

然后继续< JAVA Painting-Swing实现纪要一>by netbaixc。
纪要一已经分析了swing paint机制的一个大概的轮廓。这里将主要分析RepatinManager是如何维护绘制请求,又如何执行绘制的。
Componet类提供了几个重载repaint,
public void repaint();
public void repaint(long tm);
public void repaint(int x, int y, int width, int height);
JComponent类提供了一个重载repaint
public void repaint(Rectangle r);
这些repaint都将补充一些参数后去调用
Class JComponent.public void repaint(long tm, int x, int y, int width, int height){
RepaintManager.currentManager(this).addDirtyRegion(this, x, y, width, height);}
Class Component. public void repaint(long tm, int x, int y, int width, int height){
}
对于轻量级swing组件自然就是JComponent的repaint方法,对于顶层容器本身已经提供一个重载的public void repaint(long time, int x, int y, int width, int height)。
总之,对于swing的组件,除了纪要一分析的底层expose双缓冲flip的情况外,都是通过RepaintManager. addDirtyRegion来请求绘制。
/**
* Add a component in the list of components that should be refreshed.
* If c already has a dirty region, the rectangle (x,y,w,h)
* will be unioned with the region that should be redrawn.
*
* @see JComponent#repaint
*/
//addDirtyRegion调用该方法
private void addDirtyRegion0(Container c, int x, int y, int w, int h) {
/* Special cases we don't have to bother with.
*/
if ((w <= 0) || (h <= 0) || (c == null)) {//以防这种没有意义的请求
return;
}

if ((c.getWidth() <= 0) || (c.getHeight() <= 0)) {//以防这种没有意义的请求
return;
}

if (extendDirtyRegion(c, x, y, w, h)){ //在接受这次请求的时候,可能还有一些请求没有被调度处理,
//要考虑过去的请求中是否有同组件的请求,如果有,直接合并
//即可,合并成功即可返回。
// Component was already marked as dirty, region has been
// extended, no need to continue.
return;
}
//看来没有合并成功,则需要将此请求作为新请求处理

/* Make sure that c and all it ancestors (up to an Applet or
* Window) are visible. This loop has the same effect as
* checking c.isShowing() (and note that it's still possible
* that c is completely obscured by an opaque ancestor in
* the specified rectangle).
*/
Component root = null;

// Note: We can't synchronize around this, Frame.getExtendedState
// is synchronized so that if we were to synchronize around this
// it could lead to the possibility of getting locks out
// of order and deadlocking.
for (Container p = c; p != null; p = p.getParent()) {
if (!p.isVisible() || (p.getPeer() == null)) {
return;
}//首先校验整个层次是visible的,否则是没意义的请求。-这里我怀疑是一个逻辑BUG,就是前面合并请求的//时候为什么不去判断呢?如果visible被置为了false,那么当前这个请求就是应该完全抛弃的,如果合并进去可能就扩展//要重画的脏区,不合逻辑了。
if ((p instanceof Window) || (p instanceof Applet)) {
// Iconified frames are still visible!
if (p instanceof Frame &&
(((Frame)p).getExtendedState() & Frame.ICONIFIED) ==
Frame.ICONIFIED) {
return;//检查顶层容器是否处于最小化状态,如果是最小化状态,也是无意义的请求。
}
root = p;
break;
}
}

if (root == null) return;//搞到最后,顶层容器竟然是Null,不用搞了也

synchronized(this) {//因为前面没有同步(因害怕死锁考虑),所以在这里小同步下时考察合并,实在不能合并,注册到dirtyComponents-{组件->几何区域}
if (extendDirtyRegion(c, x, y, w, h)) {
// In between last check and this check another thread
// queued up runnable, can bail here.
return;
}
dirtyComponents.put(c, new Rectangle(x, y, w, h));
}

// Queue a Runnable to invoke paintDirtyRegions and
// validateInvalidComponents.
scheduleProcessingRunnable();//post 一个InvocationEvent请求EDT执行绘制过程。
}
/**
* Extends the dirty region for the specified component to include
* the new region.
*
* @return false if c is not yet marked dirty.
*/
private synchronized boolean extendDirtyRegion(
Component c, int x, int y, int w, int h) {
Rectangle r = (Rectangle)dirtyComponents.get(c);//历史的请求
if (r != null) {
// A non-null r implies c is already marked as dirty,
// and that the parent is valid. Therefore we can
// just union the rect and bail.
SwingUtilities.computeUnion(x, y, w, h, r);//合并区域(就是一个矩形求合集的处理,略了)
return true;
}
return false;
}
private void scheduleProcessingRunnable(AppContext context) {
if (processingRunnable.markPending()) {//如果这个processingRunnable单例已经挂在EVENTQUEUE中还没处理,这次就不必再post了。
SunToolkit.getSystemEventQueueImplPP(context).
postEvent(new InvocationEvent(Toolkit.getDefaultToolkit(),
processingRunnable));
}
}
当EDT获取到那个InvocationEvent时将执行processingRunnable这个单例。
private final class ProcessingRunnable implements Runnable {
// If true, we're wainting on the EventQueue.
private boolean pending;

/**
* Marks this processing runnable as pending. If this was not
* already marked as pending, true is returned.
*/
public synchronized boolean markPending() {//mark
if (!pending) {
pending = true;
return true;
}
return false;
}

public void run() {
synchronized (this) {
pending = false;
}
// First pass, flush any heavy paint events into real paint
// events. If there are pending heavy weight requests this will
// result in q'ing this request up one more time. As
// long as no other requests come in between now and the time
// the second one is processed nothing will happen. This is not
// ideal, but the logic needed to suppress the second request is
// more headache than it's worth.
scheduleHeavyWeightPaints();//因为nativeAddDirtyRegion是在AWT-Windows提交,而我们的//repaint应该在EDT中提交,两个线程同步运行,如果共享dirtyComponents,将有线程同步的考虑,(即一边EDT在处理//并清除注册的请求,另一边AWT在不断提交新请求),因此采用两个map记录,在这里进行一次合并。注释里说的看不懂
// Do the actual validation and painting.
validateInvalidComponents();//这涉及到validate,以后详细分析
prePaintDirtyRegions();//进行绘制操作
}
}
void scheduleHeavyWeightPaints() {//将顶层容器的请求加入到dirtyComponents中
Map hws;

synchronized(this) {
if (hwDirtyComponents.size() == 0){//hwDirtyComponents由下面nativeAddDirtyRegion加入
return;
}
hws = hwDirtyComponents;
hwDirtyComponents = new IdentityHashMap();
}
for (Container hw : hws.keySet()) {
Rectangle dirty = hws.get(hw);
if (hw instanceof Window) {
addDirtyRegion((Window)hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
else if (hw instanceof Applet) {
addDirtyRegion((Applet)hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
else { // SwingHeavyWeight
addDirtyRegion0(hw, dirty.x, dirty.y,
dirty.width, dirty.height);
}
}
}

void nativeAddDirtyRegion(AppContext appContext, Container c,
int x, int y, int w, int h) {//底层处理expose时如果没有双缓冲的处理的调用
if (w > 0 && h > 0) {
synchronized(this) {//就是尽量合并并放在hwDirtyComponents中
Rectangle dirty = hwDirtyComponents.get(c);
if (dirty == null) {
hwDirtyComponents.put(c, new Rectangle(x, y, w, h));
}
else {
hwDirtyComponents.put(c, SwingUtilities.computeUnion(
x, y, w, h, dirty));
}
}
scheduleProcessingRunnable(appContext);
}
}
/**
* This is invoked to process paint requests. It's needed
* for backward compatability in so far as RepaintManager would previously
* not see paint requests for top levels, so, we have to make sure
* a subclass correctly paints any dirty top levels.
*/
private void prePaintDirtyRegions() {
Map dirtyComponents;
java.util.List runnableList;
synchronized(this) {
dirtyComponents = this.dirtyComponents;
runnableList = this.runnableList;
this.runnableList = null;
}
if (runnableList != null) {
for (Runnable runnable : runnableList) {//D3D有关,以后java3d时分析
runnable.run();
}
}
paintDirtyRegions();//
if (dirtyComponents.size() > 0) {//在jre1.7前都是实现自己的RepaintManager,paintDirtyRegions方法,可以不考虑顶层容器,而现在jre1.7要考虑了,因此这里为了兼容,补充了新方法处理

// This'll only happen if a subclass isn't correctly dealing
// with toplevels.
paintDirtyRegions(dirtyComponents);
}
}
/**
* Paint all of the components that have been marked dirty.
*
* @see #addDirtyRegion
*/
public void paintDirtyRegions() {
synchronized(this) { // swap for thread safety
Map tmp = tmpDirtyComponents;
tmpDirtyComponents = dirtyComponents;
dirtyComponents = tmp;
dirtyComponents.clear();
}
//上面跟前面说的顶层容器和一般组件的请求分开注册类似,为了线程同步安全但如果所有的repaint都是在EDT调用,这恐怕就没有意义了。
paintDirtyRegions(tmpDirtyComponents);
}
private void paintDirtyRegions(Map
tmpDirtyComponents){
int i, count;
java.util.List roots;
Component dirtyComponent;

count = tmpDirtyComponents.size();
if (count == 0) {
return;
}//如果没有注册的脏区{组件->几何区域},返回

Rectangle rect;
int localBoundsX = 0;
int localBoundsY = 0;
int localBoundsH = 0;
int localBoundsW = 0;
Enumeration keys;

roots = new ArrayList(count);


for (Component dirty : tmpDirtyComponents.keySet()) {
collectDirtyComponents(tmpDirtyComponents, dirty, roots);//此时的tmpDirtyComponents
//中可能存在脏的祖宗组件,
//将把几何区域合并到祖宗组件//中--roots
}


count = roots.size();//从合并后的脏祖宗组件画起
// System.out.println("roots size is " + count);
painting = true;
try {
for(i=0 ; i < count ; i++) {
dirtyComponent = roots.get(i);
rect = tmpDirtyComponents.get(dirtyComponent);
// System.out.println("Should refresh :" + rect);
localBoundsH = dirtyComponent.getHeight();
localBoundsW = dirtyComponent.getWidth();

SwingUtilities.computeIntersection(localBoundsX,
localBoundsY,
localBoundsW,
localBoundsH,
rect);//确定脏区在该组件的bound范围,其实在合并中已经确定过一次了,这里又一次确定,难道不嫌啰嗦么
if (dirtyComponent instanceof JComponent) {
((JComponent)dirtyComponent).paintImmediately(
rect.x,rect.y,rect.width, rect.height);//轻量级组件在这里开始画这个区域
}
else if (dirtyComponent.isShowing()) {
Graphics g = JComponent.safelyGetGraphics(
dirtyComponent, dirtyComponent);
// If the Graphics goes away, it means someone disposed of
// the window, don't do anything.
if (g != null) {
g.setClip(rect.x, rect.y, rect.width, rect.height);
try {
dirtyComponent.paint(g);//顶层容器的paint
} finally {
g.dispose();
}
}
}
// If the repaintRoot has been set, service it now and
// remove any components that are children of repaintRoot.
if (repaintRoot != null) {//每窗口缓存策略下的支持
adjustRoots(repaintRoot, roots, i + 1);
count = roots.size();
paintManager.isRepaintingRoot = true;
repaintRoot.paintImmediately(0, 0, repaintRoot.getWidth(),
repaintRoot.getHeight());
paintManager.isRepaintingRoot = false;
// Only service repaintRoot once.
repaintRoot = null;
}
}
} finally {
painting = false;
}
tmpDirtyComponents.clear();//清理dirtyComponent
}
上述的代码已经到了具体swing组件的paintImmediately中,此时提供的几个参数分别是该组件要绘制的区域相对本组件的x,y和该区域的width,height。
public void paintImmediately(int x,int y,int w, int h) {
Component c = this;
Component parent;

if(!isShowing()) {//检查一下当前是否显示有效:首先这种检查只是根据contain tree来检查,对于不在一个tree的组件如果完全遮挡则检查不到;另外对于repaint引起的此次调用(非直接调用),这里有个意义是因为绘制是异步的---提交绘制请求和后面的EDT处理该请求,两回都要校验,这里是处理请求执行的第2次校验---这说明应用提交请求后还允许反悔,只要在EDT开始处理请求前反悔都是成功的。
return;
}
while(!((JComponent)c).isOpaque()) {//opaque确定该组件是否完全不透明,如果不是则需要从祖宗画起,这样提交请求是脏A组件,但经过上面合并后可能并入已提交的脏B组件,而在这里可能要转而绘制B组件的祖宗C组件。
parent = c.getParent();
if(parent != null) {
x += c.getX();
y += c.getY();//处理坐标的迁移,使之相对父组件
c = parent;
} else {
break;
}

if(!(c instanceof JComponent)) {
break;
}
}
if(c instanceof JComponent) {
((JComponent)c) _paintImmediately (x,y,w,h);//向上追到一个opaque的轻量级父组件具体绘制(可找到该绘制的目标了)
} else {
c.repaint(x,y,w,h);//没有的话那就是顶层容器的绘制了。
}
}
void __paintImmediately(int x, int y, int w, int h) {
Graphics g;
Container c;
Rectangle b;

int tmpX, tmpY, tmpWidth, tmpHeight;
int offsetX=0,offsetY=0;

boolean hasBuffer = false;

JComponent bufferedComponent = null;
JComponent paintingComponent = this;

RepaintManager repaintManager = RepaintManager.currentManager(this);
// parent Container's up to Window or Applet. First container is
// the direct parent. Note that in testing it was faster to
// alloc a new Vector vs keeping a stack of them around, and gc
// seemed to have a minimal effect on this.
java.util.List path = new java.util.ArrayList(7);
int pIndex = -1;
int pCount = 0;

tmpX = tmpY = tmpWidth = tmpHeight = 0;//此后始终tmpX = tmpY==0

Rectangle paintImmediatelyClip = fetchRectangle();//从回收池里获取一个Rectangle
paintImmediatelyClip.x = x;
paintImmediatelyClip.y = y;
paintImmediatelyClip.width = w;
paintImmediatelyClip.height = h;//用来记录要绘制的区域


// System.out.println("1) ************* in _paintImmediately for " + this);

boolean ontop = alwaysOnTop() && isOpaque();//ontop说明了该组件一直在界面最上层不会被非子组件遮挡,如果==true,则下面那个大循环就做一件事情,找到该组件的顶层容器,此后就是从顶层容器拿到Graph2D开始绘制该组件。如果==false,则要考虑该组件有可能被非子组件(兄弟组件)遮挡的情况。
if (ontop) {
SwingUtilities.computeIntersection(0, 0, getWidth(), getHeight(),//我靠,这里又确定一次脏区是否超出组件当前范围
paintImmediatelyClip);
if (paintImmediatelyClip.width == 0) {//经交集判断后宽度为0(或高度为0)则意味着超出范围,回收Rectangle并返回。
recycleRectangle(paintImmediatelyClip);
return;
}
}
Component child;
for (c = this, child = null;
c != null && !(c instanceof Window) && !(c instanceof Applet);
child = c, c = c.getParent()) {
//ontop记录的始终是要绘制的组件的属性;循环将一直向上递推到顶层容器,因为要通过顶层容器拿到Graph2D;同时在这此循环中将要考虑兄弟组件相互遮挡的情况,可能因此重新设置要最终要绘制的组件,也就说这次循环实际遍历做了2件事情。
JComponent jc = (c instanceof JComponent) ? (JComponent)c :
null;//jc始终记录向上的轻量级父组件,如果某一级出现非轻量级父组件则为空,c则始终记录向上的组件,不管轻重。
path.add(c);//path记录向上递推的整个路径
if(!ontop && jc != null && !jc.isOptimizedDrawingEnabled()) {//如果不能确定那个发起绘制的组件一定是ontop,而且如果当前该组件是轻量级组件而且不能保证子组件不重叠,需要考虑那个要求绘制的子组件是否与兄弟组件有遮挡关系,从而确定不同的绘制路径
boolean resetPC;

// Children of c may overlap, three possible cases for the
// painting region:
// . Completely obscured by an opaque sibling, in which
// case there is no need to paint.
// . Partially obscured by a sibling: need to start
// painting from c.
// . Otherwise we aren't obscured and thus don't need to
// start painting from parent.
if (c != this) {//当前组件已经向上循环成要绘制的组件的老一辈,就是说当前已经循环过第一轮了。
if (jc.isPaintingOrigin()) {//如果该组件已经声明所有子组件的绘制请求都应该从本组件绘制起
resetPC = true;//则直接要求重新设置准备绘制的组件为当前组件
}
else {//如果该组件没有要求一定代理子组件的绘制,需要判断要求绘制的那个子组件是否有兄弟组件遮盖
Component[] children = c.getComponents();
int i = 0;
for (; i if (children[i] == child) break;//得到子组件的索引
}
switch (jc.getObscuredState(i,
paintImmediatelyClip.x,
paintImmediatelyClip.y,
paintImmediatelyClip.width,
paintImmediatelyClip.height)) {//检查前面加入的子组件是否将该子组件遮挡。这个地方绕了我一下!因为我有个错误认识,首先给容器加入子组件时childIndex是从0加起这是没错的,但我感觉是后面加入的总会覆盖前面的,所以这里应该判断后面加入的组件是否将该组件遮挡啊。但是实际上,容器绘制子组件时是从后面向前一直画到0,一测就明白了,是前面加入的后画,因此前面加入的子组件会覆盖后面加入的子组件。Awt为什么要这样倒序绘制?
case NOT_OBSCURED:
resetPC = false;//各不相干则不用考虑
break;
case COMPLETELY_OBSCURED://经判断那个子组件已经被某个前面加入的组件完全遮挡直接返回并忽略绘制请求,因为根据倒序绘制的顺序,现在屏幕上显示的是那个兄弟组件。
recycleRectangle(paintImmediatelyClip);
return;
default:
resetPC = true;//部分包含则要调整绘制组件,从父组件-当前组件画起,因为这样才能把那些涉嫌脏区的兄弟组件也给绘制出来。
break;
}
}
}
else {
resetPC = false;//仍然是当前组件,就是说第一轮循环,不需要下面reset绘制组件
}

if (resetPC) {
// Get rid of any buffer since we draw from here and
// we might draw something larger
paintingComponent = jc;//要绘制的组件转为当前组件
pIndex = pCount;//要绘制的组件相对目标组件的层次
offsetX = offsetY = 0;//通过清零是脏区在最终绘制前变成该当前组件的坐标系。
hasBuffer = false;//如果已经调整了绘制组件,则要重新初始化hasBuffer,因为此时该组件可能不支持双缓冲,而在此之前的循环中存在过bufferComponent父组件导致hasBuffer=true。因为要最终绘制组件时需要得到的参数bufferComponent应该是要绘制组件的父组件,而这里重新设置了要最终绘制的组件但还没对父组件进行遍历,所以需要重新初始化。
}
}
pCount++;//向上循环层数

// look to see if the parent (and therefor this component)
// is double buffered
//记录在向上的路径下最后一个支持双缓冲的父组件,而且只要最终要绘制的组件有支持双缓冲的祖宗组件就会使用双缓冲(hasBuffer==true)
if(repaintManager.isDoubleBufferingEnabled() && jc != null &&
jc.isDoubleBuffered()) {//如果当前组件获得双缓冲支持则需要置双缓冲标志
hasBuffer = true;
bufferedComponent = jc;
}

// if we aren't on top, include the parent's clip
if (!ontop) {//如果不能确定那个发起绘制的组件一定是ontop,需要重新调整脏区的坐标系
int bx = c.getX();
int by = c.getY();
tmpWidth = c.getWidth();
tmpHeight = c.getHeight();
SwingUtilities.computeIntersection(tmpX,tmpY,tmpWidth,tmpHeight,paintImmediatelyClip);
paintImmediatelyClip.x += bx;//使区域相对于当前组件的父组件
paintImmediatelyClip.y += by;
offsetX += bx;
offsetY += by;
}
}
//paintingComponent现在是要经上面循环调整过的要开始绘制的最终的那个组件,
// If the clip width or height is negative, don't bother painting
//此时再次校验是否有效绘制区域,否则忽略并回收区域,真谨慎,真不容易啊
if(c == null || c.getPeer() == null ||
paintImmediatelyClip.width <= 0 ||
paintImmediatelyClip.height <= 0) {
recycleRectangle(paintImmediatelyClip);
return;
}

paintingComponent.setFlag(IS_REPAINTING, true);
//调整paintImmediatelyClip,使其成为相对绘制组件的坐标系

paintImmediatelyClip.x -= offsetX;
paintImmediatelyClip.y -= offsetY;

// Notify the Components that are going to be painted of the
// child component to paint to.
if(paintingComponent != this) {//如果调整过最终绘制的组件,记录该组件的孩子路径,以后有用地
Component comp;
int i = pIndex;
for(; i > 0 ; i--) {
comp = path.get(i);
if(comp instanceof JComponent) {
((JComponent)comp).setPaintingChild(path.get(i-1));
}
}
}
//真正进行绘制
try {
g = safelyGetGraphics(paintingComponent, c);//通过顶层容器c得到paintingComponent的graph环境。
try {
if (hasBuffer) {//如果使用双缓冲,交给RepaintManager对进行绘制,要求考虑从bufferedComponent开始缓冲支持。
RepaintManager rm = RepaintManager.currentManager(
bufferedComponent);
rm.beginPaint();
try {
rm.paint(paintingComponent, bufferedComponent, g,
paintImmediatelyClip.x,
paintImmediatelyClip.y,
paintImmediatelyClip.width,
paintImmediatelyClip.height);
} finally {
rm.endPaint();
}
}
else {//否则直接对paintingComponent绘制。
g.setClip(paintImmediatelyClip.x,paintImmediatelyClip.y,
paintImmediatelyClip.width,paintImmediatelyClip.height);
paintingComponent.paint(g);
}
} finally {
g.dispose();
}
}
finally {
// Reset the painting child for the parent components.
if(paintingComponent != this) {//清空记录
Component comp;
int i = pIndex;
for(; i > 0 ; i--) {
comp = path.get(i);
if(comp instanceof JComponent) {
((JComponent)comp).setPaintingChild(null);
}
}
}//恢复标志
paintingComponent.setFlag(IS_REPAINTING, false);
}
recycleRectangle(paintImmediatelyClip);//回收Rectangle
}

上述的这段代码实际上最主要的精神在于,如果要绘制一个组件的某区域,swing需要知道该组件的这块区域是否被兄弟组件们给遮挡。有几个参数可以帮助swing快速判断,没有这几个优化参数的提醒,swing将通过向上遍历到顶层容器的大循环来确定所有可能的遮挡情况(可能该组件没有被兄弟组件遮挡,但其爷爷组件可能被二爷爷给遮挡,这也得要考虑)。注意,如果是多个顶层容器相互遮挡,那这个遍历是没有考虑的,所以可能因此导致没有必要的重画遮挡区。但是因为Graphic2D是从各自顶层容器获取,所以也不会出现穿透的效果。这个遍历无疑是耗费效率的,虽然它已经通过约定容器画子组件的顺序而尽量去减少遍历的节点,也通过多次检查可能的区域不合理来过滤,但是为提高性能还是应该注意如果要自行开发swing组件,要设置优化参数,同时GUI不应该无谓地增加组件层次。
阅读(791) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2011-01-03 15:10:22

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com