banner



Database Schema Design Tool Eclipse

Copyright © 2004 Realsolve Solutions Ltd.

Building a Database Schema Diagram Editor
with GEF

Summary
GEF is a very powerful framework for visually creating and editing models. With a small initial investment, even the relative Eclipse novice can be quickly up and running, building applications with graphical editing capabilities. To illustrate, this article uses a relational database schema diagram editor with a deliberately simplified underlying model, but with enough bells and whistles to show some of the interesting features of GEF at work.

Phil Zoio, Realsolve Solutions Ltd.
September 27, 2004


Introduction

Having graphical editing capabilities can be a real asset, if not an essential feature, for many tools and applications. Examples are not hard to think of: UML tools, GUI builders, in fact, any application which comprises a dynamic model which can be visualized. With GEF, Eclipse developers have at their disposal a framework which can really simplify development of graphical editors. This article uses a simple but non-trivial example to show how a GEF application works - and what you need to do to get it to perform its little miracles.

The screenshot below shows what our example editor looks like. The edit area uses a "flyout" palette which contains some very basic entries and can be minimized to increase the editable screen area. On the right side is a scrollable graphical viewer containing the tables and their relationships.

Download and unzip the example plug-in schemaeditor.zip into your eclipse/ directory, then create a new diagram by launching the wizard from the File menu: File -> New -> Example ... -> GEF (Graphical Editing Framework) -> Schema Diagram Editor.

At the heart of GEF is the Model-View-Controller pattern, discussed in Randy Hudson's introductory tutorial How to Get Started with the GEF, and also providing a focus for much of this article.

The Model

The starting point for any GEF application is the model. This is what needs to be displayed, edited and persisted. Our somewhat oversimplified model contains the following classes:

  • Table: represents a relational database table. The only attribute that the table holds itself is the name
  • Column: represents a table column. Here we are interested in the column name and the type of data, which itself can either be VARCHAR or INTEGER
  • Relationship: represents a primary key/foreign key relationship between two tables. The foreign key table we denote the source of the relationship, while the primary key table is the target. Note that our model applies a relationship directly between two tables, rather than between foreign and primary key fields in the respective tables, as we would in the real world
  • Schema: simply represents all the tables we plan to group together (and ultimately show on the same diagram)
Our model is extremely simple, but does at least include the two key forms of relationship in a typical GEF model:
  • The parent-child relationship that exists between schemas and tables, and between tables and columns
  • Connections between different nodes. In our example application, the connections are in the form of primary key/foreign key relationships, with the nodes being the tables

Of course, we need to decide what we want our editor to be able to do with the model. Here, we want to be able to:

  • lay our tables nicely on the diagram. The diagram must scale to accommodate any growth in our model, and should be readable
  • add new tables to our schema, and new columns to our table, using either drag and drop or point and click
  • directly edit the names of the tables as well as both the names and types of our columns. In both cases, we want validation to tell us when we are typing in nonsense
  • use drag and drop to set up relationships between tables, as well as to change these relationships
  • use drag and drop to move column definitions from one table to another, or to reorder columns within tables
  • be able to delete tables, columns and relationships by hitting the delete key
  • have a choice between manually laying out our diagram, and having this done automatically. When using manual layout, we want to be able to shift our tables around using drag and drop
There are of course many more things we would like to be able to do with our editor, but we have enough here to be able to test out many of the most commonly used GEF features.

The View

Figures

The display component in both GEF and draw2d is built around the draw2d IFigure interface. Figures in draw2d are lightweight objects which can be nested to create a complex graphical representation. The view is created by rendering and laying out the set of figures which reflect the model. In a typical GEF application, you would normally create a set of customized IFigure implementations, each subclassing Figure.

If you're unfamiliar with draw2d and Figures, take a look at Daniel Lee's article on Display a UML Diagram using Draw2D,

In our application we have the following figures:

  • EditableLabel: a subclass of the draw2d Label class which itself subclasses Figure. We need this for the column and table names
  • ColumnsFigure: a container for all the column labels
  • TableFigure: contains an EditableLabel for the table name, as well as a ColumnsFigure for the column names
  • SchemaFigure: a container for all the TableFigures in the schema

We haven't provided any custom figures to represent connections - we simply use the draw2d PolylineConnection class, which is a just a line with zero or more kinks or bend points.

