流程設計器開發一(模型部分)

 
我自從進入公司後,一直從事有關gef方面的開發工作,在這期間,走過不少彎路,僅僅是把GEF框架弄明白,就費了很大力氣,所以,現在想寫一點東西出來,供初學者閱讀。
GEF(Graphical Editing Framework)是圖形化編輯器開發的工具,比較典型的應用就是IBM 的Rose,它是一個模型驅動的MVC框架,控制器(EditPart)作爲模型的偵聽器,偵聽模型的變化,如果模型的屬性發生變化,它會通知控制器,控制器就會刷新模型對應的視圖(Figure)。可以看出模型和視圖之間沒有直接聯繫,它們通過控制器而間接聯繫,可見控制器在gef框架中有着很重要的重要。
下面我們將從最基本的開始,介紹如何用GEF框架開發出一個流程設計器(開發工具Eclipse3.2.1包含插件包gef3.2.1和draw2d3.2.1)。
我們首先從模型開始,流程設計器頂層模型是流程(WorkflowProcess),流程有活動和鏈接這些活動的轉移組成,其中活動又可以分爲開始活動,普通活動,結束活動。理解了模型之間的組成關係,我們就可以設計模型對應的類了。由於上面提到,模型的屬性變化了,必須通知控制器,由它來刷新模型對應的視圖,所以控制器必須註冊爲模型的偵聽器。由於每個模型都有相應的控制器偵聽器偵聽它屬性的變化,我們把這方面的功能都放在父類中,定義一個ModelElement父類,具體代碼如下:
package com.example.workflow.model;
 
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
publicclass ModelElement implements Serializable{  
    privatestaticfinallongserialVersionUID = -5117340723140888394L;  
    privatetransient PropertyChangeSupport pcsDelegate = new PropertyChangeSupport(this);
      
    publicsynchronizedvoid addPropertyChangeListener(PropertyChangeListener l) {
       if (l == null) {
           thrownew IllegalArgumentException();
       }
       pcsDelegate.addPropertyChangeListener(l);
    }
      
    protectedvoid firePropertyChange(String property, Object oldValue, Object newValue) {
       if (pcsDelegate.hasListeners(property)) {
           pcsDelegate.firePropertyChange(property, oldValue, newValue);
       }
    }
      
    privatevoid readObject(ObjectInputStream in)
    throws IOException, ClassNotFoundException {
       in.defaultReadObject();
       pcsDelegate = new PropertyChangeSupport(this);
    }
   
    publicsynchronizedvoid removePropertyChangeListener(PropertyChangeListener l) {
       if (l != null) {
           pcsDelegate.removePropertyChangeListener(l);
       }
    }
 
}
 
接下來我們定義流程,活動,轉移模型,讓這些模型都繼承這個父類ModelElement,我們注意到活動由開始活動,普通活動,結束活動組成,這三類活動由很多相同的屬性,例如活動的位置,名稱,大小等等,所以給這三類活動進行抽象,定義一個父類AbstractActivity,把這些公共屬性都放在這個父類中,父類的代碼如下:
package com.example.workflow.model;
 
import java.util.ArrayList;
import java.util.List;
 
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
 
/**
 * Abstract prototype of a Activity.
 * Has a size (width and height), a location (x and y position) and a list of incoming
 * and outgoing connections. Use subclasses to instantiate a specific Activity.
 * @see com.example.workflow.model.Activity
 * @see com.example.workflow.model.StartActivity
 * @see com.example.workflow.model.EndActivity
 */
public class AbstractActivity extends ModelElement{
   
    private static final long serialVersionUID = 3023802629976246906L;
    /** Property ID to use when the location of this Activity is modified. */
    public static final String LOCATION_PROP = "Activity.Location";
    /** Property ID to use then the size of this Activity is modified. */
    public static final String SIZE_PROP = "Activity.Size";
    /** ID for the Name property value (used for by the corresponding property descriptor). */
    public static final String NAME_PROP = "Activity.Name";
   
    /** Property ID to use when the list of outgoing transitions is modified. */
    public static final String SOURCE_TRANSITIONS_PROP = "Activity.SourceTran";
    /** Property ID to use when the list of incoming transitions is modified. */
    public static final String TARGET_TRANSITIONS_PROP = "Activity.TargetTran";
    /** ID for the Width property value (used for by the corresponding property descriptor). */
    private static final String WIDTH_PROP = "Activity.Width";
    /** ID for the X property value (used for by the corresponding property descriptor). */
    private static final String XPOS_PROP = "Activity.xPos";
    /** ID for the Y property value (used for by the corresponding property descriptor). */
    private static final String YPOS_PROP = "Activity.yPos";
   
   
    /** Name of this Activity. */
    private String name = new String("");
    /** Location of this Activity. */
    private Point location = new Point(0, 0);
    /** Size of this Activity. */
    private Dimension size = new Dimension(50, 50);
    /** List of outgoing Transitions. */
    private List sourceTransitions = new ArrayList();
    /** List of incoming Transitions. */
    private List targetTransitions = new ArrayList();
   
