IBM Skip to main content
Search for:   within 
      Search help  
     IBM home  |  Products & services  |  Support & downloads   |  My account

developerWorks > Open source projects | Java technology
developerWorks
Create an Eclipse-based application using the Graphical Editing Framework
95 KBe-mail it!
Contents:
Overview of GEF
Step 1. Bring your own model
Step 2. Define the view
Step 3. Write your EditParts, the controllers
Step 4. Bring it all together
Next steps
Resources
About the author
Rate this article
Related content:
Developing JFace wizards
Using JFace and SWT in stand-alone mode
Other Eclipse articles for developers
Subscriptions:
dW newsletters
dW Subscription
(CDs and downloads)
How to get started with the GEF

Level: Intermediate

Randy Hudson
Software developer, IBM
29 July 2003

This article describes the initial steps involved in creating an Eclipse-based application using the Graphical Editing Framework (GEF). GEF has been used to build a variety of applications for Eclipse, including state diagrams, activity diagrams, class diagrams, GUI builders for AWT, Swing and SWT, and process flow editors. Eclipse and GEF are both open source technologies. They are also included in IBM's WebSphere Studio Workbench.

The article walks you through the steps for using GEF. Rather than finishing each step in its entirety, we'll use a subset of your application's model and get that working first. For example, we might initially ignore connections, or focus on just a subset of the types of graphical elements in your application.

Overview of GEF
GEF assumes that you have a model that you would like to display and edit graphically. To do this, GEF provides viewers (of the type EditPartViewer) that can be used anywhere in the Eclipse workbench. Like JFace viewers, GEF viewers are adapters on an SWT Control. But the similarity stops there. GEF viewers are based on a model-view-controller (MVC) architecture.

The controllers bridge the view and model (see Figure 1). Each controller, or EditPart as they are called here, is responsible both for mapping the model to its view, and for making changes to the model. The EditPart also observes the model, and updates the view to reflect changes in the model's state. EditParts are the objects with which the user interacts. EditParts are covered in more detail later.

Figure 1. Model-View-Controller
Model-View-Controller

GEF provides two viewer types: graphical and tree-based. Each hosts a different type of view. The graphical viewer uses figures that paint on an SWT Canvas. Figures are defined in the Draw2D plug-in, which is included as part of GEF. The TreeViewer uses an SWT Tree and TreeItems for its view.

Step 1. Bring your own model
GEF knows nothing about a model. Any model type should work, as long as it meets the properties described below.

What's in the model?
Everything is in the model. The model is the only thing that is persisted and restored. Your application should store all important data in the model. During the course of editing, undo, and redo, the model is the only thing that endures. Figures and EditParts will be garbage collected and recreated over time.

When the user interacts with EditParts, the model is not manipulated directly by the EditParts. Instead, a Command is created that encapsulates the change. Commands can be used to validate the user's interaction, and to provide undo and redo support.

Strictly speaking, Commands are also conceptually part of the model. They are not the model per se, but the means by which the model is edited. Commands are used to perform all of the user's undoable changes. Ideally, commands should only know about the model. They should avoid referencing an EditPart or figure. Similarly, a command should avoid invoking the user interface (such as a pop-up dialog) whenever possible.

A tale of two models
A straightforward GEF application is an editor for drawing diagrams. (Here, diagram means just a picture, not class diagram, etc.) A diagram can be modeled as some shapes. A shape might have properties for location, color, etc., and may be a group structure of multiple shapes. There are no surprises here, and the previous requirement is easily maintained (see Figure 2).

Figure 2. A simple model
A simple model

Another common GEF application is a UML editor, such as a class diagram editor. One important piece of information in the diagram is the (x, y) location where a class appears. Based on the previous section, you might assume that the model must describe a class as having an x and y property. Most developers want to avoid polluting their model with attributes that don't make sense. In such applications, the term "business" model can be used to refer to the base model in which the important semantic details are stored. While diagram-specific information is stored in the "view" model (which means a "view" of something in the business model; an object may be viewed multiple times in one diagram). Sometimes the split is even reflected in the workspace, where different resources might be used to persist the diagram and business model separately. There may even be several diagrams for the same business model (see Figure 3).