Because the table names as well as the number of columns and their names are likely to change during the lifetime of a TableFigure instance, we want our ColumnsFigure and TableFigure to be resizable. A key role in allowing this to happen is played by layout managers, another important part of the draw2d framework.

Layout Management

GEF provides a layout management framework which is distinct from the Swing and Eclipse SWT layout managers: its job is specifically to handle layout of the child figures of draw2d IFigure instances. Your job as an application developer is to decide which layout manager to use for each figure containing child figures.

Broadly speaking, there are three types of layout managers:

  • Structured layout managers, such as FlowLayout and ToolbarLayout, which lay out child figures according to their order by arranging them vertically or horizontally
  • Constraint-based layout managers, such as the XYLayout and the DelegatingLayout. Here the application itself participates directly in the placement of figures by setting a constraint Object for each child figure. In the case of the XYLayout, this object is a Rectangle with specified location and size
  • Layout using geometry computation algorithms. Here layout is determined by applying a series of rather complex algorithms to calculate the "best" layout for child figures. The algorithms take a specially constructed data structure as input and deliver as their output a solution to geometrical problems such as node placement and routing of paths. The algorithms provided by GEF are in the classes DirectedGraphLayout and CompoundDirectedGraphLayout

The GEF developer needs to understand which layout managers can be best applied in which situation. Structured layout managers are suitable when there is a well defined parent-child relationship between the containing figure and its children and the children are not related to each other in arbitrary ways. In our example application, TableFigure uses a ToolbarLayout to place its children (simply stacking them vertically). The ColumnsFigure does the same with its child Label objects, but uses the FlowLayout for this purpose.

This kind of arrangement of course does not work with SchemaFigure - any of its child TableFigures may be related to any other via a primary key/foreign key relationship, so we cannot simply stack the table figures next to each other or side by side. For SchemaFigure we need to choose between either a constraint-based layout manager or a graph layout manager. In our example application we use both. Users can switch between manual layout, which involves dragging table figures to their desired locations, and automatic placement of figures using geometry computation algorithms. How this is done is beyond the scope of this article, although interested readers can examine the DelegatingLayoutManager class in the example application source code.

Open a schema diagram editor and make some changes, switching between manual and automatic layout using the icon.

The Controller

We only really move into GEF territory proper when we start talking about the controller in the MVC trilogy. GEF provides an abstraction that prevents the model from having to know about the figures, and vice versa. At the centre of this architecture is the EditPart interface.

EditParts

The first thing to know is that typically every separately editable part of the model will need to be associated with an EditPart instance. This means that there will usually be a close to one-for-one mapping between classes in the model hierarchy and classes in the EditPart hierarchy. In most cases, an EditPart is also a GraphicalEditPart, which means that as well as managing a model component, it also has an associated view component. Because the model and view are completely decoupled, all coordination between the model and the view must be managed by the EditPart. This coordination can be divided into two separate areas of activity:

  1. Acting as a listener to changes in the model so that these can be propagated to the view, by calling layout related methods. We discuss this in detail in the section Updating an Repainting the Display
  2. Providing a means by which user interaction can be interpreted and propagated to changes in the model. Central to this are EditPolicies, discussed in the section EditPolicies and Roles
  3. Managing what are known as direct edits, where the user types text directly into an editable control
In our example application we have the following EditPart implementations
  • SchemaDiagramPart: represents a Schema instance and associated SchemaFigure
  • TablePart: represents a Table and manages the TableFigure and child view components
  • ColumnPart: enables editing functionality for the column label
  • RelationshipPart: represents a primary key/foreign key relationship. In the same way that Relationship in the model is associated with two Table instances, a RelationshipPart is associated with two TableParts

When an instance of any of these classes is created, it is automatically associated with a part of the model. This is a build-in feature of the framework. As part of our editor, we have to provide an EditPartFactory implementation. Ours looks like this:

public class SchemaEditPartFactory implements EditPartFactory {     public EditPart createEditPart(EditPart context, Object model)     {         EditPart part = null;         if (model instanceof Schema)             part = new SchemaDiagramPart();         else if (model instanceof Table)             part = new TablePart();         else if (model instanceof Relationship)             part = new RelationshipPart();         else if (model instanceof Column)             part = new ColumnPart();         part.setModel(model);         return part;     } }

SchemaDiagramPart, TablePart and ColumnPart all extend AbstractGraphicalEditPart and implement GraphicalEditPart. In addition, TablePart can be a node in a primary/foreign key relationship, so it has to implement NodeEditPart. Finally, RelationshipPart represents the connection part of the relationship, so it extends AbstractConnectionEditPart.

