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 beVARCHAR
orINTEGER
-
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)
- 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
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 draw2dLabel
class which itself subclassesFigure
. We need this for the column and table names -
ColumnsFigure
: a container for all the column labels -
TableFigure
: contains anEditableLabel
for the table name, as well as aColumnsFigure
for the column names -
SchemaFigure
: a container for all theTableFigures
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
andToolbarLayout
, which lay out child figures according to their order by arranging them vertically or horizontally - Constraint-based layout managers, such as the
XYLayout
and theDelegatingLayout
. Here the application itself participates directly in the placement of figures by setting a constraintObject
for each child figure. In the case of theXYLayout
, this object is aRectangle
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
andCompoundDirectedGraphLayout
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:
- 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
- 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 - Managing what are known as direct edits, where the user types text directly into an editable control
EditPart
implementations -
SchemaDiagramPart
: represents aSchema
instance and associatedSchemaFigure
-
TablePart
: represents aTable
and manages theTableFigure
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 twoTable
instances, aRelationshipPart
is associated with twoTableParts
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
.
EditPart
subclasses must fulfill: -
Provide a figure instance to be associated with the
EditPart
. In the case ofTablePart
, we simply return a newTableFigure
instance with a name label:protected IFigure createFigure() { Table table = getTable(); EditableLabel label = new EditableLabel(table.getName()); TableFigure tableFigure = new TableFigure(label); return tableFigure; }
-
EditParts
which represent parent objects in parent-child relationships need to overridegetModelChildren()
. In the case ofTablePart
, our implementation of this method simply returns theColumn
objects it contains:protected List getModelChildren() { return getTable().getColumns(); }
Note that theAbstractEditPart
implements a parallel methodgetChildren()
, which returns theEditPart
collection representing the model children. In the case ofTablePart
,getChildren()
returns a list ofColumnPart
objects. We know this because our implementation ofEditPartFactory
associatesColumn
model instances with instances ofColumnPart
. TheEditPart
List
returned bygetChildren()
always needs to be kept in sync with thegetModelChildren()
. In the Section Synchronizing EditPart Relationships with Model Changes we describe how this happens
-
If the parent
EditPart
's figure is not the direct parent of the childEditPart
's figure, you will need to overrideAbstractGraphicalEditPart.getContentPane()
. The content pane is the containing figure into which GEF adds figures created by childEditParts
, which is by default the figure returned by theEditPart
'screateFigure()
method.In our example application the column labels are not contained within a
TableFigure
but within itsColumnsFigure
child. Our implementation ofgetContentPane()
inTablePart
reflects this:public IFigure getContentPane() { TableFigure figure = (TableFigure) getFigure(); return figure.getColumnsFigure(); }
Do not add and remove child figures by overriding
AbstractGraphicalEditPart.addChildVisual()
andAbstractGraphicalEditPart.removeChildVisual()
. OverridegetContentPane()
instead. -
EditParts
which represent nodes (model objects which may participate in connections) must also implement a number of additional methods defined in the interfaceNodeEditPart
-
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 currentTablePart
'sTable
is the foreign key. Once again, there is a parallel methodgetSourceConnections()
, which returns theList
ofRelationshipParts
associated with these relationships. We also consider in the Section Synchronizing EditPart Relationships with Model Changes how theConnectionEditPart
list returned bygetSourceConnections()
stays in sync with theRelationship
list returned bygetModelSourceConnections()
-
protected List getModelTargetConnections()
: this works identically togetModelSourceConnections()
, except that it returns theRelationship
objects for which the currentTablePart
'sTable
is the primary key -
the node
GraphicalEditPart
must also provide implementations of theNodeEditPart
getSourceConnectionAnchor()
andgetTargetConnectionAnchor()
methods. In each case, these methods return objects which represent the points to which connections between nodes can be attached
-
- Provide an implementation for
createEditPolicies()
, during whichEditPolicy
implementations are associated with specific editing roles. TheEditPolicy
and its associated roles,Request
andCommand
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 ofTablePart
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
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 theCommand
object encapsulates -
undo()
: used to reverse the effect ofexecute()
. HereCommand
also provides a no-op implementation -
redo()
: used redo a command execution. TheCommand
implementation simply callsexecute()
, which should usually be adequate -
canExecute()
: whetherexecute()
can be executed. The subclass can implement this to specify the conditions under which the command can be executed -
canUndo()
: whetherundo()
can be executed. TheCommand
implementation simply returns true, which subclasses can override -
canRedo()
: whetherredo()
can be executed. TheCommand
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
Sending and Receiving Event Notifications
The requirements imposed on our model implementation are that
- it exposes a mechanism by which listeners can register interest in event notifications, and
- it actually fires these event notifications at the appropriate times!
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 anEditPart
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 correspondingTableParts
. The base implementation of this method not only synchronizes your model andEditPart
hierarchies - it also adds or removes visual components as required by calling theAbstractGraphicalEditPart
addChildVisual()
andremoveChildVisual()
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:
- The cached information held by the layout managers for the
TableFigure
andColumnsFigure
, which includes minimum size and preferred size for the child figures, needs to be thrown away - The
SchemaFigure
's layout manager needs to update any cached constraint information it is holding for theTableFigure
- The bounds of both the
TableFigure
and theColumnsFigure
need to change to reflect addition of the column - 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
|
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