Figure 3. A model split into business and view models
A model split into business and view models

Whether your model is split into two parts, or even multiple resources, does not matter to GEF. The term model is used to refer to the whole application model. An object on screen may correspond to multiple objects in the model. GEF is designed to allow the developer to handle such mappings easily.

Notification strategies
Updates to the view should almost always be the result of notification from the model. Your model must provide some notification mechanism, and this mechanism must be mapped to the appropriate updates in your application. Exceptions might be read-only models, or models that cannot notify, such as a file system or a remote connection.

Notification strategies are usually either distributed (per object), or centralized (per domain). A domain notifier knows about every change to any object in the model, and broadcasts these changes to domain listeners. If your application employs this notification model, you will probably add a domain listener per viewer. When that listener receives a change, it will look up the affected EditPart(s), and then re-dispatch the change appropriately. If your application uses distributed notification, each EditPart will typically add its own listeners to whichever model objects affect it.

Step 2. Define the view
The next step is decide how you will display your model using figures from the Draw2D plug-in. Some figures can be used directly to display one of your model's objects. For example, the Label figure can be used to display an Image and String. Sometimes the desired results can be achieved by composing multiple figures, layout managers, and/or borders. Finally, you may end up writing your own figure implementations that paint in a way specific to your application.

More information about composing or implementing figures and layouts can be found in the Draw2D developers guide included with the GEF SDK.

When using Draw2D with GEF, you can make your project easier to manage, and more flexible to changing requirements, by following these guidelines:

  • Don't reinvent the wheel. You can combine the provided layout managers to render most things. Consider composing multiple figures using combinations of the toolbar layout (in vertical or horizontal orientation) and the border layout. Only as a last resort should you write your own layout manager. As a reference point, look at the palette provided in GEF. The palette is rendered using many of the standard figures and layouts from Draw2D.

  • Keep a clean separation between EditPart and figure. If your EditPart uses a composite structure of several figures, layouts, and/or borders, keep as much of that detail hidden from the EditPart. It is possible (but not a good idea) to have the EditPart build everything itself. But, doing this does not lead to a clean separation between controller and view. The EditPart has intimate knowledge of the figure structure, so reusing that structure with a similar EditPart is not possible. Also, changing the appearance or structure may lead to unexpected bugs.

    Instead, you should write your own subclass of Figure that hides the details of its structure. Then, define the minimal API on that subclass that the EditPart (the controller) uses to update the view. This practice, referred to as the separation of concerns, results in greater reuse and fewer bugs.

  • Don't reference the model or EditPart from the figure. The figure should not have access to the EditPart or model. In some situations, the EditPart may add itself as a listener to the figure, but it will only be known about as a listener, not as the EditPart. This practice of de-coupling also yields greater reuse.

  • Use a contents pane. Sometime you have a container that will contain other graphical elements, but you need decorations around the outside of the container. For example, a UML class is typically shown as a box, where the top portion is labeled with the class name and perhaps some stereotypes, and the bottom portion is reserved for attributes and methods. This can be done by composing multiple figures. The first figure is the title box for the class, while another figure is designated as the contents pane. This figure will eventually contain the figures for attributes and methods. When writing the EditPart implementation later, it is trivial to indicate that the contents pane should be used as the parent for all children elements.

Step 3. Write your EditParts, the controllers
Next we'll bridge the model and view with the controller, or EditPart. This is the step that puts the "framework" in GEF. The provided classes are abstract, so clients must actually write code. It turns out that sub-classing not only is familiar, but is probably the most flexible and straightforward way to map from model to view.

There are three base implementations provided for sub-classing. Use AbstractTreeEditPart for EditParts that appear in the tree viewer. Extend AbstractGraphicalEditPart and AbstractConnectionEditPart in graphical viewers. We will focus on graphical EditParts here. The same principles apply to use in the tree viewer.