SchemaDiagramPart's job is primarily managing the layout of the tables. ColumnPart's role is relatively limited - it just needs to handle editing of the label displaying name and type information.

Of the four of these, TablePart has the most to do. In GEF, most of the work that is done to manage relationships is done by NodeEditPart, and not ConnectionEditPart. Because we sometimes need to rename tables, TablePart also has to manage editing of the label that displays its name. We will spend more of our time focusing on TablePart.

In a GEF application, there are a number of tasks EditPart subclasses must fulfill:
  1. Provide a figure instance to be associated with the EditPart. In the case of TablePart, we simply return a new TableFigure instance with a name label:

                              protected IFigure createFigure()   {       Table table = getTable();       EditableLabel label = new EditableLabel(table.getName());       TableFigure tableFigure = new TableFigure(label);       return tableFigure;   }          
  2. EditParts which represent parent objects in parent-child relationships need to override getModelChildren(). In the case of TablePart, our implementation of this method simply returns the Column objects it contains:

                              protected List getModelChildren()   {       return getTable().getColumns();   }          
    Note that the AbstractEditPart implements a parallel method getChildren(), which returns the EditPart collection representing the model children. In the case of TablePart, getChildren() returns a list of ColumnPart objects. We know this because our implementation of EditPartFactory associates Column model instances with instances of ColumnPart. The EditPart List returned by getChildren() always needs to be kept in sync with the getModelChildren(). In the Section Synchronizing EditPart Relationships with Model Changes we describe how this happens
  3. If the parent EditPart's figure is not the direct parent of the child EditPart's figure, you will need to override AbstractGraphicalEditPart.getContentPane(). The content pane is the containing figure into which GEF adds figures created by child EditParts, which is by default the figure returned by the EditPart's createFigure() method.

    In our example application the column labels are not contained within a TableFigure but within its ColumnsFigure child. Our implementation of getContentPane() in TablePart reflects this:

    public IFigure getContentPane() {     TableFigure figure = (TableFigure) getFigure();     return figure.getColumnsFigure(); }

    Do not add and remove child figures by overriding AbstractGraphicalEditPart.addChildVisual() and AbstractGraphicalEditPart.removeChildVisual(). Override getContentPane() instead.

  4. EditParts which represent nodes (model objects which may participate in connections) must also implement a number of additional methods defined in the interface NodeEditPart
    • protected List getModelSourceConnections(): this returns all the connection model objects for which the node model object is the source. In our example application, we have identified foreign keys as the source of a primary key/foreign key relationship. TablePart's implementation contains just a single line of code:

      return getTable().getForeignKeyRelationships();

      This method simply returns the Relationship objects for which the current TablePart's Table is the foreign key. Once again, there is a parallel method getSourceConnections(), which returns the List of RelationshipParts associated with these relationships. We also consider in the Section Synchronizing EditPart Relationships with Model Changes how the ConnectionEditPart list returned by getSourceConnections() stays in sync with the Relationship list returned by getModelSourceConnections()

    • protected List getModelTargetConnections(): this works identically to getModelSourceConnections(), except that it returns the Relationship objects for which the current TablePart's Table is the primary key

    • the node GraphicalEditPart must also provide implementations of the NodeEditPart getSourceConnectionAnchor() and getTargetConnectionAnchor() methods. In each case, these methods return objects which represent the points to which connections between nodes can be attached

  5. Provide an implementation for createEditPolicies(), during which EditPolicy implementations are associated with specific editing roles. The EditPolicy and its associated roles, Request and Command objects are a fundamental part of GEF which we discuss in the next sections

Requests

We begin with requests because these are really the starting point of the editing process that GEF application developer works with. In fact, the real magic in GEF is being able to interpret user interactions and transform these into requests, which the application can work with in an object-oriented fashion. For example, when we drag from the "New Column" palette button onto an existing table on the diagram, we are of course trying to add a new column to the table. As users interact with the application, GEF's behind-the-scenes work produces Request objects. In the create column example, GEF produces a CreateRequest, which contains the following important information:

  • the instance of the new model object that has been created (but probably not yet configured and definitely not added to the rest of the model)
  • The EditPart object which is hosting this request. In our case this will be an instance of TablePart
