這章我將說一下如何去實(shí)現(xiàn)一個數(shù)據(jù)表在編輯器上顯示,并且能夠進(jìn)行位置和尺寸的改變。我們將涉及到的內(nèi)容有:Figure,EditPolicy,Command。示例代碼下載
1.在Editor上實(shí)現(xiàn)一個簡單的數(shù)據(jù)表
在上一章中,我們實(shí)現(xiàn)了一個空的Editor,畫布上什么都沒有,今天我會讓它顯示點(diǎn)東西。
我們已經(jīng)說過了,Editor要顯示一個圖形,是根據(jù)我們給Viewer設(shè)置的模型,找到對應(yīng)的EditPart,然后調(diào)用EditPart的createFigure得到圖形,最后繪制在Editor上,大致如下:
我們需要做的事情就讓我們的TableEditPart復(fù)寫基類的createFigure方法,然后返回一個Figure圖形,最后再在代碼中更改我們最初設(shè)置的模型內(nèi)容。
創(chuàng)建TableFigure
一般來說,當(dāng)我們需要生成一個Figure圖形的時候,最好的辦法就是從Figure類繼承一個新的類出來,然后復(fù)寫Figure的paintFigure方法,繪制出我們想要的圖案。見下代碼:
public
?
class
?TableFigure?extends?Figure?{
????
protected
?
void
?paintFigure(Graphics?graphics)?{
????????super.paintFigure(graphics);
????????
//
?得到Figure的Bounds
????????Rectangle?bounds?
=
?getBounds();
????????
//
?在它周圍繪制一個矩形,寬度和高度稍微小一點(diǎn),以便能全部顯示
????????graphics.drawRectangle(bounds.x,bounds.y,bounds.width?
-
?
1
?,?bounds.height?
-
?
1
);
????}
}
TableFigure 顯示的是一個矩形圖形。getBounds方法是得到的是Figure的范圍對象,這個對象是一個Recangle類型,包括有圖形的坐標(biāo)(Point),以及尺寸(Dimension),而這個范圍對象并不是我們?nèi)ブ付ǖ模歉鶕?jù)TableFigure的父Figure來給它設(shè)定的。這里需要說一下,在GEF中,EditPart在繪制Figure的時候,步驟是先繪制好自己的Figure,然后查看自己的子EditPart,獲得他們的Figure,確定他們的大小位置(根據(jù)默認(rèn)或者是本身Figure的布局管理器來得到)繪制在自身Figure之上,也就是說,這是一個遞歸的過程。
修改TableEditPart代碼
我們已經(jīng)生成了一個TableFigure類,現(xiàn)在我們需要讓TableEditPart的createFigure方法返回這個類的一個實(shí)例。
public
?
class
?TableEditPart?extends?DBEditPartBase?{
????
protected
?IFigure?createFigure()?{
????????
//
?返回Table的Figure
????????
return
?
new
?TableFigure();
????}
}
重新設(shè)置模型
我們創(chuàng)建Editor的時候,在初始化Viewer的方法中,生成了一個Schema模型給它,現(xiàn)在我們需要把我們的Table模型添加上去,也就是說,讓新的Table模型作為Schema的子對象添加進(jìn)去
protected
?
void
?initializeGraphicalViewer()?{
????????
//
?硬編碼生成一個數(shù)據(jù)庫模型
????????
//
?這個數(shù)據(jù)庫中有一個表
????????Schema?schema?
=
?
new
?Schema();
????????Table?table?
=
?
new
?Table();
????????
????????schema.addChild(table);
????????
this
.getGraphicalViewer().setContents(schema);
????}
可以看得出來,我們其實(shí)只是修改了我們的模型,但是圖形顯示是和EditPart相關(guān)的,SchemaEditPart是如何得知它會擁有一個子EditPart —— TableEditPart的呢?仔細(xì)看下DBBaseEditPart就可以很清楚了。
由于我們復(fù)寫了EditPart的getModelChildren,返回的是DBBaseEditPart對應(yīng)模型的子模型,所以EditPart就可以通過這些子模型來得到子編輯單元(Chilren EditPart)
????
protected
?List?getModelChildren()?{
????????
if
(getModel()?instanceof?DBBase){
????????????
return
?((DBBase)getModel()).getChildren();
????????}
????????
return
?super.getModelChildren();
????}
最后讓我們運(yùn)行一下,得到了一個繪制有矩形的Editor:
2.初步討論EditPolicy
在剛才我們實(shí)現(xiàn)的編輯其中,已經(jīng)將Table模型的圖形顯示了出來,但是僅僅只是有一個矩形畫在畫布上,當(dāng)我們點(diǎn)擊它的時候沒有任何反應(yīng),好像完全只是一張靜態(tài)的圖片,我們要的效果的并不是這個,而是需要一個能夠?qū)ζ洳僮鞯膱D形。
在第一章里我簡單地提到了在GEF中事件處理的過程,看看前面的文章可以知道,我們想要能夠?qū)ξ覀冿@示的TableFigure圖形進(jìn)行編輯,需要的是一個能夠處理相關(guān)Request的EditPolicy。
EditPolicy的類型有很多,大致分為圖形相關(guān)和圖形無關(guān)兩大類,我在這個例子中使用到了這幾個圖形相關(guān)的EditPolicy:LayoutEditPolicy ,NonResizeableEditPolicy。
LayoutEditPolicy 一般是作為父EditPart所具有的EditPolicy,就是說,如果我們的Figure需要對它的子EditPart進(jìn)行一些圖形方面管理的話,使用這個EditPolicy比較合適。它能夠處理一些容器類EditPart的應(yīng)該具備的操作:增加一個EditPart,刪除一個EditPart,移動子EditPart對應(yīng)圖形等。
并且它還能夠?yàn)樗淖覧ditPart設(shè)置對應(yīng)的EditPolicy,這樣一來就統(tǒng)一了子EditPart的一些行為。一會我講具體說一下這個問題。
NonResizeableEditPolicy,顧名思義,它是一個不處理尺寸變化的EditPolicy,但是它能夠處理EditPart對應(yīng)Figure位置變化,由于我們的數(shù)據(jù)表的大小需要根據(jù)它所擁有列來決定,外界不應(yīng)該對它的尺寸進(jìn)行修改,所以我在這里選用了它。
怎么使用EditPolicy呢?EditPolicy是被"安裝"到EditPart上的,在EditPart中,有一個接口方法:createEditPolicies,我們要在這里面進(jìn)行安裝。安裝EditPolicy使用installEditPolicy方法。
回過頭再看看我們所要用的這兩個EditPolicy,他們兩個都應(yīng)該安裝到哪個EditPart上呢。很顯然,LayoutEditPolicy應(yīng)該安裝到父EditPart,也就是SchemaEditPart,這樣一來,SchemaEditPart就能對TableEditPart進(jìn)行管理了; NonResizeableEditPolicy就應(yīng)該屬于TableEditPart,我們需要用它來改變TableEditPart對應(yīng)Figure 的位置。
當(dāng)然,LayoutEditPolicy和NonResizeableEditPolicy不能實(shí)例化后直接安裝到 EditPart上,因?yàn)槲覀冞€需要復(fù)寫他們的一些方法,稍后我們會提到。我們先創(chuàng)建兩個類,分別繼承LayoutEditPolicy, NonResizeableEditPolicy:
public
?
class
?SchemaLayoutEditPolicy?extends?LayoutEditPolicy?{
????
protected
?EditPolicy?createChildEditPolicy(EditPart?child)?{
????????????? ?
return
?
null
;
????}
????
protected
?Command?getCreateCommand(CreateRequest?request)?{
????????????? ?
return
?
null
;
????}
????
protected
?Command?getDeleteDependantCommand(Request?request)?{
????????????????
return
?
null
;
????}
???
protected
?Command?getMoveChildrenCommand(Request?request)?{
????????????? ?
return
?
null
;
????}
}
?
public
?
class
?TableNonResizableEditPolicy?extends?NonResizableEditPolicy?{
}
很明顯,這輛個類只是繼承了基類,并沒有復(fù)寫或?qū)崿F(xiàn)基類的方法。
姑且這樣,我們先把SchemaLayoutEditPolicy安裝到SchemaEditPart上:
????
protected
?
void
?createEditPolicies()?{
????????
this
.installEditPolicy(EditPolicy.LAYOUT_ROLE,
new
?SchemaLayoutEditPolicy());
????}
installEditPolicy 的第一個參數(shù)其實(shí)并沒有什么用,這個參數(shù)是一個String類型,隨便寫個字符串也不會影響到我們EditPolicy的安裝以及它的工作的(也有人說,如果第一個參數(shù)重復(fù)的話,EditPolicy會被覆蓋掉。我沒有研究過,各位朋友可以試一下)。
好了,SchemaEditPart所需要的EditPolicy已經(jīng)搞定,剩下TableEditPart了。大家可能會認(rèn)為,安裝它的EditPolicy也和 SchemaEditPart一樣,復(fù)寫createEditPolicies方法,然后installEditPolicy即可。是的,這樣沒有問題,但是由于我們的SchemaLayoutEditPolicy中有這么一個接口方法:createChildEditPolicy,這就是我在前面所說的,為了統(tǒng)一管理,給子EditPart安裝所對應(yīng)的EditPolicy。雖然這樣做和直接安裝的效果差不多(應(yīng)該是一樣,但是也有可能有一些差別),但是我認(rèn)為還是把子EditPolicy的安裝交給父EditPolicy吧:
????
protected
?EditPolicy?createChildEditPolicy(EditPart?child)?{
????????
if
(child?instanceof?TableEditPart)?
return
?
new
?TableNonResizableEditPolicy();
????????
return
?
new
?NonResizableEditPolicy();
????}
好了,讓我們運(yùn)行一下。呵呵,是不是可以點(diǎn)擊我們的“數(shù)據(jù)表”了。但是這樣還是不能移動我們的數(shù)據(jù)表。
3. 修改我們的類 ;Command 的使用
在第一章我已經(jīng)講過了,我們的模型有時需要添加一些和模型本身無關(guān)但和圖形有關(guān)的屬性。因?yàn)檫@樣一來我們就能夠記錄我們的圖形發(fā)生的位置變化,再通過模型的改變?nèi)ネㄖ狤ditPart刷新我們的圖形。
我們先為Table增加一個屬性:location
????
protected
?Point?location?
=
?
new
?Point(
0
,
0
);
????
/*
*
?????*?@return?返回?location.
?????
*/
????
public
?Point?getLocation()?{
????????
return
?location;
????}
????
/*
*
?????*?@param?location?設(shè)置?location?
?????
*/
????
public
?
void
?setLocation(Point?location)?{
????????Point?old?
=
?
this
.location;
????????
this
.location?
=
?location;
????}
這個屬性代表了圖形目前所在的位置坐標(biāo)。
有朋友要問:這里只是有了屬性,那當(dāng)屬性改變的時候怎么去通知呢?我記得我也在第一章講了,一般的做法是為我們的模型增加一個屬性更改的事件發(fā)生源:PropertyChangeSupport
我們把事件發(fā)生源寫到基類DBBase中,并增加幾個方法去發(fā)送事件以及添加刪除監(jiān)聽器:
????
public
?
static
?final?String?PRO_FIGURE?
=
?
"
__figure__property
"
;
????
????
private
?PropertyChangeSupport?support?
=
?
new
?PropertyChangeSupport(
this
);
?
public
?
void
?addPropertyChangeListener(PropertyChangeListener?l){
????????support.addPropertyChangeListener(l);
????}
????
????
public
?
void
?removePropertyChangeListener(PropertyChangeListener?l){
????????support.removePropertyChangeListener(l);
????}
????
????
public
?
void
?fireFigurePropertyChange(Object?old,Object?now){
????????support.firePropertyChange(PRO_FIGURE,old,now);
????}
好了,我們的事件源做好了,下面該想想讓誰去監(jiān)聽了。
毫無疑問,我們的監(jiān)聽器應(yīng)該是DBBaseEditPart,因?yàn)樗庞心芰θニ⑿翭igure,所以我們需要更改DBBasEditPart代碼,如下:
public
?
class
?DBEditPartBase?extends?AbstractGraphicalEditPart?implements?PropertyChangeListener{
????
public
?
void
?activate()?{
????????
if
(getModel()?
!=
?
null
?
&&
?getModel()?instanceof?DBBase){
????????????((DBBase)getModel()).addPropertyChangeListener(
this
);
????????}
????????super.activate();
????}
?????
public
?
void
?deactivate()?{
????????
if
(getModel()?
!=
?
null
?
&&
?getModel()?instanceof?DBBase){
????????????((DBBase)getModel()).removePropertyChangeListener(
this
);
????????}
????????super.deactivate();
????}
????
public
?
void
?propertyChange(PropertyChangeEvent?evt)?{
???????String?pName?
=
?evt.getPropertyName();
???????
if
(pName.equals(DBBase.PRO_FIGURE)){
???????????
this
.refreshVisuals();
???????}
????}
}
大家注意下propertyChange方法,當(dāng)我們在獲得事件類型為PRO_FIGURE后,就會直接去調(diào)用refreshVisuals去刷新我們的Figure。
但是refreshVisuals其實(shí)是空方法,它什么都沒有做!
所以我們必須在TableEditPart中要復(fù)寫它:
???
protected
?
void
?refreshVisuals()?{
????????super.refreshVisuals();
????????
//
?得到當(dāng)前Figure的位置和大小
????????Rectangle?rect?
=
?
this
.getFigure().getBounds();
????????
????????
//
?獲得更改后的位置
????????Point?p?
=
?((Table)?getModel()).getLocation();
????????
????????
//
?我們只更改Table的位置
????????((GraphicalEditPart)?
this
.getParent()).setLayoutConstraint(
this
,?
this
????????????????.getFigure(),
new
?Rectangle(p,?rect.getSize()));
????}
最后一句代碼是什么含義呢?這是讓TableEditPart去找到它的父EditPart,也就是SchemaEditPart,再讓它去“約束” TableEditPart圖形的位置和大小,當(dāng)然了,我們這里沒有改變大小,只是通過Table模型的Location屬性去更改它的位置而已。當(dāng)調(diào)用了setLayoutConstraint方法后,我們的圖形就會自動進(jìn)行重繪。
接下來,讓Table模型中更改location屬性時將更改事件發(fā)送出來,以便EditPart能夠截獲并處理:
????public?void?setLocation(Point?location)?{
????????Point?old?=?this.location;
????????this.location?=?location;
????????this.fireFigurePropertyChange(old,this.location);
????}
我們已經(jīng)做了很多調(diào)整,改了不少代碼了,這會運(yùn)行看看吧!
對不起,我們的TableFigure還是不會移動!
這是由于我們忘記了在TableNonResizeableEditPolicy中做文章。
剛才已經(jīng)說過了,TableNonResizeableEditPolicy能夠處理對圖形移動的,但是它只是通知我們圖形移動了,要找我們索取一個 Command去執(zhí)行這種變化。所以我們還需要寫一個Command類,讓這個Command去執(zhí)行對模型位置的更改(關(guān)于Command的介紹請回過頭看
第一章):
public?class?TableMoveCommand?extends?Command?{
????private?ChangeBoundsRequest?request;
????
????private?Table?model;
????public?void?execute()?{
??????Point?old?=?getModel().getLocation();
??????int?x?=?request.getMoveDelta().x;
??????int?y?=?request.getMoveDelta().y;
??????
??????getModel().setLocation(new?Point(old.x+x,old.y+y));
????}
}
然后我們在TableNonResizeableEditPolicy中復(fù)寫getMoveCommand方法:
?protected?Command?getMoveCommand(ChangeBoundsRequest?request)?{
????????TableMoveCommand?command?=?new?TableMoveCommand();
????????command.setModel((Table)getHost().getModel());
????????command.setRequest(request);
????????return?command;
????}
好了!這會再運(yùn)行看看,是不是能移動了?
4.結(jié)束語我們今天把上次例子代碼進(jìn)行了一些修改,得到了一個能夠隨意改變位置的矩形,其中主要簡單地講述了一些EditPolicy如何使用。
本人文筆比較爛,說事情總說不清,如果有不清楚的地方請留言,我會進(jìn)行修改,盡量讓大家都能看懂。
以后的章節(jié),我們會繼續(xù)討論EditPolicy以及Figure布局等問題。