EditPart life-cycle
Before you write your EditParts, it helps to know where they come from, and where they go when they are no longer needed. Each viewer is configured with a factory for creating EditParts. When you set the viewer's contents, you do so by providing the model object that represents the input for that viewer. The input is typically the top-most model object, from which all other objects can be traversed. The viewer then uses its factory to construct the contents EditPart for that input object. From then on, each EditPart in the viewer will populate and manage its own children (and connection) EditParts, delegating to the EditPart factory when new EditParts are needed, until the viewer is populated. As new model objects are added by the user, the EditParts in which those objects appear will respond by constructing the corresponding EditParts. Note that the view construction parallels the EditParts construction. So, after each EditPart is constructed and added to its parent EditPart, the same happens with the view, whether figures or tree items.

EditParts are thrown out as soon as the user removes the corresponding model object. If the user undoes a delete, it is a different EditPart that is recreated to represent the restored object. This is why EditParts cannot contain long-term information, and should not be referenced by commands.

Your first EditPart: The Contents EditPart
The first EditPart you write is the EditPart that corresponds to the diagram itself. This EditPart is referred to as the contents of the viewer. It corresponds to the top-most element in the model, and is parented by the viewer's root EditPart (see Figure 4). The root lays the foundation for the contents by providing various graphical layers, such as connection layers, handle layers, etc., and possibly zoom or other functionality at the viewer level. Note that the root's functions are not dependent on any model object, and that GEF provides several ready-to-use implementations for roots.

Figure 4. EditParts in a viewer
EditParts in a viewer

The content's figure is not too interesting, and is often just an empty panel that will contain the diagram's children. Its figure should be opaque and should be initialized with the layout manager that will layout the diagrams' children. It will, however, have structure. The diagram's immediate children are determined by the list of child model objects returned. Listing 1 shows a sample contents EditPart that creates an opaque figure that will position its children using the XYLayout.

Listing 1. Initial implementation for the contents EditPart

public class DiagramContentsEditPart extends AbstractGraphicalEditPart {
    protected IFigure createFigure() {
        Figure f = new Figure();
        f.setOpaque(true);
        f.setLayoutManager(new XYLayout());
        return f;
    }

    protected void createEditPolicies() {
        ...
    }

    protected List getModelChildren() {
        return ((MyModelType)getModel()).getDiagramChildren();
    }
}

To determine the items on the diagram, the method getModelChildren() is implemented. This method returns the list of child model objects, such as the nodes in the diagram. The superclass will use this list of model objects to create the corresponding EditParts. The newly created EditParts are added to the part's list of children EditParts. This in turn adds each child's figure to the diagram's figure. By default, an empty list would have been returned, which indicates no children.

More graphical EditParts
Your remaining EditParts (representing the items in the diagram) will probably have data to be displayed graphically. They may also have their own structure, such as connections or their own children. Many GEF applications depict labeled icons with connections between them. Let's assume your EditPart is going to use a Label as its figure, and that the model provides a name, icon, and connections going to and from the label. Listing 2 shows a first pass at implementing this type of EditPart.

Listing 2. Initial implementation for a "node" EditPart

public class MyNodeEditPart extends AbstractGraphicalEditPart {
    protected IFigure createFigure() {
        return new Label();
    }
    protected void createEditPolicies() {
        ...
    }
    protected List getModelSourceConnections() {
        MyModel node = (MyModel)getModel();
        return node.getOutgoingConnections();
    }
    protected List getModelTargetConnections() {
        MyModel node = (MyModel)getModel();
        return node.getIncomingConnections();
    }
    protected void refreshVisuals() {
        MyModel node = (MyModel)getModel();
        Label label = (Label)getFigure();
        label.setText(node.getName());
        label.setIcon(node.getIcon());

        Rectangle r = new Rectangle(node.x, node.y, -1, -1);
        ((GraphicalEditPart) getParent()).setLayoutConstraint(this, label, r);
    }
}