Different types of user interactions will produce different Request types - these are well covered in the GEF API and platform documentation. These request objects neatly encapsulate the information the application needs to transform user interaction into changes to the model. We can take a look at how this is done once we have looked at Commands and EditPolicies, which we cover in the next section.

EditPolicies and Roles

An EditPolicy is really just an extension of an EditPart, in the sense that certain editing related tasks are passed on from the EditPart to its EditPolicy delegates. EditPart implementations would rapidly become bloated if they had to take on everything that EditPolicies do. To understand what an EditPolicy is and what it does, lets start by looking at the createEditPolicies() method in TablePart:

        protected void createEditPolicies() {     installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new TableNodeEditPolicy());     installEditPolicy(EditPolicy.LAYOUT_ROLE, new TableLayoutEditPolicy());     installEditPolicy(EditPolicy.CONTAINER_ROLE, new TableContainerEditPolicy());     installEditPolicy(EditPolicy.COMPONENT_ROLE, new TableEditPolicy());     installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new TableDirectEditPolicy()); }      

The purpose of this method is simply to decorate the TablePart with editing functionality. Each call to installEditPolicy() in the above method registers an EditPolicy with the EditPart. The key constant used in each of these calls is the name of the role used. For example, EditPolicy.CONTAINER_ROLE is simply the string "ContainerEditPolicy". The container role is relevant for TablePart because we know that tables contain columns, and one of our application's requirements is to create new columns and add these to existing tables.

The use of a particular role name in the installEditPolicy() call is really just a convention - the framework does not attach any behavior to a particular choice of role name. What distinguishes an EditPolicy implementation (and its corresponding role) is the type of requests it understands. Most of the abstract EditPolicy classes provide an implementation of the getCommand(Request request) method.

In ContainerEditPolicy we find the following:

        public Command getCommand(Request request) {   if (REQ_CREATE.equals(request.getType())) 	return getCreateCommand((CreateRequest)request);   if (REQ_ADD.equals(request.getType())) 	return getAddCommand((GroupRequest)request);   if (REQ_CLONE.equals(request.getType())) 	return getCloneCommand((ChangeBoundsRequest)request);   if (REQ_ORPHAN_CHILDREN.equals(request.getType())) 	return getOrphanChildrenCommand((GroupRequest)request);   return null; }      

Here getCommand() simply uses the request type to determine which getXXXCommand() method to call. In ContainerEditPolicy, getCreateCommand() is abstract - we must provide an implementation in order to use the base ContainerEditPolicy functionality.

Here is our implementation of TableContainerEditPolicy:

        public class TableContainerEditPolicy extends ContainerEditPolicy {     protected Command getCreateCommand(CreateRequest request)     {                    Object newObject = request.getNewObject();         if (!(newObject instanceof Column))         {             return null;         }         Column column = (Column) newObject;                    TablePart tablePart = (TablePart) getHost();         Table table = tablePart.getTable();                    ColumnCreateCommand command = new ColumnCreateCommand();         command.setTable(table);         command.setColumn(column);         return command;     } }      

In most cases, our EditPolicy implementations simply amount to using a Request object to generate a Command. Our getCreateCommand() method

Our TablePart createEditPolicies() implementation uses one of our customized EditPolicy implementations for each invocation of installEditPolicy(). Each of our EditPolicy implementations subclasses a GEF-provided abstract EditPolicy for a different role. For example, TableEditPolicy extends ComponentEditPolicy to fulfill the EditPolicy.COMPONENT_ROLE. It does so by implementing the createDeleteCommand(GroupRequest request) to handle requests of type REQ_DELETE.

The GEF platform documentation provides a lot more detail on the types of roles and requests and how and when they can be used, so we won't cover them in any more detail here.

Commands

Command is GEF's abstract base class whose function is simply to encapsulate our application's response to a request. Key methods included in the Command class are the following:

  • execute(): Command provides a no-op implementation. As the name suggests, this contains the code to apply any change to the model that the Command object encapsulates
  • undo(): used to reverse the effect of execute(). Here Command also provides a no-op implementation
  • redo(): used redo a command execution. The Command implementation simply calls execute(), which should usually be adequate
  • canExecute(): whether execute() can be executed. The subclass can implement this to specify the conditions under which the command can be executed
  • canUndo(): whether undo() can be executed. The Command implementation simply returns true, which subclasses can override
  • canRedo(): whether redo() can be executed. The Command implementation here also simply returns true