    /**
     * Add an incoming or outgoing connection to this Activity.
     * @param conn a non-null Transition instance
     * @throws IllegalArgumentException if the Transition is null or has not distinct endpoints
     */
    void addTransition(Transition tran) {
       if (tran == null || tran.getSource() == tran.getTarget()) {
           throw new IllegalArgumentException();
       }
       if (tran.getSource() == this) {
           sourceTransitions.add(tran);
           firePropertyChange(SOURCE_TRANSITIONS_PROP, null, tran);
       } else if (tran.getTarget() == this) {
           targetTransitions.add(tran);
           firePropertyChange(TARGET_TRANSITIONS_PROP, null, tran);
       }
    }
   
    /**
     * Return the Name of this Activity.
     * @return name
     */
    public String getName() {
       return name;
    }
   
    /**
     * Return the Location of this Activity.
     * @return a non-null location instance
     */
    public Point getLocation() {
       return location.getCopy();
    }  
   
    /**
     * Return the Size of this Activity.
     * @return a non-null Dimension instance
     */
    public Dimension getSize() {
       return size.getCopy();
    }
 
    /**
     * Return a List of outgoing Transitions.
     */
    public List getSourceTransitions() {
       return new ArrayList(sourceTransitions);
    }
 
    /**
     * Return a List of incoming Transitions.
     */
    public List getTargetTransitions() {
       return new ArrayList(targetTransitions);
    }
   
    /**
     * Remove an incoming or outgoing Transition from this Activity.
     * @param conn a non-null Transition instance
     * @throws IllegalArgumentException if the parameter is null
     */
    void removeTransition(Transition tran) {
       if (tran == null) {
           throw new IllegalArgumentException();
       }
       if (tran.getSource() == this) {
           sourceTransitions.remove(tran);
           firePropertyChange(SOURCE_TRANSITIONS_PROP, null, tran);
       } else if (tran.getTarget() == this) {
           targetTransitions.remove(tran);
           firePropertyChange(TARGET_TRANSITIONS_PROP, null, tran);
       }
    }
   
    /**
     * Set the Name of this Activity.
     * @param newName
     * @throws IllegalArgumentException if the parameter is null
     */
    public void setName(String newName) {
       if (newName == null) {
           throw new IllegalArgumentException();
       }
       this.name = newName;
       firePropertyChange(LOCATION_PROP, null, name);
    }
 
    /**
     * Set the Location of this Activity.
     * @param newLocation a non-null Point instance
     * @throws IllegalArgumentException if the parameter is null
     */
    public void setLocation(Point newLocation) {
       if (newLocation == null) {
           throw new IllegalArgumentException();
       }
       location.setLocation(newLocation);
       firePropertyChange(LOCATION_PROP, null, location);
    }
   
    /**
     * Set the Size of this Activity.
     * Will not modify the size if newSize is null.
     * @param newSize a non-null Dimension instance or null
     */
    public void setSize(Dimension newSize) {
       if (newSize != null) {
           size.setSize(newSize);
           firePropertyChange(SIZE_PROP, null, size);
       }
    }
 
}
 
在這個類中,我們定義兩個List對象,分別對應該活動的輸入轉移和輸出轉移,因爲對於一個完整的流程來說,每個活動都會轉移的(或者有輸入轉移,或者有輸出轉移,或者兩者都有),在這個類中,我們還注意到,每個改變對象屬性的方法中,都會調用firePropertyChange方法,這個方面就是通知控制器,模型的屬性發生髮生變化了,讓控制器根據相應的屬性來刷新視圖。
定義了活動的父類之後,我們就可以分別來定義開始活動,普通活動,結束活動對應的類了,具體代碼如下:
開始活動:
package com.example.workflow.model;
 
publicclass StartActivity extends AbstractActivity{
   
    privatestaticfinallongserialVersionUID = 4639994300421360712L;
    privatestaticfinal String STARTACTIVITY_NAME = "START";
   
    public String getName() {      
       returnSTARTACTIVITY_NAME;
    }
 
    public String toString() {
       return"StartActivity " + hashCode();
    }
}
 
普通活動:
package com.example.workflow.model;
 
 
publicclass Activity extends AbstractActivity{
   
    privatestaticfinallongserialVersionUID = 3023802629976246906L;
    privatestaticfinal String ACTIVITY_NAME = "ACTIVITY";
   
    public String getName() {      
       returnACTIVITY_NAME;
    }
    public String toString() {
       return"Activity " + hashCode();
    }
 
}
 
結束活動:
package com.example.workflow.model;
 
publicclass EndActivity extends AbstractActivity{  
   
    privatestaticfinallongserialVersionUID = 316984190041034535L; 
    privatestaticfinal String ENDACTIVITY_NAME = "END";
   
    public String getName() {      
       returnENDACTIVITY_NAME;
    }
 
    public String toString() {
       return"EndActivity " + hashCode();
    }
 
}
 