A new method, refreshVisuals(), is overridden. This method is called when it is time to update the figure using data from the model. In this case, the model's name and icon are reflected in the label. But more importantly, the label is positioned by passing its layout constraint to the parent. In the contents EditPart, we used an XY layout manager. This layout uses a Rectangle constraint to determine where to place the children figures. A width and height of "-1" indicate that the figure should be given its preferred size.

Tip #1
Figures should never placed using the setBounds(...) method. The use of layout managers like XYLayout ensures that scrollbars will be updated correctly. Also, XYLayout handles converting relative constraints to absolute locations, and it can be used to size a figure to its preferred size automatically (in other words, when the constraint's width and height are -1).

The method refreshVisuals() is called only once during the initialization of the EditPart, and is never called again. When responding to model notification, it is the responsibility of the application to call refreshVisuals() again as needed to update the figure. To improve performance, you may wish to factor out the code for each model attribute into its own method (or a single method with a "switch"). That way, when the model notifies, you run the least amount of code to refresh only what has changed.

The other interesting difference is the code for connection support. Similar to getModelChildren(), getModelSourceConnections() and getModelTargetConnections() should return the model objects representing the links between nodes. The superclass creates the corresponding EditParts when necessary, and adds them to the list of source and target connection EditParts. Note that a connection is referred to by the nodes at each end, but that its EditPart need only be created once. GEF ensures that a connection is only created once by first checking to see if it exists already in the viewer.

Making connections
Writing a connection EditPart implementation is not much different. Start by subclassing AbstractConnectionEditPart. As before, refreshVisuals() may be implemented to map attributes from the model to the figure. A connection may also have a constraint, although constraints are slightly different than before. Here, constraints are used by connection routers to bend the connection. Furthermore, a connection EditPart's figure must be a Draw2D Connection, which introduces one more requirement: connection anchors.

A connection must be anchored at both ends by a ConnectionAnchor. Therefore, you must indicate, either in the connection EditPart or in the node implementations, which anchors to use. By default, GEF assumes that the node EditParts will provide the anchors by implementing the NodeEditPart interface. One reason for this is that the choice of anchor is dependent on the figure being used by the nodes at each end. The connection EditPart shouldn't know anything about the figures being used by the nodes. Another reason is that when the user is creating a connection, the connection EditPart doesn't exist, so the node must be able to show feedback on its own. Continuing with Listing 2, we add the necessary anchor support in Listing 3.

Listing 3. Adding anchor support to the "node" EditPart

public class MyNodeEditPart
    extends AbstractGraphicalEditPart
    implements NodeEditPart
{
    ...
    public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
        return new ChopboxAnchor(getFigure());
    }
    public ConnectionAnchor getSourceConnectionAnchor(Request request) {
        return new ChopboxAnchor(getFigure());
    }
    public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {
        return new ChopboxAnchor(getFigure());
    }
    public ConnectionAnchor getTargetConnectionAnchor(Request request) {
        return new ChopboxAnchor(getFigure());
    }

    ...
}

Tip #2
Don't forget to actually implement the NodeEditPart interface. Otherwise, your methods will never get called.

The methods that take a connection are the ones used when setting the anchors on an existing connection EditPart. The other two methods take a Request. These methods are used during editing when the user is creating a new connection. For this example, a chopbox anchor is returned in all cases. A chopbox anchor just finds the point at which a line intersects the bounding box of the node's figure.

Implementing the connection EditPart is relatively straightforward. Note that it isn't even necessary to create the figure, since the default creation of PolylineConnection is suitable for most uses (see Listing 4).

Listing 4. Initial Connection EditPart implementation

public class MyConnectionEditPart extends AbstractConnectionEditPart {
    protected void createEditPolicies() {
        ...
    }