Any non-trivial Command subclass would need to implement execute(). Implementation of undo() would be recommended in most cases. The other methods are optional and would only be overridden as required.

Lets take a look at our rather straightforward ColumnCreateCommand implementation:

        public class ColumnCreateCommand extends Command {     private Column column;     private Table table;      public void setColumn(Column column)     {         this.column = column;                    this.column.setName("COLUMN " + (table.getColumns().size() + 1));         this.column.setType(Column.VARCHAR);     }      public void setTable(Table table)     {         this.table = table;     }      public void execute()     {                    table.addColumn(column);     }      public void undo()     {                    table.removeColumn(column);     } }      
Much of the class is self-explanatory. We have setter methods to populate the Command object with the newly-created Column as well as the target container Table. We arbitrarily provide a name and type for the Column , which the user can later change. We can also see that execute() simply adds the Column object to the Table, and undo() simply reverses that change.

The use of Commands has two key advantages over using EditPolicies directly to effect model changes

  • Commands are more elegant and in line with OO best practice
  • The Command framework has built-in support for undo and redo functionality

The Command implementation is closely tied to the model, and should be cleanly separated from GEF-specific components. It should not contain any references to EditParts or EditPolicies. Observing this rule preserves the clean separation between commands and the UI logic, helping to keep code more maintainable and bug-free.

Propagating Model Changes

Once we've changed the model, our GEF editor needs to propagate these changes to the UI. Our model, view and controller need to work together to achieve this.

So far, we have discussed the GraphicalEditPart's responsibility to provide a figure to represent the part of the model it is managing. To participate in a fully functional graphical editor, it needs to do more:

  • It needs to act as a listener for changes in the model. The model itself needs to fire event notifications which the EditPart can receive
  • It needs to maintain its own child and connection relationships with other EditParts, keeping these in sync with changes to the model
  • It needs to update the figures that it is managing, and their layouts, in line with model changes
We discuss each of these in turn.

Sending and Receiving Event Notifications

The requirements imposed on our model implementation are that

  1. it exposes a mechanism by which listeners can register interest in event notifications, and
  2. it actually fires these event notifications at the appropriate times!
In our example application we want all our model objects to use a common framework, so we satisfy the first requirement by allowing all our model classes to extend PropertyAwareObject, which looks like this:
public abstract class PropertyAwareObject implements Serializable {                    public static final String CHILD = "CHILD";     ... other String constants representing the other types of model changes                    protected transient PropertyChangeSupport listeners = new PropertyChangeSupport(this);      protected PropertyAwareObject()     {     }      public void addPropertyChangeListener(PropertyChangeListener l)     {                    listeners.addPropertyChangeListener(l);     }      public void removePropertyChangeListener(PropertyChangeListener l)     {                    listeners.removePropertyChangeListener(l);     }        protected void firePropertyChange(String prop, Object old, Object newValue)     {                    listeners.firePropertyChange(prop, old, newValue);     }          ... }
Our abstract model base class contains a few String constants representing the types of model changes it knows about. It uses the java.beans.PropertyChangeSupport to provide the "plumbing" for the event handling. It also exposes methods and which observers can use to register and deregister their interest in model changes. Finally, it includes a firePropertyChange() method which subclasses can use to trigger property events. In our example of adding a column to a table, we see a good example in Table:
public void addColumn(Column column) {     columns.add(column);     firePropertyChange(CHILD, null, column); }
With this mechanism available, we now need to take advantage in our EditPart listeners. Once again, we address the issue by providing a common base class for our GraphicalEditParts to extend, shown below:
public abstract class PropertyAwarePart  		extends AbstractGraphicalEditPart  		implements PropertyChangeListener {     public void activate()     {         super.activate();         PropertyAwareObject propertyAwareObject = (PropertyAwareObject) getModel();                    propertyAwareObject.addPropertyChangeListener(this);     }      public void deactivate()     {         super.deactivate();         PropertyAwareObject propertyAwareObject = (PropertyAwareObject) getModel();                    propertyAwareObject.removePropertyChangeListener(this);     }      public void propertyChange(PropertyChangeEvent evt)     {                    //handle property change event          ...        } }
The GEF API documentation recommends the use of activate() and deactivate() to register or deregister model listeners. This is what we do here. After casting our model object to PropertyAwareObject, we add our EditPart as a listener in , and allow it to be removed on deactivation in . Once the EditPart is activated, any event notifications fired from our model will result in an invocation of propertyChange() . Our propertyChange() implementation in PropertyAwarePart in turn delegates its response to other methods, which can be overridden by EditPart subclasses to customize reactions to specific changes in the model.

Synchronizing EditPart Relationships with Model Changes

As we mentioned previously, the first thing the EditPart implementation needs to do in response to a model change is to ensure that its relationship hierarchy is in sync with that of the model. GEF provides a quick and easy solution in the form of three methods in the EditPart hierarchy. Before discussing a more performant approach that many applications will demand, we'll take a look at these methods.

