Posted on 2008-01-02 15:32
笑看人生 阅读(3519)
评论(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父类,具体代码如下:
1package com.example.workflow.model;
2
3import java.beans.PropertyChangeListener;
4import java.beans.PropertyChangeSupport;
5import java.io.IOException;
6import java.io.ObjectInputStream;
7import java.io.Serializable;
8publicclass 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,把这些公共属性都放在这个父类中,父类的代码如下:
1package com.example.workflow.model;
2
3import java.util.ArrayList;
4import java.util.List;
5
6import org.eclipse.draw2d.geometry.Dimension;
7import 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 */
17public 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方法,这个方面就是通知控制器,模型的属性发生发生变化了,让控制器根据相应的属性来刷新视图。
定义了活动的父类之后,我们就可以分别来定义开始活动,普通活动,结束活动对应的类了,具体代码如下:
开始活动:
1package com.example.workflow.model;
2
3public 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}
普通活动:
1package com.example.workflow.model;
2
3
4public 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
结束活动:
1package com.example.workflow.model;
2
3public 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}
定义完这些活动之后,我们来定义流程模型,由于流程中包含多个活动,所以里面应该有个列表来维护这些对象,同样,流程中还包含多个转移,由于在活动模型中,已经维护了转移对象,所以这里就不维护这些转移对象了,具体代码如下:
1package com.example.workflow.model;
2
3import java.util.ArrayList;
4import java.util.List;
5
6/** *//**
7 * 流程模型,可以包含多个活动和转移模型
8 * @author Administrator
9 *
10 */
11public 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}
最后我们来定义转移模型,我们知道转移模型是链接两个活动的,所以在转移模型中,应该有个转移的源活动和目标活动,同时如果两个活动之间已经有转移连接时,就不能再在两者之间建立转移了,所以在两个活动之间建立转移时,必须先判断两者之间是否已经建立转移,所以转移模型具体代码如下:
1package com.example.workflow.model;
2
3/** *//**
4 *ATransitionbetweentwodistinctactivities.
5 */
6public 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框架中最重要的部分,也是最复杂的部分,控制器。