Posted on 2008-01-02 15:32
笑看人生 閱讀(3528)
評論(5) 編輯 收藏 所屬分類:
Java插件開發
我自從進入公司后,一直從事有關gef方面的開發工作,在這期間,走過不少彎路,僅僅是把GEF框架弄明白,就費了很大力氣,所以,現在想寫一點東西出來,供初學者閱讀。
GEF(Graphical Editing Framework)是圖形化編輯器開發的工具,比較典型的應用就是IBM 的Rose,它是一個模型驅動的MVC框架,控制器(EditPart)作為模型的偵聽器,偵聽模型的變化,如果模型的屬性發生變化,它會通知控制器,控制器就會刷新模型對應的視圖(Figure)。可以看出模型和視圖之間沒有直接聯系,它們通過控制器而間接聯系,可見控制器在gef框架中有著很重要的重要。
下面我們將從最基本的開始,介紹如何用GEF框架開發出一個流程設計器(開發工具Eclipse3.2.1包含插件包gef3.2.1和draw2d3.2.1)。
我們首先從模型開始,流程設計器頂層模型是流程(WorkflowProcess),流程有活動和鏈接這些活動的轉移組成,其中活動又可以分為開始活動,普通活動,結束活動。理解了模型之間的組成關系,我們就可以設計模型對應的類了。由于上面提到,模型的屬性變化了,必須通知控制器,由它來刷新模型對應的視圖,所以控制器必須注冊為模型的偵聽器。由于每個模型都有相應的控制器偵聽器偵聽它屬性的變化,我們把這方面的功能都放在父類中,定義一個ModelElement父類,具體代碼如下:
1
package com.example.workflow.model;
2
3
import java.beans.PropertyChangeListener;
4
import java.beans.PropertyChangeSupport;
5
import java.io.IOException;
6
import java.io.ObjectInputStream;
7
import java.io.Serializable;
8
publicclass ModelElement implements Serializable
{
9
privatestaticfinallongserialVersionUID = -5117340723140888394L;
10
privatetransient PropertyChangeSupport pcsDelegate = new PropertyChangeSupport(this);
11
12
publicsynchronizedvoid addPropertyChangeListener(PropertyChangeListener l)
{
13
if (l == null)
{
14
thrownew IllegalArgumentException();
15
}
16
pcsDelegate.addPropertyChangeListener(l);
17
}
18
19
protectedvoid firePropertyChange(String property, Object oldValue, Object newValue)
{
20
if (pcsDelegate.hasListeners(property))
{
21
pcsDelegate.firePropertyChange(property, oldValue, newValue);
22
}
23
}
24
25
privatevoid readObject(ObjectInputStream in)
26
throws IOException, ClassNotFoundException
{
27
in.defaultReadObject();
28
pcsDelegate = new PropertyChangeSupport(this);
29
}
30
31
publicsynchronizedvoid removePropertyChangeListener(PropertyChangeListener l)
{
32
if (l != null)
{
33
pcsDelegate.removePropertyChangeListener(l);
34
}
35
}
36
37
}
38
接下來我們定義流程,活動,轉移模型,讓這些模型都繼承這個父類ModelElement,我們注意到活動由開始活動,普通活動,結束活動組成,這三類活動由很多相同的屬性,例如活動的位置,名稱,大小等等,所以給這三類活動進行抽象,定義一個父類AbstractActivity,把這些公共屬性都放在這個父類中,父類的代碼如下:
1
package com.example.workflow.model;
2
3
import java.util.ArrayList;
4
import java.util.List;
5
6
import org.eclipse.draw2d.geometry.Dimension;
7
import org.eclipse.draw2d.geometry.Point;
8
9
/** *//**
10
* Abstract prototype of a Activity.
11
* Has a size (width and height), a location (x and y position) and a list of incoming
12
* and outgoing connections. Use subclasses to instantiate a specific Activity.
13
* @see com.example.workflow.model.Activity
14
* @see com.example.workflow.model.StartActivity
15
* @see com.example.workflow.model.EndActivity
16
*/
17
public class AbstractActivity extends ModelElement
{
18
19
private static final long serialVersionUID = 3023802629976246906L;
20
/** *//** Property ID to use when the location of this Activity is modified. */
21
public static final String LOCATION_PROP = "Activity.Location";
22
/** *//** Property ID to use then the size of this Activity is modified. */
23
public static final String SIZE_PROP = "Activity.Size";
24
/** *//** ID for the Name property value (used for by the corresponding property descriptor). */
25
public static final String NAME_PROP = "Activity.Name";
26
27
/** *//** Property ID to use when the list of outgoing transitions is modified. */
28
public static final String SOURCE_TRANSITIONS_PROP = "Activity.SourceTran";
29
/** *//** Property ID to use when the list of incoming transitions is modified. */
30
public static final String TARGET_TRANSITIONS_PROP = "Activity.TargetTran";
31
/** *//** ID for the Width property value (used for by the corresponding property descriptor). */
32
private static final String WIDTH_PROP = "Activity.Width";
33
/** *//** ID for the X property value (used for by the corresponding property descriptor). */
34
private static final String XPOS_PROP = "Activity.xPos";
35
/** *//** ID for the Y property value (used for by the corresponding property descriptor). */
36
private static final String YPOS_PROP = "Activity.yPos";
37
38
39
/** *//** Name of this Activity. */
40
private String name = new String("");
41
/** *//** Location of this Activity. */
42
private Point location = new Point(0, 0);
43
/** *//** Size of this Activity. */
44
private Dimension size = new Dimension(50, 50);
45
/** *//** List of outgoing Transitions. */
46
private List sourceTransitions = new ArrayList();
47
/** *//** List of incoming Transitions. */
48
private List targetTransitions = new ArrayList();
49
50
/** *//**
51
* Add an incoming or outgoing connection to this Activity.
52
* @param conn a non-null Transition instance
53
* @throws IllegalArgumentException if the Transition is null or has not distinct endpoints
54
*/
55
void addTransition(Transition tran)
{
56
if (tran == null || tran.getSource() == tran.getTarget())
{
57
throw new IllegalArgumentException();
58
}
59
if (tran.getSource() == this)
{
60
sourceTransitions.add(tran);
61
firePropertyChange(SOURCE_TRANSITIONS_PROP, null, tran);
62
} else if (tran.getTarget() == this)
{
63
targetTransitions.add(tran);
64
firePropertyChange(TARGET_TRANSITIONS_PROP, null, tran);
65
}
66
}
67
68
/** *//**
69
* Return the Name of this Activity.
70
* @return name
71
*/
72
public String getName()
{
73
return name;
74
}
75
76
/** *//**
77
* Return the Location of this Activity.
78
* @return a non-null location instance
79
*/
80
public Point getLocation()
{
81
return location.getCopy();
82
}
83
84
/** *//**
85
* Return the Size of this Activity.
86
* @return a non-null Dimension instance
87
*/
88
public Dimension getSize()
{
89
return size.getCopy();
90
}
91
92
/** *//**
93
* Return a List of outgoing Transitions.
94
*/
95
public List getSourceTransitions()
{
96
return new ArrayList(sourceTransitions);
97
}
98
99
/** *//**
100
* Return a List of incoming Transitions.
101
*/
102
public List getTargetTransitions()
{
103
return new ArrayList(targetTransitions);
104
}
105
106
/** *//**
107
* Remove an incoming or outgoing Transition from this Activity.
108
* @param conn a non-null Transition instance
109
* @throws IllegalArgumentException if the parameter is null
110
*/
111
void removeTransition(Transition tran)
{
112
if (tran == null)
{
113
throw new IllegalArgumentException();
114
}
115
if (tran.getSource() == this)
{
116
sourceTransitions.remove(tran);
117
firePropertyChange(SOURCE_TRANSITIONS_PROP, null, tran);
118
} else if (tran.getTarget() == this)
{
119
targetTransitions.remove(tran);
120
firePropertyChange(TARGET_TRANSITIONS_PROP, null, tran);
121
}
122
}
123
124
/** *//**
125
* Set the Name of this Activity.
126
* @param newName
127
* @throws IllegalArgumentException if the parameter is null
128
*/
129
public void setName(String newName)
{
130
if (newName == null)
{
131
throw new IllegalArgumentException();
132
}
133
this.name = newName;
134
firePropertyChange(LOCATION_PROP, null, name);
135
}
136
137
/** *//**
138
* Set the Location of this Activity.
139
* @param newLocation a non-null Point instance
140
* @throws IllegalArgumentException if the parameter is null
141
*/
142
public void setLocation(Point newLocation)
{
143
if (newLocation == null)
{
144
throw new IllegalArgumentException();
145
}
146
location.setLocation(newLocation);
147
firePropertyChange(LOCATION_PROP, null, location);
148
}
149
150
/** *//**
151
* Set the Size of this Activity.
152
* Will not modify the size if newSize is null.
153
* @param newSize a non-null Dimension instance or null
154
*/
155
public void setSize(Dimension newSize)
{
156
if (newSize != null)
{
157
size.setSize(newSize);
158
firePropertyChange(SIZE_PROP, null, size);
159
}
160
}
161
162
}
163
在這個類中,我們定義兩個List對象,分別對應該活動的輸入轉移和輸出轉移,因為對于一個完整的流程來說,每個活動都會轉移的(或者有輸入轉移,或者有輸出轉移,或者兩者都有),在這個類中,我們還注意到,每個改變對象屬性的方法中,都會調用firePropertyChange方法,這個方面就是通知控制器,模型的屬性發生發生變化了,讓控制器根據相應的屬性來刷新視圖。
定義了活動的父類之后,我們就可以分別來定義開始活動,普通活動,結束活動對應的類了,具體代碼如下:
開始活動:
1
package com.example.workflow.model;
2
3
public class StartActivity extends AbstractActivity
{
4
5
private staticfinallongserialVersionUID = 4639994300421360712L;
6
private staticfinal String STARTACTIVITY_NAME = "START";
7
8
public String getName()
{
9
returnSTARTACTIVITY_NAME;
10
}
11
12
public String toString()
{
13
return"StartActivity " + hashCode();
14
}
15
}
普通活動:
1
package com.example.workflow.model;
2
3
4
public class Activity extends AbstractActivity
{
5
6
private staticfinallongserialVersionUID = 3023802629976246906L;
7
private staticfinal String ACTIVITY_NAME = "ACTIVITY";
8
9
public String getName()
{
10
returnACTIVITY_NAME;
11
}
12
public String toString()
{
13
return"Activity " + hashCode();
14
}
15
16
}
17
結束活動:
1
package com.example.workflow.model;
2
3
public class EndActivity extends AbstractActivity
{
4
5
private static final long serialVersionUID = 316984190041034535L;
6
privates tatic final String ENDACTIVITY_NAME = "END";
7
8
public String getName()
{
9
returnENDACTIVITY_NAME;
10
}
11
12
public String toString()
{
13
return"EndActivity " + hashCode();
14
}
15
16
}
定義完這些活動之后,我們來定義流程模型,由于流程中包含多個活動,所以里面應該有個列表來維護這些對象,同樣,流程中還包含多個轉移,由于在活動模型中,已經維護了轉移對象,所以這里就不維護這些轉移對象了,具體代碼如下:
1
package com.example.workflow.model;
2
3
import java.util.ArrayList;
4
import java.util.List;
5
6
/** *//**
7
* 流程模型,可以包含多個活動和轉移模型
8
* @author Administrator
9
*
10
*/
11
public class WorkflowProcess extends ModelElement
{
12
13
private static final long serialVersionUID = -5478693636480758659L;
14
/** *//** Property ID to use when a child is added to this WorkflowProcess. */
15
public static final String CHILD_ADDED_PROP = "WorkflowProcess.ChildAdded";
16
/** *//** Property ID to use when a child is removed from this WorkflowProcess. */
17
public static final String CHILD_REMOVED_PROP = "WorkflowProcess.ChildRemoved";
18
private List activities = new ArrayList();
19
20
/** *//**
21
* Add a Activity to this WorkflowProcess.
22
* @param s a non-null Activity instance
23
* @return true, if the Activity was added, false otherwise
24
*/
25
public boolean addChild(Activity a)
{
26
if (a != null && activities.add(a))
{
27
firePropertyChange(CHILD_ADDED_PROP, null, a);
28
return true;
29
}
30
return false;
31
}
32
33
/** *//** Return a List of Activities in this WorkflowProcess. The returned List should not be modified. */
34
public List getChildren()
{
35
return activities;
36
}
37
38
/** *//**
39
* Remove a Activity from this WorkflowProcess.
40
* @param s a non-null Activity instance;
41
* @return true, if the Activity was removed, false otherwise
42
*/
43
public boolean removeChild(Activity a)
{
44
if (a != null && activities.remove(a))
{
45
firePropertyChange(CHILD_REMOVED_PROP, null, a);
46
return true;
47
}
48
return false;
49
}
50
}
最后我們來定義轉移模型,我們知道轉移模型是鏈接兩個活動的,所以在轉移模型中,應該有個轉移的源活動和目標活動,同時如果兩個活動之間已經有轉移連接時,就不能再在兩者之間建立轉移了,所以在兩個活動之間建立轉移時,必須先判斷兩者之間是否已經建立轉移,所以轉移模型具體代碼如下:
1
package com.example.workflow.model;
2
3
/** *//**
4
*ATransitionbetweentwodistinctactivities.
5
*/
6
public class Transition extends ModelElement
{
7
8
private static final long serialVersionUID = 516473924757575767L;
9
/** *//**True,ifthetransitionisattachedtoitsendpoints.*/
10
private boolean isConnected;
11
/** *//**Transition'ssourceendpoint.*/
12
private AbstractActivity source;
13
/** *//**Transition'stargetendpoint.*/
14
private AbstractActivity target;
15
16
/** *//**
17
*CreateaTransitionbetweentwodistinctactivities.
18
*@paramsourceasourceendpointforthisTransition(nonnull)
19
*@paramtargetatargetendpointforthisTransition(nonnull)
20
*@throwsIllegalArgumentExceptionifanyoftheparametersarenullorsource==target
21
*@see#setLineStyle(int)
22
*/
23
public Transition(AbstractActivity source, AbstractActivity target)
{
24
reconnect(source, target);
25
}
26
27
/** *//**
28
*Disconnectthisconnectionfromtheactivitiesitisattachedto.
29
*/
30
publicvoid disconnect()
{
31
if (isConnected)
{
32
source.removeTransition (this);
33
target.removeTransition (this);
34
isConnected = false;
35
}
36
}
37
38
/** *//**
39
*ReturnsthesourceendpointofthisTransition.
40
*@returnanon-nullActivityinstance
41
*/
42
public AbstractActivity getSource()
{
43
returnsource;
44
}
45
46
/** *//**
47
*ReturnsthetargetendpointofthisTransition.
48
*@returnanon-nullActivityinstance
49
*/
50
public AbstractActivity getTarget()
{
51
returntarget;
52
}
53
54
/** *//**
55
*ReconnectthisTransition.
56
*TheTransitionwillreconnectwiththeactivitiesitwaspreviouslyattachedto.
57
*/
58
public void reconnect()
{
59
if (!isConnected)
{
60
source.addTransition (this);
61
target.addTransition (this);
62
isConnected = true;
63
}
64
}
65
66
/** *//**
67
*Reconnecttoadifferentsourceand/ortargetActivity.
68
*Theconnectionwilldisconnectfromitscurrentattachmentsandreconnectto
69
*thenewsourceandtarget.
70
*@paramnewSourceanewsourceendpointforthisTransition(nonnull)
71
*@paramnewTargetanewtargetendpointforthisTransition(nonnull)
72
*@throwsIllegalArgumentExceptionifanyoftheparamersarenullornewSource==newTarget
73
*/
74
public void reconnect(AbstractActivity newSource, AbstractActivity newTarget)
{
75
if (newSource == null || newTarget == null || newSource == newTarget)
{
76
thrownew IllegalArgumentException();
77
}
78
disconnect();
79
this.source = newSource;
80
this.target = newTarget;
81
reconnect();
82
}
83
}
到這兒,模型的定義已經全部完成,下一節我們將定義GEF框架中最重要的部分,也是最復雜的部分,控制器。