3.更新管理器的设计和实现
Draw2d SDK这样描述更新管理器:更新管理器要负责处理重绘图形元素并布局它们的任务。一个恰当的实现是批处理待做的工作并合并任何冗余的工作。更新管理器可以含有0或多个被嵌套的更新管理器。在所有的请求已经被批处理后可以执行一些优化。因为这个原因,一个更新管理器应该在完成自己的更新之前要对它所嵌套的更新管理器调用PerformUpdate()操作。在被嵌套的更新期间,可以增加新的请求。
对于Draw2d SDK关于更新管理器的描述,我没有完全理解,到现在也没有。但我认为对UpdateManager的具体实现DeferredUpdateManager还是有一定了解的,下面就谈谈我的认识。
UpdateManager定义了更新管理器的抽象实现,DeferredUpdateManager和SubordinateUpdateManager是它的两个具体实现。下面以DeferredUpdateManager为例探讨更新管理器的实现,它能异步更新受到影响的图形元素。SubordinateUpdateManager我也不太明白,如果有哪位朋友能够将它解释清楚,那将是一件很高兴的事情。
· 该类定义了如下的字段:
private boolean updating; //表明更新正在进行之中。
private GraphicsSource graphicsSource; //图形源
private IFigure root; //根图形元素。
private boolean updateQueued = false; //是否有更新被排队
private List invalidFigures = new ArrayList(); //无效的图形元素列表。
private Map dirtyRegions = new HashMap(); //脏区域图
private Rectangle damage; //受到破坏的区域
关于这些字段的含义,似乎没有什么可进一步解释,好像比较直接了当。
· 这个类中最重要的函数是performUpdate,该方法的逻辑过程如下:
public synchronized void performUpdate()
{
//如果更新管理器被处置了或者正在执行更新任务的话,就直接返回。
if (isDisposed() || updating)
return;
updating = true; //将正在更新标志设置为true。
try
{
validateFigures(); //使所有的图形元素有效
updateQueued = false; //表明没有更新正在排队
repairDamage(); //修补被毁坏的区域
}
finally
{
updating = false; //清除正在被更新标志。
}
}
performUpdate方法受到临界区保护,也就是说,当performUpdate方法被执行的过程中,该方法不可能有机会再被其它的线程调用。
validateFigures()方法的执行逻辑过程如下,它的任务是使更新队列中的无效的图形元素有效并调用fireValidating (),除非更新队列中没有无效的图形元素。
protected void validateFigures()
{
//如果无效的图形元素队列为空,那么就直接返回。
if (invalidFigures.isEmpty()) return;
try
{
IFigure fig;
fireValidating(); //通知对图形元素有效感兴趣的监听器
for (int i = 0; i < invalidFigures.size(); i++)
{
fig = (IFigure) invalidFigures.get(i);
invalidFigures.set(i, null);
fig.validate(); //使图形元素有效
}
}
finally
{
invalidFigures.clear(); //清空无效图形元素队列。
}
}
有一段时间,我对图形元素的有效和无效非常困惑,因为图形元素的Validate和Invalidate与平常意义上的控件的有效和无效很不一样。图形元素的有效和无效通常与重绘联系再一起。但draw2d中图形元素的有效与无效却不是这样的,下面对draw2d中的有效与无效等之类的概念进行一下解释;在draw2d中到处都是Invalidate(无效),Validate(有效),ReValidate(重新有效)等概念,这里对这些概念进行总结性描述。
1. 在AbstractLayout中的Invalidate()方法:告诉布局管理器抛弃它所缓存的关于它所负责布局的图形元素的所有缓存信息。只要拥有这种布局的图形元素被无效,这个方法就必须被调用。布局要负责计算拥有这个布局的图形元素的最小大小(MimSize)和合适的大小(PreferredSize),这些信息被计算一次后,就被缓存起来了,以后就使用这些被缓存的信息而不必重新计算它们。AbstractLayout中的Invalidate()方法实际上就是要抛弃这些信息。
2. 在Figure中的Invalidate()方法:使图形无效。这个无效过程要完成两个事情:1,调用该图形的布局管理器,使布局管理器无效。2,将图形的有效性标志设置为false。
3. 在Figure中的validate()方法:使图形元素的布局管理器布局该图形元素以及它的子图形元素。这个方法要做三件事:(1),将图形元素有效性标志设置为true。 (2),调用布局管理器布局本图形元素。(3),对图形元素的的子图形元素依次调用Validate()方法。
4. 在Figure中的revalidate()方法:使本图形元素无效并重新有效化它的父亲。这个方法要做两件事:(1),使本图形元素无效。(2),如果本图形元素的父亲存在,就对它的父亲调用revalidate()方法;否则,如果本图形元素是根图形元素,就将本图形元素增加到更新管理器中的无效图形元素队列中去。很明显,这是一个递推的过程,最终必然会递推到根图形元素并将根图形元素放置到更新管理器的无效图形元素队列中去。
对图形元素调用validate会使该图形元素被重新布局,而重新布局会导致图形元素向更行管理器报告脏区域,然后更新管理器会再次绘制被报告的脏区域,但需要说明的时,这个过程不会导致无限循环。
现在解释与fireValidating()方法调用相关的问题
UpdateManager是一个事件容器,能够产生“正在被有效化”和“正在被绘制”事件。到目前位置,我所知道的,只有FigureCanvas对更新管理器的“正在被有效化”事件感兴趣。因为有效化的结果会导致内容图形元素的大小发生变化,而视口是用来显示部分内容图形元素的,当内容图形元素的大小发生变化时,一定也要调整视口的布局,使它能够正确的显示内容图形元素。所以FigureCanvas会将自身注册到更行管理器中,每当UpdateManager有效化无效图形元素之前,FigureCanvas都会得到通知,以重新布局视口。
repairDamage()方法的执行逻辑过程如下,它的任务是重绘更新队列中的脏区域并调用firePainting(Rectangle, Map),除非没有脏区域。
protected void repairDamage()
{
1. 定义一个矩形对象contribution,该对象要负责承载需要被绘制的精确矩形。
2. 在一个迭代循环中完成如下事情:
a,获取图形元素figure,将contribution设置为figure的脏区域与其约束边界的交集。
b,在考虑f的祖先层次的的情况下计算计算figure贡献的脏区域矩形范围,这个范围被累积到contribution中,这个逻辑过程是在一个循环中完成的。
c,将contribution累积到记录累积被毁坏区域的变量damage中
3. 如果dirtyRegions的元素个数不为0,那么
a,调用firePainting(damage, dirtyRegions)通知对绘制被毁坏的区域感兴趣的外部对象。
b,清除脏区域图
4. 如果damage不为空,那么
a, 获取damage中的Graphics绘图对象。
b, 如果绘图对象不为空,那么就对root图形元素调用paint()方法并释放Graphics对象。
5. 将damage设置为null。
}
· 步骤2的目的是合并所有的脏区域以获得所有脏区域的并集。
· 步骤3的目的是向外界通知更新管理器即将更新脏区域,并将脏区域列表复位。
· 步骤4的目的是通过获取绘图上下文对象绘制脏区域。读者对“获取damage中的Graphics绘图对象”一话估计会难以理解,这句话的意思实际上就是:获取一个恰当的绘图上下文对象并将该damage区域设置为绘图上下文的剪切区。
现在对于更新管理器是如何绘制脏区域的,那么如何向更新管理器提交需要被绘制的脏区域呢?DeferredUpdateManager定义了两个方法:AddDirtyRegion和AddInvalidFigure。
先看AddDirtyRegion是如何实现的:
/*该函数表明与图形元素figure相关的由左上角点(x,y),宽w,高h构成的脏区域需要重绘。*/
public synchronized void addDirtyRegion(IFigure figure, int x, int y, int w, int h)
{
//如果图形元素没有被显示,那么就谈不上重绘了,所以就直接返回。
if (!figure.isShowing()) return;
//如果脏区域的宽度和高度为0,那么表明脏区域为空,就直接返回。
if (w == 0 || h == 0) return;
//从脏区域图中获取与图形元素figure相关的已经存在的脏矩形。
Rectangle rect = (Rectangle)dirtyRegions.get(figure);
//如果脏矩形区域rect为空,那么就将Rectangle(x,y,w,h)设置为figure的脏矩形区域;否则,将Rectangle(x,y,w,h)同rect合并。(注意:rect对象是个引用对象)。
if (rect == null)
{
rect = new Rectangle(x, y, w, h);
dirtyRegions.put(figure, rect);
}
else
{
rect.union(x, y, w, h);
}
//将更新操作排队,使操作系统在空闲时通过用户接口线程调用本对象的PerformUpdate()方法。
queueWork();
}
再看看AddInvalidFigure的实现,该方法要负责将给定的图形元素增加到更新队列。无效的图形元素将在给毁坏的区域被重绘之前被有效化。
public synchronized void addInvalidFigure(IFigure f)
{
//如果无效图形元素列表中已经含有了图形元素f,那么就直接返回。
if (invalidFigures.contains(f)) return;
//将更新操作排队,使操作系统在空闲时通过用户接口线程调用本对象的PerformUpdate()方法。
queueWork();
//将图形元素f增加到无效图形元素列表。
invalidFigures.add(f);
}
这个方法的实现似乎有些不可理解,虽然该方法声称将图形元素增加到了图形元素更新队列,但实际上在repairDamage根本就没有用到无效图形元素列表,repairDamage仅仅利用dirtyRegions来计算需要被重新绘制的精确区域;无效图形元素列表对计算需要被重新绘制的精确区域没有做任何贡献。但不要忘了,addInvalidFigure方法调用了queueWork(),而该调用会导致PerformUpdate()被调用,PerformUpdate()的调用会导致所有的无效图形元素被有效化,图形元素有效化的过程会导致图形元素向更新管理器报告脏区域,然后PerformUpdate()会再次被调用。结果,无效图形元素被正确绘制了。
4.结束语
本来打算在本章介绍图形上下文抽象、图形上下文源、图形上下文实现中种种需要考虑的问题以及figure的整个呈现过程的,但考虑到这些方面的内容足以再写成一篇文章了,所以打算把这方面的内容放到下一文章来完成。
在上期文章中,曾经说过要提供dxf浏览器给读者的,请从文章末尾的链接现在它们。这些材料被组织在一个.Net工作空间中, 通过Visual Studio 2003可以直接打开它,工作空间文件在DxfModel文件夹下。在打开工作空间后,一定要重新修改DxfViewer工程中对Graphstone2d的引用。Graphstone2d动态库位于与DxfModel文件夹同级目录下。具体操作过程:
1,首先将压缩文件解压,假设文件被解压到d:\dxf例子工程。
2,进入”d:\dxf例子工程“目录,然后将"mapgis陈家庄.rar"解压,将解压后的文件直接放置在d:\dxf例子工程目录下。
3,启动visual studio 2003,然后打开工作空间dxfmodel.sln。工作空间在d:\dxf例子工程\DxfModel目录下。如果通过直接双击dxfmodel.sln的方式打开工作空间的话,有可能打不开,出现什么版本不匹配之类的错误提示,所以不要通过这种方式打开dxfmodel.sln。
4,在打开工作空间后,将dxfViewer工程设为启动工程,并重新设置该工程对Graphstone2d的引用,该Graphstone2d就位于d:\dxf例子工程目录下。
5,运行dxfViewer工程。
如果读者觉得dxf文件解析器是可用的或者可部分使用的,可以随意使用;但如果能够在引用处注明代码来源的话,本人认为更合适一些。
dxf浏览器软件包下载链接 Draw2D目录下的“dxf例子工程.zip”
下面介绍几个与GEF和draw2d或图形框架开发相关的站点,希望对大家有用:
1,http://eclipsewiki.editme.com/GefDescription
2,http://c2.com/cgi/wiki?DirectManipulation
作者简介:
余学锋 1996年毕业于江汉石油学院。在大学时就对软件开发非常感兴趣,在大学时通过程序员中级水平考试;后来在2001年通过国家高级程序员水平考试。在2001年正式转行做软件开发,在做软件开发的将近5年的时间里,对软件开发的认识、态度、理念都发生了许多的变化,基本上完成了从程序开发到软件开发的过渡和转变。
从事过unix下c高级编程、tcp/ip网络编程、COM+开发。熟悉c++、VC++, .Net,了解java,目前正从事图形软件领域的开发.
e_mail:yxuefeng@slof.com
感谢javamxj同意发布、管理我的文章。javamxj创建的blog的站点:分享Java快乐