定義完這些活動之後,我們來定義流程模型,由於流程中包含多個活動,所以裏面應該有個列表來維護這些對象,同樣,流程中還包含多個轉移,由於在活動模型中,已經維護了轉移對象,所以這裏就不維護這些轉移對象了,具體代碼如下:
package com.example.workflow.model;
 
import java.util.ArrayList;
import java.util.List;
 
/**
 * 流程模型,可以包含多個活動和轉移模型
 * @author Administrator
 *
 */
public class WorkflowProcess extends ModelElement{      
      
       private static final long serialVersionUID = -5478693636480758659L;
       /** Property ID to use when a child is added to this WorkflowProcess. */
       public static final String CHILD_ADDED_PROP = "WorkflowProcess.ChildAdded";
       /** Property ID to use when a child is removed from this WorkflowProcess. */
       public static final String CHILD_REMOVED_PROP = "WorkflowProcess.ChildRemoved";    
       private List activities = new ArrayList();
      
       /**
        * Add a Activity to this WorkflowProcess.
        * @param s a non-null Activity instance
        * @return true, if the Activity was added, false otherwise
        */
       public boolean addChild(Activity a) {
              if (a != null && activities.add(a)) {
                     firePropertyChange(CHILD_ADDED_PROP, null, a);
                     return true;
              }
              return false;
       }
 
       /** Return a List of Activities in this WorkflowProcess. The returned List should not be modified. */
       public List getChildren() {
              return activities;
       }
      
       /**
        * Remove a Activity from this WorkflowProcess.
        * @param s a non-null Activity instance;
        * @return true, if the Activity was removed, false otherwise
        */
       public boolean removeChild(Activity a) {
              if (a != null && activities.remove(a)) {
                     firePropertyChange(CHILD_REMOVED_PROP, null, a);
                     return true;
              }
              return false;
       }
}
 
最後我們來定義轉移模型,我們知道轉移模型是鏈接兩個活動的,所以在轉移模型中,應該有個轉移的源活動和目標活動,同時如果兩個活動之間已經有轉移連接時,就不能再在兩者之間建立轉移了,所以在兩個活動之間建立轉移時,必須先判斷兩者之間是否已經建立轉移,所以轉移模型具體代碼如下:
package com.example.workflow.model;
 
/**
 *ATransitionbetweentwodistinctactivities.
 */
publicclass Transition extends ModelElement{
   
    privatestaticfinallongserialVersionUID = 516473924757575767L;
    /**True,ifthetransitionisattachedtoitsendpoints.*/
    privatebooleanisConnected;
    /**Transition'ssourceendpoint.*/
    private AbstractActivity source;
    /**Transition'stargetendpoint.*/
    private AbstractActivity target;
   
    /**
     *CreateaTransitionbetweentwodistinctactivities.
     *@paramsourceasourceendpointforthisTransition(nonnull)
     *@paramtargetatargetendpointforthisTransition(nonnull)
     *@throwsIllegalArgumentExceptionifanyoftheparametersarenullorsource==target
     *@see#setLineStyle(int)
     */
    public Transition(AbstractActivity source, AbstractActivity target) {
       reconnect(source, target);
    }
 
    /**
     *Disconnectthisconnectionfromtheactivitiesitisattachedto.
     */
    publicvoid disconnect() {
       if (isConnected) {
           source.removeTransition (this);
           target.removeTransition (this);
           isConnected = false;
       }
    }
   
    /**
     *ReturnsthesourceendpointofthisTransition.
     *@returnanon-nullActivityinstance
     */
    public AbstractActivity getSource() {
       returnsource;
    }
 
    /**
     *ReturnsthetargetendpointofthisTransition.
     *@returnanon-nullActivityinstance
     */
    public AbstractActivity getTarget() {
       returntarget;
    }
 
    /**
     *ReconnectthisTransition.
     *TheTransitionwillreconnectwiththeactivitiesitwaspreviouslyattachedto.
     */ 
    publicvoid reconnect() {
       if (!isConnected) {
           source.addTransition (this);
           target.addTransition (this);
           isConnected = true;
       }
    }
 
    /**
     *Reconnecttoadifferentsourceand/ortargetActivity.
     *Theconnectionwilldisconnectfromitscurrentattachmentsandreconnectto
     *thenewsourceandtarget.
     *@paramnewSourceanewsourceendpointforthisTransition(nonnull)
     *@paramnewTargetanewtargetendpointforthisTransition(nonnull)
     *@throwsIllegalArgumentExceptionifanyoftheparamersarenullornewSource==newTarget
     */
    publicvoid reconnect(AbstractActivity newSource, AbstractActivity newTarget) {
       if (newSource == null || newTarget == null || newSource == newTarget) {
           thrownew IllegalArgumentException();
       }
       disconnect();
       this.source = newSource;
       this.target = newTarget;
       reconnect();
    }
}
到這兒,模型的定義已經全部完成,下一節我們將定義GEF框架中最重要的部分,也是最複雜的部分,控制器。
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章