    protected void refreshVisuals() {
        PolylineConnection figure = (PolylineConnection)getFigure();
        MyConnection connx = (MyConnection)getModel();
        figure.setForegroundColor(MagicHelper.getConnectionColor(connx));
        figure.setRoutingConstraint(MagicHelper.getConnectionBendpoints(connx));
    }
}

Tip #3
Most importantly, know when to use ConnectionEditParts, and when not to use them. A connection EditPart is used when there is something that the user can select and interact with. It probably has a direct correlation to an object in the model, and can generally be deleted by itself.

If you just have a node or container that needs to draw a line, just draw the line in the figure's paint method, or compose a figure that contains a Polyline figure.

A connection must have a source and a target at all times. If you need a connection that can exist without source or target, then you are better off extending just AbstractGraphicalEditPart, and using a connection figure.

Listening to the model
After creation, an EditPart should start listening for change notifications from the model. Since GEF is model neutral, all applications must add their own listeners and handle the resulting notifications. When notification is received, the handler may call one of the provided methods to force a refresh. For example, if a child has been deleted, calling refreshChildren() will cause the corresponding EditPart and its figure to be removed. For simple attribute changes, refreshVisuals() can be used. As was mentioned previously, this method may be factored into multiple parts to avoid needlessly updating every displayed attribute.

Adding listeners and forgetting to remove them is a frequent cause for memory leaks. For this reason, the point where you add and remove listeners is clearly spelled out in the API. Your EditParts must extend activate() to add any listeners that must later be removed. Remove those same listeners by extending deactivate(). Listing 5 shows the additions to the node EditPart implementation for model notification.

Listing 5. Listening for model changes in a "node" EditPart

public class MyNodeEditPart
extends AbstractGraphicalEditPart
    implements NodeEditPart, ModelListener
{
    ...

    public void activate() {
        super.activate();
        ((MyModel)getModel()).addModelListener(this);
    }

    public void deactivate() {
        ((MyModel)getModel()).removeModelListener(this);
        super.deactivate();
    }

    public void modelChanged(ModelEvent event) {
        if (event.getChange().equals("outgoingConnections"))
            refreshSourceConnections();
        else if (event.getChange().equals("incomingConnections"))
            refreshTargetConnections();
        else if (event.getChange().equals("icon")
          || event.getChange().equals("name"))
            refreshVisuals();
    }

    ...
}

Editing the model
So far we have covered how EditParts are created, how they create their visuals, and how they update themselves when the model changes. In addition to this, EditParts are also the primary players in making changes to the model. This happens when a request for a command is sent to the EditPart. Requests are also used to ask EditParts to show feedback such as during a mouse drag. The EditPart either supports, prevents, or ignores a given request. The types of requests that are supported or prevented determines the EditPart's behavior.

The focus so far has been on mapping the model's structure and properties into the view. It turns out, this is basically all you do in the EditPart class itself. Its behavior is determined by a set of pluggable helpers called EditPolicies. In the provided examples we have ignored the method createEditPolicies(). Once you implement this method, you are pretty much done with your EditPart. Of course, you'll still need to provide edit policies that know how to modify your application's model.

Since editing behavior is pluggable, when developing your various EditPart implementations, you can create a class hierarchy based around the task of mapping the model to the view and handling model updates.

Step 4. Bring it all together
At this point, you have all the pieces needed to graphically display your model. For final assembly, we will be using an IEditorPart. However, GEF's viewers can also be used in views, dialogs, or just about anywhere you can place a control. For this step, you must have your UI plug-in, which will define the editor and file extension for the resources being opened. Your model may be defined in the same or in a separate plug-in. You will also need a pre-populated model, since there is no editing functionality yet.

There are several ways to provide sample model data. For the purposes of this example, we will create the model in code when the editor is opened, ignoring the actual contents of the file. To do this, we'll assume the existence of a test factory. Alternatively, you can create an example wizard that pre-populates the resource with data (normal wizards would just create an empty diagram). Finally, you may be able to write the document's contents by hand with a text editor.