  • refreshChildren(): when an EditPart represents a model object with children, this method may need to be called. Our example of adding a column to a table is a good one. The same applies for removing a column from a table. If we moved a column from one table to another, refreshChildren() would need to be called for both corresponding TableParts. The base implementation of this method not only synchronizes your model and EditPart hierarchies - it also adds or removes visual components as required by calling the AbstractGraphicalEditPart addChildVisual() and removeChildVisual() methods
  • refreshSourceConnections(): this applies to any model change where the source of a connection is added, removed or reassigned. For example, if we added or deleted a primary/foreign key relationship, this method would need to be called
  • refreshTargetConnections(): this only applies to a model change where the target of a connection is added, removed or reassigned. It would be needed for any change affecting the primary key of a relationship between tables

Returning to our example of adding a column to a table, our implementation of PropertyAwarePart.propertyChange() can be reduced to the following:

        public void propertyChange(PropertyChangeEvent evt) {     String property = evt.getPropertyName();      if (PropertyAwareObject.CHILD.equals(property))     {                    refreshChildren();                    refreshVisuals();     }     ... handle other types of property changes here  }      
To resynchronize the EditPart hierarchy, we simply call refreshChildren() . To update the display, we then call refreshVisuals() . We discuss the mechanics and rationale for in the next section.

Using the methods refreshChildren(), refreshSourceConnections() and refreshSourceConnections() can help you get your application working quickly, but if we want our application to run efficiently, we need to be more selective in the methods we use. For example, to add or remove a child, we can use the EditPart methods addChild(EditPart, int) and removeChild(EditPart). Our revised handleChildChange(PropertyChangeEvent)below is a better performing replacement for refreshChildren() which uses these methods:

        protected void handleChildChange(PropertyChangeEvent evt) {     Object newValue = evt.getNewValue();     Object oldValue = evt.getOldValue();      if (newValue != null)     {         //add new child                    EditPart editPart = createChild(newValue);                    int modelIndex = getModelChildren().indexOf(newValue);                    addChild(editPart, modelIndex);     }     else     {         //remove an existing child         List children = getChildren();          EditPart partToRemove = null;         for (Iterator iter = children.iterator(); iter.hasNext();)         {             EditPart part = (EditPart) iter.next();             if (part.getModel() == oldValue)             {                    partToRemove = part;                 break;             }         }         if (partToRemove != null)                    removeChild(partToRemove);     } }      

When adding our child, we need to call createChild() to get a new EditPart for the model child. We then find the index of the model child in the containing List , and add our new child EditPart using this index . When removing a child, we iterate through the existing children EditParts until we find the one representing the removed model child . We then remove this EditPart .

Clearly, there is more work here than in simply calling refreshChildren(): but for large models where performance is critical, this effort will be worth it.

Interested readers can examine handleInputChange(PropertyChangeEvent) and handleOuputChange(PropertyChangeEvent) in PropertyAwarePart for similar alternatives to refreshSourceConnections() and refreshTargetConnections() when updating relationships.

Updating and Repainting the Display

Consider our example of adding a column to a table. In draw2d terms, this is represented by adding an EditableLabel into a ColumnsFigure instance, which is itself contained within a TableFigure. Both the ColumnsFigure and the TableFigure both need to enlarge - the result otherwise is ugly (take my word for it!).

A few things need to happen:

  1. The cached information held by the layout managers for the TableFigure and ColumnsFigure, which includes minimum size and preferred size for the child figures, needs to be thrown away
  2. The SchemaFigure's layout manager needs to update any cached constraint information it is holding for the TableFigure
  3. The bounds of both the TableFigure and the ColumnsFigure need to change to reflect addition of the column
  4. Any area affected by the change needs to be repainted

In fact, all we need to achieve this is in our implementation of refreshVisuals() in TablePart:

        protected void refreshVisuals() {     TableFigure tableFigure = (TableFigure) getFigure();                    Point location = tableFigure.getLocation();     SchemaDiagramPart parent = (SchemaDiagramPart) getParent();                    Rectangle constraint = new Rectangle(location.x, location.y, -1, -1);                    parent.setLayoutConstraint(this, tableFigure, constraint); }      
We get the location of our figure , use this to provide a new Rectangle constraint object . By setting the width and height to -1, we ensure that the preferred width and height are calculated automatically. We then pass on the constraint to the parent EditPart's layout manager .

That's all there is to it. But how do we know that the preferred size calculation for the TableFigure or ColumnFigure won't be using some stale cached value? If you're interested in the answers to questions like this, read the sidebar below.

Sidebar: Invalidating and Updating Figures

How does GEF know when to rearrange and resize figures, and which parts of the screen to repaint? The key is in the methods in the IFigure interface and in the behavior of the update manager:

  • invalidate(): each Figure instance has a valid flag which can be set to true or false. When invalidate() is called:

    1. the valid flag for the figure is set to false (the figure becomes invalid)
    2. invalidate() is called on the Figure's layout manager. Here, any cached preferred size information that the layout manager holds for the figure is thrown away
    The importance of the invalid state will become more clear when we discuss validate().
  • revalidate(): this method is used to invalidate() a figure and its parent chain. When called on a figure, this figure invalidates itself and then calls its parent's revalidate() method. The hierarchy's root figure (one with no parent) is finally placed on the update manager's queue of invalid root figures, that is, figures that need to be validated.

    revalidate() is called automatically when changes to figures occur which are likely to affect the bounds of parent figures. Its role can be clearly seen in its usages in the Figure and Label classes draw2d package, shown below:

    In our example of adding a column label to a table, revalidate() is automatically called when the new column label is added to the ColumnsFigure instance using the IFigure.addFigure(IFigure, Object, int) method. This is why correct resizing of the table occurs without having to invalidate any figures in our example's code.

    If no method is called which itself automatically invokes revalidate(), you may need to invoke this method yourself in your application code to correctly update the display.

  • repaint(): in the same way that revalidate() queues a figure with the update manager for validating, this method queues the figure's bounds as a dirty region for repainting. Like revalidate(), this method is automatically called in many places in draw2d, such as when the size of a figure changes. You are most likely to need to call this method in your application code if implementing custom subclasses of Figure.

  • validate(): this finishes the job that revalidate() started. The update manager calls validate() on each of the invalid root figures on the queue. During the validate() execution the following happens:

    1. the figure's valid flag is set back to true
    2. layout takes place - if the figure has a layout manager then its layout() method is called
    3. the figure then validates each of its invalid children

    The value of revalidate() is in helping to ensure that only figures that need to participate in the validate and layout process can be correctly flagged as invalid before this process begins.

    After validating its invalid root figures, the update manager will repaint the enclosing rectangle for regions marked as dirty via the repaint() method.

Conclusion

We've covered quite a lot of ground in this article. Most significantly, we've talked about how you can use the basic building blocks of a GEF application to easily build an application which adheres to a clean MVC design. With the exception of the direct editing functionality, most of the other types of editing operations work in a very similar way to the simple column adding example presented. Of course, all of the building blocks need to be put together in the context of an Eclipse editor. Space limitations preclude any discussion of these topics, but interested readers can peruse the source code, as well as that of the official GEF examples, to see how this can be done.

For more information on GEF, look at the Eclipse platform documentation, available via Eclipse online help if you download and install the GEF SDK. How to Get Started with the GEF gives a good introduction to GEF basics. Display a UML Diagram using Draw2D is a good starting point for those unfamiliar with Eclipse draw2d. Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework is an IBM Redbook providing more detailed coverage of GEF and EMF. You will also need to install EMF to get the Redbook examples to work.

Acknowledgements

Thanks to Randy Hudson and Jim des Rivi�res for their thorough and careful reviews, which have been very helpful in improving both the technical accuracy and readability of this article.

Source Code

To run the example or view the source code for this article, download and unzip schemaeditor.zip into your eclipse/ subdirectory. Note that you will need Eclipse 3.0 or later to run the examples.

Database Schema Design Tool Eclipse

Source: https://www.eclipse.org/articles/Article-GEF-editor/gef-schema-editor.html

Posted by: hatcherdeconsenry.blogspot.com

0 Response to "Database Schema Design Tool Eclipse"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel