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是如何實作的:
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
3, http://www.osgi.org/
作者簡介:
餘學鋒 1996年畢業于江漢石油學院。在大學時就對軟體開發非常感興趣,在大學時通過程式員中級水準考試;後來在2001年通過國家進階程式員水準考試。在2001年正式轉行做軟體開發,在做軟體開發的将近5年的時間裡,對軟體開發的認識、态度、理念都發生了許多的變化,基本上完成了從程式開發到軟體開發的過渡和轉變。
從事過unix下c進階程式設計、tcp/ip網絡程式設計、COM+開發。熟悉c++、VC++, .Net,了解java,目前正從事圖形軟體領域的開發.
e_mail:[email protected]
感謝javamxj同意釋出、管理我的文章。javamxj建立的blog的站點: 分享Java快樂