Now that you have a sample model, let's create the editor part that will display the model. A quick way to get started is to subclass or copy GEF's GraphicalEditor. This class creates an instance of ScrollingGraphicalViewer, and constructs a canvas to serve as the editor's control. It is a convenience class provided to help you get started with GEF; a properly behaved Eclipse editor has many other things to consider, such as pessimistic team environments, the resource being deleted or moved, etc.

Listing 6 shows a sample editor implementation. There are several abstract methods that must be implemented. For the scope of this article, we will be ignoring model persistence and markers. You must do two things to get your diagram to appear in the graphical viewer. First, configure the viewer with your own EditPart factory to construct the EditParts from Step 3. Then, pass in the diagram model object to the viewer.

Listing 6. Implementing your Editor Part

public class MyEditor extends GraphicalEditor {
    public MyEditor() {
        setEditDomain(new DefaultEditDomain(this));
    }

    protected void configureGraphicalViewer() {
        super.configureGraphicalViewer(); //Sets the viewer's background to System "white"
        getGraphicalViewer().setEditPartFactory(new MyGraphicalEditpartFactory());
    }

    protected void initializeGraphicalViewer() {
        getGraphicalViewer().setContents(MagicHelper.constructSampleDiagram());
    }
    public void doSave(IProgressMonitor monitor) {
        ...
    }
    public void doSaveAs() {
        ...
    }
    public void gotoMarker(IMarker marker) {
        ...
    }
    public boolean isDirty() {
        ...
    }
    public boolean isSaveAsAllowed() {
        ...
    }
}

Next steps
We've gone from having just a model, to displaying that model in a graphical editor. But we have only laid the foundation. We briefly mentioned edit policies. You can get more information on edit policies by reading the developer documentation provided with the GEF SDK. There is also an example available from the GEF home page (see the Resources at the end of the article) that demonstrates the use of each edit policy type.

GEF also provides a palette. The palette displays a set of tools for creating objects in the diagram. The user may activate tools or drag items directly from the palette using native drag and drop. User customization of content is also supported.

Several JFace actions are also available in GEF. Things like undo, align, delete, etc., can be used by your application in menus, toolbars, or context-menus.

Finally, your application should support both the outline view and properties view. The outline view is used for both navigational and limited editing purposes. GEF's TreeViewer and/or the overview window may be used here. The property sheet allows the user to see and edit the detailed properties of whatever is currently selected.

To show selection and allow the user to make changes, you must add edit policies to your EditParts. See the GEF home page example, and also refer to the developer documentation included with the GEF SDK.

Detail on additional features provided by GEF and the Eclipse workbench are beyond the scope of this article, but you may be interested in knowing a bit about them:

  • Palette. A palette of tools is the de facto means for creating new objects in a diagram. GEF includes a rich palette, which supports drag-and-drop, multiple drawers, and layout settings, and even user customization of content, if the application desires.
  • Action bars. Editors and Views may contribute Actions to toolbars, menus, and context menus. GEF provides several reusable action implementations, but it is up to the application to display them somewhere.
  • Property sheet. The property sheet can be used to display details of properties of the selected items. GEF allows you to add property sheet support either on the EditPart or in the model.
  • Outline. The outline view is often used to show a structural representation of the diagram, but it can be used for anything in general. GEF's TreeViewer is often used in the outline view.

Resources

About the author
Randy Hudson is a software engineer for IBM at Research Triangle Park, North Carolina. As the technical lead for the Graphical Editing Framework (GEF), he has helped transition the once internal project to an open source technology. His current work focuses on usability, graphical editing, graph layout, and edge routing. You can contact Randy at buchu at nc.rr.com.


95 KBe-mail it!

What do you think of this document?
Killer! (5) Good stuff (4) So-so; not bad (3) Needs work (2) Lame! (1)

Comments?



developerWorks > Open source projects | Java technology
developerWorks
  About IBM  |  Privacy  |  Terms of use  |  Contact