我自從進入公司后,一直從事有關gef方面的開發工作,在這期間,走過不少彎路,僅僅是把GEF框架弄明白,就費了很大力氣,所以,現在想寫一點東西出來,供初學者閱讀。
GEF(Graphical Editing Framework)是圖形化編輯器開發的工具,比較典型的應用就是IBM 的Rose,它是一個模型驅動的MVC框架,控制器(EditPart)作為模型的偵聽器,偵聽模型的變化,如果模型的屬性發生變化,它會通知控制器,控制器就會刷新模型對應的視圖(Figure)。可以看出模型和視圖之間沒有直接聯系,它們通過控制器而間接聯系,可見控制器在gef框架中有著很重要的重要。
下面我們將從最基本的開始,介紹如何用GEF框架開發出一個流程設計器(開發工具Eclipse3.2.1包含插件包gef3.2.1和draw2d3.2.1)。
我們首先從模型開始,流程設計器頂層模型是流程(WorkflowProcess),流程有活動和鏈接這些活動的轉移組成,其中活動又可以分為開始活動,普通活動,結束活動。理解了模型之間的組成關系,我們就可以設計模型對應的類了。由于上面提到,模型的屬性變化了,必須通知控制器,由它來刷新模型對應的視圖,所以控制器必須注冊為模型的偵聽器。由于每個模型都有相應的控制器偵聽器偵聽它屬性的變化,我們把這方面的功能都放在父類中,定義一個ModelElement父類,具體代碼如下:
接下來我們定義流程,活動,轉移模型,讓這些模型都繼承這個父類ModelElement,我們注意到活動由開始活動,普通活動,結束活動組成,這三類活動由很多相同的屬性,例如活動的位置,名稱,大小等等,所以給這三類活動進行抽象,定義一個父類AbstractActivity,把這些公共屬性都放在這個父類中,父類的代碼如下:
在這個類中,我們定義兩個List對象,分別對應該活動的輸入轉移和輸出轉移,因為對于一個完整的流程來說,每個活動都會轉移的(或者有輸入轉移,或者有輸出轉移,或者兩者都有),在這個類中,我們還注意到,每個改變對象屬性的方法中,都會調用firePropertyChange方法,這個方面就是通知控制器,模型的屬性發生發生變化了,讓控制器根據相應的屬性來刷新視圖。
定義了活動的父類之后,我們就可以分別來定義開始活動,普通活動,結束活動對應的類了,具體代碼如下:
開始活動:
普通活動:
結束活動:
定義完這些活動之后,我們來定義流程模型,由于流程中包含多個活動,所以里面應該有個列表來維護這些對象,同樣,流程中還包含多個轉移,由于在活動模型中,已經維護了轉移對象,所以這里就不維護這些轉移對象了,具體代碼如下:
最后我們來定義轉移模型,我們知道轉移模型是鏈接兩個活動的,所以在轉移模型中,應該有個轉移的源活動和目標活動,同時如果兩個活動之間已經有轉移連接時,就不能再在兩者之間建立轉移了,所以在兩個活動之間建立轉移時,必須先判斷兩者之間是否已經建立轉移,所以轉移模型具體代碼如下:
到這兒,模型的定義已經全部完成,下一節我們將定義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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

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

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

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
}

2

3

4

5

6

7

8

9

10

11

12

13

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

2

3

4

5

6

7

8

9

10

11

12

13

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
}

2

3

4

5

6

7

8

9

10

11

12

13

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
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

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
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

到這兒,模型的定義已經全部完成,下一節我們將定義GEF框架中最重要的部分,也是最復雜的部分,控制器。