這章我將說一下如何去實現一個數據表在編輯器上顯示,并且能夠進行位置和尺寸的改變。我們將涉及到的內容有:Figure,EditPolicy,Command。示例代碼下載
1.在Editor上實現一個簡單的數據表
在上一章中,我們實現了一個空的Editor,畫布上什么都沒有,今天我會讓它顯示點東西。
我們已經說過了,Editor要顯示一個圖形,是根據我們給Viewer設置的模型,找到對應的EditPart,然后調用EditPart的createFigure得到圖形,最后繪制在Editor上,大致如下:
我們需要做的事情就讓我們的TableEditPart復寫基類的createFigure方法,然后返回一個Figure圖形,最后再在代碼中更改我們最初設置的模型內容。
創建TableFigure
一般來說,當我們需要生成一個Figure圖形的時候,最好的辦法就是從Figure類繼承一個新的類出來,然后復寫Figure的paintFigure方法,繪制出我們想要的圖案。見下代碼:
public
?
class
?TableFigure?extends?Figure?{
????
protected
?
void
?paintFigure(Graphics?graphics)?{
????????super.paintFigure(graphics);
????????
//
?得到Figure的Bounds
????????Rectangle?bounds?
=
?getBounds();
????????
//
?在它周圍繪制一個矩形,寬度和高度稍微小一點,以便能全部顯示
????????graphics.drawRectangle(bounds.x,bounds.y,bounds.width?
-
?
1
?,?bounds.height?
-
?
1
);
????}
}
TableFigure 顯示的是一個矩形圖形。getBounds方法是得到的是Figure的范圍對象,這個對象是一個Recangle類型,包括有圖形的坐標(Point),以及尺寸(Dimension),而這個范圍對象并不是我們去指定的,而是根據TableFigure的父Figure來給它設定的。這里需要說一下,在GEF中,EditPart在繪制Figure的時候,步驟是先繪制好自己的Figure,然后查看自己的子EditPart,獲得他們的Figure,確定他們的大小位置(根據默認或者是本身Figure的布局管理器來得到)繪制在自身Figure之上,也就是說,這是一個遞歸的過程。
修改TableEditPart代碼
我們已經生成了一個TableFigure類,現在我們需要讓TableEditPart的createFigure方法返回這個類的一個實例。
public
?
class
?TableEditPart?extends?DBEditPartBase?{
????
protected
?IFigure?createFigure()?{
????????
//
?返回Table的Figure
????????
return
?
new
?TableFigure();
????}
}
重新設置模型
我們創建Editor的時候,在初始化Viewer的方法中,生成了一個Schema模型給它,現在我們需要把我們的Table模型添加上去,也就是說,讓新的Table模型作為Schema的子對象添加進去
protected
?
void
?initializeGraphicalViewer()?{
????????
//
?硬編碼生成一個數據庫模型
????????
//
?這個數據庫中有一個表
????????Schema?schema?
=
?
new
?Schema();
????????Table?table?
=
?
new
?Table();
????????
????????schema.addChild(table);
????????
this
.getGraphicalViewer().setContents(schema);
????}
可以看得出來,我們其實只是修改了我們的模型,但是圖形顯示是和EditPart相關的,SchemaEditPart是如何得知它會擁有一個子EditPart —— TableEditPart的呢?仔細看下DBBaseEditPart就可以很清楚了。
由于我們復寫了EditPart的getModelChildren,返回的是DBBaseEditPart對應模型的子模型,所以EditPart就可以通過這些子模型來得到子編輯單元(Chilren EditPart)
????
protected
?List?getModelChildren()?{
????????
if
(getModel()?instanceof?DBBase){
????????????
return
?((DBBase)getModel()).getChildren();
????????}
????????
return
?super.getModelChildren();
????}
最后讓我們運行一下,得到了一個繪制有矩形的Editor:
2.初步討論EditPolicy
在剛才我們實現的編輯其中,已經將Table模型的圖形顯示了出來,但是僅僅只是有一個矩形畫在畫布上,當我們點擊它的時候沒有任何反應,好像完全只是一張靜態的圖片,我們要的效果的并不是這個,而是需要一個能夠對其操作的圖形。
在第一章里我簡單地提到了在GEF中事件處理的過程,看看前面的文章可以知道,我們想要能夠對我們顯示的TableFigure圖形進行編輯,需要的是一個能夠處理相關Request的EditPolicy。
EditPolicy的類型有很多,大致分為圖形相關和圖形無關兩大類,我在這個例子中使用到了這幾個圖形相關的EditPolicy:LayoutEditPolicy ,NonResizeableEditPolicy。
LayoutEditPolicy 一般是作為父EditPart所具有的EditPolicy,就是說,如果我們的Figure需要對它的子EditPart進行一些圖形方面管理的話,使用這個EditPolicy比較合適。它能夠處理一些容器類EditPart的應該具備的操作:增加一個EditPart,刪除一個EditPart,移動子EditPart對應圖形等。
并且它還能夠為它的子EditPart設置對應的EditPolicy,這樣一來就統一了子EditPart的一些行為。一會我講具體說一下這個問題。
NonResizeableEditPolicy,顧名思義,它是一個不處理尺寸變化的EditPolicy,但是它能夠處理EditPart對應Figure位置變化,由于我們的數據表的大小需要根據它所擁有列來決定,外界不應該對它的尺寸進行修改,所以我在這里選用了它。
怎么使用EditPolicy呢?EditPolicy是被"安裝"到EditPart上的,在EditPart中,有一個接口方法:createEditPolicies,我們要在這里面進行安裝。安裝EditPolicy使用installEditPolicy方法。
回過頭再看看我們所要用的這兩個EditPolicy,他們兩個都應該安裝到哪個EditPart上呢。很顯然,LayoutEditPolicy應該安裝到父EditPart,也就是SchemaEditPart,這樣一來,SchemaEditPart就能對TableEditPart進行管理了; NonResizeableEditPolicy就應該屬于TableEditPart,我們需要用它來改變TableEditPart對應Figure 的位置。
當然,LayoutEditPolicy和NonResizeableEditPolicy不能實例化后直接安裝到 EditPart上,因為我們還需要復寫他們的一些方法,稍后我們會提到。我們先創建兩個類,分別繼承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?{
}
很明顯,這輛個類只是繼承了基類,并沒有復寫或實現基類的方法。
姑且這樣,我們先把SchemaLayoutEditPolicy安裝到SchemaEditPart上:
????
protected
?
void
?createEditPolicies()?{
????????
this
.installEditPolicy(EditPolicy.LAYOUT_ROLE,
new
?SchemaLayoutEditPolicy());
????}
installEditPolicy 的第一個參數其實并沒有什么用,這個參數是一個String類型,隨便寫個字符串也不會影響到我們EditPolicy的安裝以及它的工作的(也有人說,如果第一個參數重復的話,EditPolicy會被覆蓋掉。我沒有研究過,各位朋友可以試一下)。
好了,SchemaEditPart所需要的EditPolicy已經搞定,剩下TableEditPart了。大家可能會認為,安裝它的EditPolicy也和 SchemaEditPart一樣,復寫createEditPolicies方法,然后installEditPolicy即可。是的,這樣沒有問題,但是由于我們的SchemaLayoutEditPolicy中有這么一個接口方法:createChildEditPolicy,這就是我在前面所說的,為了統一管理,給子EditPart安裝所對應的EditPolicy。雖然這樣做和直接安裝的效果差不多(應該是一樣,但是也有可能有一些差別),但是我認為還是把子EditPolicy的安裝交給父EditPolicy吧:
????
protected
?EditPolicy?createChildEditPolicy(EditPart?child)?{
????????
if
(child?instanceof?TableEditPart)?
return
?
new
?TableNonResizableEditPolicy();
????????
return
?
new
?NonResizableEditPolicy();
????}
好了,讓我們運行一下。呵呵,是不是可以點擊我們的“數據表”了。但是這樣還是不能移動我們的數據表。
3. 修改我們的類 ;Command 的使用
在第一章我已經講過了,我們的模型有時需要添加一些和模型本身無關但和圖形有關的屬性。因為這樣一來我們就能夠記錄我們的圖形發生的位置變化,再通過模型的改變去通知EditPart刷新我們的圖形。
我們先為Table增加一個屬性:location
????
protected
?Point?location?
=
?
new
?Point(
0
,
0
);
????
/*
*
?????*?@return?返回?location.
?????
*/
????
public
?Point?getLocation()?{
????????
return
?location;
????}
????
/*
*
?????*?@param?location?設置?location?
?????
*/
????
public
?
void
?setLocation(Point?location)?{
????????Point?old?
=
?
this
.location;
????????
this
.location?
=
?location;
????}
這個屬性代表了圖形目前所在的位置坐標。
有朋友要問:這里只是有了屬性,那當屬性改變的時候怎么去通知呢?我記得我也在第一章講了,一般的做法是為我們的模型增加一個屬性更改的事件發生源:PropertyChangeSupport
我們把事件發生源寫到基類DBBase中,并增加幾個方法去發送事件以及添加刪除監聽器:
????
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);
????}
好了,我們的事件源做好了,下面該想想讓誰去監聽了。
毫無疑問,我們的監聽器應該是DBBaseEditPart,因為它才有能力去刷新Figure,所以我們需要更改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方法,當我們在獲得事件類型為PRO_FIGURE后,就會直接去調用refreshVisuals去刷新我們的Figure。
但是refreshVisuals其實是空方法,它什么都沒有做!
所以我們必須在TableEditPart中要復寫它:
???
protected
?
void
?refreshVisuals()?{
????????super.refreshVisuals();
????????
//
?得到當前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圖形的位置和大小,當然了,我們這里沒有改變大小,只是通過Table模型的Location屬性去更改它的位置而已。當調用了setLayoutConstraint方法后,我們的圖形就會自動進行重繪。
接下來,讓Table模型中更改location屬性時將更改事件發送出來,以便EditPart能夠截獲并處理:
????public?void?setLocation(Point?location)?{
????????Point?old?=?this.location;
????????this.location?=?location;
????????this.fireFigurePropertyChange(old,this.location);
????}
我們已經做了很多調整,改了不少代碼了,這會運行看看吧!
對不起,我們的TableFigure還是不會移動!
這是由于我們忘記了在TableNonResizeableEditPolicy中做文章。
剛才已經說過了,TableNonResizeableEditPolicy能夠處理對圖形移動的,但是它只是通知我們圖形移動了,要找我們索取一個 Command去執行這種變化。所以我們還需要寫一個Command類,讓這個Command去執行對模型位置的更改(關于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中復寫getMoveCommand方法:
?protected?Command?getMoveCommand(ChangeBoundsRequest?request)?{
????????TableMoveCommand?command?=?new?TableMoveCommand();
????????command.setModel((Table)getHost().getModel());
????????command.setRequest(request);
????????return?command;
????}
好了!這會再運行看看,是不是能移動了?
4.結束語我們今天把上次例子代碼進行了一些修改,得到了一個能夠隨意改變位置的矩形,其中主要簡單地講述了一些EditPolicy如何使用。
本人文筆比較爛,說事情總說不清,如果有不清楚的地方請留言,我會進行修改,盡量讓大家都能看懂。
以后的章節,我們會繼續討論EditPolicy以及Figure布局等問題。