Posted on 2008-01-02 15:32
笑看人生 阅读(3524)
评论(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框架中最重要的部分,也是最复杂的部分,控制器。