Zeichnen in Eclipse

Aus Eclipse
Wechseln zu: Navigation, Suche

Einführung

Überblick

Dieser Artikel gibt den Überblick über die Architektur und die Möglichkeiten des Graphical Editing Framework (GEF) für Eclipse. Das GEF ist nach dem Entwicklungsmuster MVC (Model-View-Controller) gebaut. Deswegen wird hier zuerst eine kleine Einführung in MVC gemacht.

Model-Veiw-Controller-Muster (MVC)

Unter Model-View-Controller-Muster versteht man eine Software-Architektur für die Programmierung von Benutzer-Interaktionen, für die Folgendes gilt:

  • Es sind drei Einheiten in der Architektur für das Model, das View und den Controller klar erkennbar, deren Aufgaben strikt verteilt und voneinander getrennt sind.
  • Das Model verwaltet das darzustellende Datenmodell und bietet ein Interface zur Beobachtung und Änderung der Daten an. Das Model wird ohne Kenntnis von anderen MVC-Schichten implementiert. Sein Design soll auch darauf orientiert sein, möglichst effizient und einfach die Daten zu verwalten, ohne Rücksicht auf das View oder den Controller. Wenn man mit der Entwicklung anfängt, ist das Model oft bereits vorhanden. Aus der MVC-Sicht ist das Model also eine Komponente, die von Außen dem Entwickler gegeben wird und unverändert bleibt. Ein gutes Beispiel für ein Model ist eine SQL-Datenbank. Bei der Entwicklung der GUI kann der Entwickler weder die SQL-Sprache noch das DBMS ändern, und muss es als fest vorprogrammiert sehen.
  • Das View präsentiert das Model dem Benutzer. In manchen Implementierungen kann die View-Schicht direkt mit dem Model kontaktieren, in anderen ist das die Aufgabe des Controllers, die Information über das Model an das View weiterzugeben (in GEF ist es der Fall). Die Präsentation muss natürlich auf die Änderungen im Model reagieren. In manchen Implementierungen informiert das Model direkt die View-Schicht über die Änderungen. In GEF ist es auch die Aufgabe des Controllers. Wenn man die Schichten des MVS als drei Punkte auf der Fläche darstellt und die Verbindungen zwischen den Schichten als Linien zeichnet, bekommt man im allgemeinen Fall ein Dreieck (wie hier :http://de.wikipedia.org/w/index.php?title=Datei:ModelViewControllerDiagram2.svg&filetimestamp=20100429123612). Im Fall von GEF reduziert sich dieses Dreieck zu einer Linie (das kann man hier im Kapitel "Overview" sehen: http://www.ibm.com/developerworks/opensource/library/os-gef/). Weiter ist es die Aufgabe von View, die Benutzeraktionen dem Controller weiterzugeben.
  • Der Controller wartet auf die Benutzeraktionen, die ihm vom View gemeldet werden, und führt am Model die dieser Aktion entsprechenden Operationen aus. In vielen Implementierungen, wie z.B. in GEF, ist es die Aufgabe des Controllers, die Kommunikation zwischen Model und View zu gewährleisten.
  • Alle drei Einheiten wissen möglichst wenig voneinander.

Die konsequente Durchsetzung dieses Musters ermöglicht es, mit minimalem Aufwand Änderungen in einer der drei Einheiten vorzunehmen oder bei Bedarf sogar eine komplette Schicht auszutauschen (z.B. ein Model durch ein anderes zu ersetzen, das nur das gleiche Interface unterstützen muss). Es ermöglicht darüber hinaus eine unabhängige Entwicklung jeder dieser drei Komponenten.
Hier ist es wichtig zu betonen, dass sowohl das View als auch der Controller vom Model abhängig sind. Das Model hingegen ist weder vom View noch vom Controller abhängig. Das ist eine der wichtigsten Eigenschaften dieser Architektur. Damit wird ermöglicht, mehrere Darstellungsweisen für das gleiche Model zu präsentieren.
Die nächsten zwei Diagrammen zeigen den Unterschied zwischen der klassischen Variante des MVC und der MVC-Variante in GEF.
MVC: Dratva 1919 MVC classic.png MVC-GEF: Dratva 1919 MVC GEF.png

Weitere Entwicklungsmuster, die in GEF verwendet werden

  • Observer: EditPart beobachtet das Model, um die Änderungen im View darzustellen.
  • Factory: Zum Beispiel, bei der Erstellung der EditPart-Objekten auf Basis vom Model.
  • Command: Für die Ausführung der Benutzeraktionen mit der Undo-Möglichkeit.
  • Chain of Responsibility: Bei der Auswahl der EditPolicy.

Anwendungsfälle

  • Graphical Modeling Framework (GMF)
  • Visual Editor Project (VEP)
  • Eclipse Viszalization Toolkit (Zest)

Architektur

Das GEF besteht aus 2 Plugins, Draw2d (org.eclipse.draw2d) und GEF-Plugin (org.eclipse.gef). Weiter im Text wird das Gesamtsystem "GEF" genannt, und der Plugin org.eclipse.gef wird "GEF-Plugin" genannt.
Das GEF ist nach MVC-Muster entwickelt. Das bedeutet, dass es in GEF eine klare Trennung zwischen Model-, View- und Controller-Schichten gibt und die Interfaces zwischen diesen drei Schichten möglichst schlank gehalten werden. Das bedeutet natürlich, dass die Entwickler, die ihre Eclipse-Plugins auf GEF basieren, bestimmte Regeln einhalten müssen, damit die saubere MVC-Trennung in diesen Plugins erhalten bleibt. Zu diesen Regeln kommen wir später.
Im Prinzip stellt der GEF-Plugin die Controller-Schicht der MVC-Architektur dar. Draw2d ist die View-Schicht und das Model kann alles sein, was den unten beschriebenen Anforderungen entspricht.
Hier wird die Implementierung der einzelnen MVC-Schichten in GEF beschrieben.

Model

Das Model ist eine Datenstruktur, die man mit Hilfe von GEF darstellen und grafisch editierbar machen kann. Die Voraussetzungen für das Model sind:

  • Es muss aus Java-Objekten bestehen.
  • Es muss über seine Änderungen benachrichtigen können.
  • Es muss ein Interface zur Durchführung der Änderungen anbieten.


Alle persistenten Daten werden nur im Model gespeichert.

Controller

Der Controller in GEF besteht hauptsächlich aus den EditParts. Die EditParts bilden während der Bearbeitung eine eigene Hierarchie, die die Hierarchie des Models abbildet. Die Regeln dieser Abbildung sind im Controller definiert. Dafür sind das EditPartFactory und die EditParts zuständig. In den EditParts sind die Methoden getModelChildren, getModelSourceConnections und getModelTargetConnections für diese Abbildung zuständig.

Der EditPartViewer ist ein Interface, dessen Implementierungen die EditParts verwalten. Ein EditPartViewer ist für den ganzen Lebenszyklus der EditParts verantwortlich. Der Inhalt von einem EditPartViewer wird in der Regel mithilfe von einem EditPartFactory bestückt. Drei fertige Implementierungen von EditPartViewer existieren in GEF:

  • GraphicalViewerImpl - eine Implementierung von EditPartViewer, die auf draw2d und Figuren basiert ist (das ist kein Muss, wie man am Beispiel von TreeViewer sehen wird).
  • ScrollingGraphicalViewer - eine Scrollbare Implementierung von GraphicalViewerImpl.
  • TreeViewer - eine auf SWT-Tree basierte Implementierung von EditPartViewer.


Als EditPart wird alles bezeichnet, was das Interface org.eclipse.gef.EditPart implementiert. Dieses Interface ist sehr umfangreich, aber das GEF bietet mehrere abstrakte Klassen an, die von EditPart abgeleitet sind und für die meisten Methoden eine Default-Implementierung anbieten (wie AbstractGraphicalEditPart, AbstractConnectionEditPart oder AbstractTreeEditPart). Siehe dazu GEF-Plugin.

View

Die View-Schicht des GEF besteht aus den draw2d-Figuren. Diese Figuren bilden wieder eine Hierarchie, die eine Abbildung der EditPart-Hierarchie ist. Die Regeln dieser Umformung sind in einzelnen EditParts definiert. Siehe dazu die Methode AbstractGraphicalEditPart#createFigure.
Für die genauere Beschreibung der Möglichkeiten der View-Schicht siehe Draw2d.

Überblick

Dieses Bild zeigt vereinfacht die Hierarchien der Model-, Controller- und View-Objekte:

Dratva 1919 GEF overview.png

Verbindung Model-Controller

Die Verbindung zwischen dem Model und dem Controller wird dadurch ermöglicht, dass der Controller, also die EditParts, das Model kennen. Die EditParts sind bei entsprechenden Model-Objekten als Listener angemeldet und werden über Änderungen informiert.
Beim Starten eines grafischen Editors wird eine Instanz von EditPartViewer erstellt, die mit dem Wurzelobjekt des Models parametriert wird. Der nächste Schritt wird rekursiv wiederholt bis der Baum der EditParts komplett ist. Die EditPartFactory wird beauftragt, ein EditPart für das gegebene Model-Objekt zu generieren. Das erzeugte EditPart wird nach Kinder seines Model-Objekten gefragt. Das ist eine der Stellen, wo der Controller das Model kennen muss. Für diese Kinder-Objekte wird, wie oben gesagt, der Schritt wiederholt.

Verbindung Controller-View

Die Verbindung zwischen dem Controller und dem View in GEF ist die Verbindung zwischen den EditParts und den draw2d-Figuren. Die verbindende Instanz dabei ist ein GraphicalViewer. Er wird mit dem Wurzel-EditPart parametriert. Der GraphicalViewer steuert den Aufbau des Baums der EditParts und der Figuren und kümmert sich um die Koordination zwischen diesen zwei Bäumen.

MVC-GEF-Regeln

  • Das Model darf natürlich nicht die Objekte aus der Controller- oder View-Schicht referenzieren und darf grundsätzlich nichts von GEF wissen.
  • Eine Figur darf nicht das Model oder das EditPart referenzieren.
  • Ein EditPart muss möglichst wenig Einzeiheiten wissen.
  • Nur Command-Objekte dürfen das Model verändern.

Abläufe in GEF

Reaktion auf Änderungen im Model

Hier ist ein typischer Ablauf der Reaktion in GEF auf Änderungen im Model dargestellt.
Wie wir bereits wissen, ist die View-Schicht in GEF vom Model unabhängig und bekommt die ganze Information vom Controller. Die erste Stufe der Reaktion ist also der Controller. Die einzelnen EditParts sind "Listener" der entsprechenden Model-Objekten (in der Methode activate haben sie die Möglichkeit, sich für die Änderungen im Model anzumelden). Nach dem Empfang einer Benachrichtigung über die Änderung im Model ist das weitere Vorgehen von dem Typ dieser Änderung abhängig. Wenn die Änderung im Model nur die Erscheinung dieses konkreten EditPart beeinflusst (z.B. eine Text-Änderung für ein Label) - wird nur die Methode refreshVisuals aufgerufen. Diese Methode soll die Änderungen in der entsprechenden Figur des View durchführen. Wenn die Änderung im Model die Struktur der Kinder des EditPart ändert, oder die ein- oder ausgehenden Verbindungen zum EditPart - werden die Methoden refreshChildren, refreshSourceConnections oder refreshTargetConnections aufgerufen. Mit dem Aufruf von refresh werden alle diese Methoden ausgeführt.

Ablauf der Editiervorgänge

Das Editieren ist der komplizierteste Prozess in GEF (wie auch in jedem anderen grafischen Editor). Hier wird es nur grob beschrieben.
Wenn der Benutzer im Editor irgendetwas macht (Kontextmenü-Aktionen, Maus- oder Tastaturaktionen usw) - muss der Controller als Vermittler zwischen dem View und dem Model darüber informiert werden. Diese Information wird mithilfe der Requests (org.eclipse.gef.Request) übergeben. Die Request-Objekte werden z.B. in den Actions erstellt, wenn der Benutzer einen Kontextmenüeintrag anklickt. Das Request-Objekt wird der Methode getCommand des ausgewählten EditPart weitergegeben. Das EditPart erstellt mithilfe der EditPolicies, wie in GEF-Plugin#EditPolicy beschrieben ist, ein Command-Objekt (siehe GEF-Plugin#Command. Dieses Command wird anschließend ausgeführt (z.B. in der run-Methode des Action-Objektes). Das Command soll die gewünschten Änderungen im Model durchführen. Das View und der Controller sollen dabei aktualisiert werden. Wie das passiert, wurde im vorherigen Kapitel erklärt.

GEF3D

Eine neue Entwicklung im Eclipse ist das GEF3D - das Framework für die Erzeugung dreidimensionaler Diagrammen. Das Projekt befindet sich bei Eclipse noch im "Incubation State". Ähnlich wie GEF mit Draw2d bringt es das Draw3D-Plugin mit den dreidimensionalen Figuren. Im Gegenteil zu Draw2d wird in Draw3D OpenGL für das Rendering der Figuren benutzt. Die existierenden GEF-Editoren kann man laut Entwickler problemlos in GEF3D portieren. GEF3D ist so konzipiert, dass möglichst wenig Kenntnisse in der 3D-Geometrie vom Entwickler verlangt werden.

Beispiel - Organigramm-Editor

Hier ist ein konkretes Beispiel einer GEF-Anwendung, ein Organigramm-Edior. Das Ziel dieses Beispiels ist es, in einem einfachen Plugin die Grundideen und die Architektur von GEF zu zeigen.

Datenmodell

Für das Organigramm wird ein einfaches Datenmodell verwendet, das aus gleichen Knoten-Objekten besteht. Jeder Knoten hat eine Liste der untergeordneten Objekten und der "Assistenten".
Das Datenmodell zum Organigramm ist hier grob grafisch dargestellt:
Dratva Beispiel Organigramm UML.gif
Dieses Datenmodell ermöglicht die Erstellung einer baumartigen Struktur, die im Editor präsentiert wird.
In diesem einfachen Beispiel wird das Datenmodell nicht in einer Datei gespeichert, sondern es wird immer beim Öffnen des Organigramm-Editors eine festkodierte Struktur im Konstruktor der Organigramm-Klasse angelegt:

	public Organigram()
	{
		Node node1Child = new Node("Node 1 child");
		node1Child.addAssistant(new Node("Node 1 child assistant 1") );
 
		Node node1 = new Node("Node 1");
		node1.addChild(new Node("Node 1.1") );
		node1.addChild(new Node("Node 1.2") );
		node1.addAssistant(new Node("Node 1 assistant 1") );
		node1.addChild(node1Child);
 
		Node node2 = new Node("Node 2");
		node2.addChild(new Node("Node 2.1") );
		node2.addChild(new Node("Node 2.2") );
		node2.addChild(new Node("Node 2.3") );
		node2.addAssistant(new Node("Node 2 assistant 1") );
 
		primaryNodes.add(node1);
		primaryNodes.add(node2);
	}


Schritt 1

Als erster Schritt wird das Datenmodell ohne Verbindungslinien und ohne Editiermöglichkeit dargestellt. Das Ziel ist die Transformation vom Datenmodell zu der Hierarchie der Figuren zu zeigen.


In dem nächsten Codeausschnitt sieht man die Implementierung des EditPartFactory für dieses Beispiel. Es wird für jedes an createEditPart übergebene Objekt ein EditPart erzeugt. Der Typ dieses EditPart-Objektes ist von der Klasse des Model-Objektes abhängig.

public class OrganigramEditPartFactory implements EditPartFactory {
 
	@Override
	public EditPart createEditPart(EditPart context, Object model) {
		EditPart editPart = null;
		if (model instanceof Organigram) {
			editPart = new OrganigramEditPart();
		} else if (model instanceof Node) {
			editPart = new NodeEditPart();
		} else if (model instanceof AssistantsList) {
			editPart = new AssistantsSetEditPart();
		} else if (model instanceof ChildrenList) {
			editPart = new ChildrenSetEditPart();
		} else if (model instanceof String) {
			editPart = new StringToLabelEditPart();
		} else if (model instanceof ModelConnection) {
			editPart = new ConnectionEditPart();
		}
 
		if (editPart != null) {
			editPart.setModel(model);
		}
 
		return editPart;
	}
 
}


Wie man sofort sieht, sind nicht alle ausgewerteten Model-Klassen wirklich aus dem Model. Die nicht aus dem Model stammenden Klassen sind AssistantsList, ChildrenList und ModelConnection. Sie werden deswegen benötigt, weil nicht alle grafischen Elemente im Editor ein entsprechendes Objekt im Model besitzen. Z. B. haben die Verbindungslinien kein Objekt im Model, das diese Linien repräsentiert. Es ist also eine Erweiterung des Models im Controller.

Der nächste Code-Ausschnitt zeigt die Implementierung des EditPart für ein Node-Objekt. Die Methode createFigure erstellt eine neue Figur mit einem Layout-Manager. Es wurde absichtlich in dieses und in alle anderen EditParts ein dicker Rahmen eingefügt, damit man im Ergebis deutlich sieht, welche Komponente welchen Bereich belegt. In der Methode getModelChildren werden alle Elemente gesammelt, die in der grafischen Darstellung dieser Figur untergeordnet sein sollen.

public class NodeEditPart extends AbstractGraphicalEditPart {
 
	...	
	@Override
	protected IFigure createFigure() {
		LineBorder border = new LineBorder();
		border.setColor(ColorConstants.gray);
		border.setWidth(5);
 
		Figure f = new Figure();
                f.setOpaque(true);
		f.setBorder(border);
 
		ToolbarLayout layout = new ToolbarLayout(false);
		layout.setSpacing(10);
                f.setLayoutManager(layout);
 
                return f;
	}
 
        ...
 
	@Override
	protected List<Object> getModelChildren() {
		List<Object> modelChildren = new ArrayList<Object>();
		modelChildren.add( ( (Node)getModel() ).getName() );
		modelChildren.add( new AssistantsList( ( (Node)getModel() ).getAssistants() ) );
		modelChildren.add( new ChildrenList( ( (Node)getModel() ).getChildren() ) );
		return modelChildren;
	}
}


Die Methode getModelChildren ist in der Klasse AbstractEditPart definiert. Sie soll die Model-Objekte liefern, für die EditParts erstellt werden. Diese neuen EditParts werden zu Kinder dieses EditPart. Man sieht hier auch die Erstellung der zusätzlichen Model-Erweiterungs-Klassen AssistantList und ChildrenList.
Das nächste Diagramm zeigt den Zusammenhang zwischen dem Model und den EditParts in diesem Schritt. Die vertikale Linie zeigt die Grenze zwischen dem Kontroller und dem Model. Die grünen Kästchen sind die Pseudo-Model-Klassen.
Step1 diag.gif
Ähnlich ist auch OrganigramEditPart implementiert, liefert aber im getModelChildren die Objekte aus Organigram.primaryNodes:

public class OrganigramEditPart extends AbstractGraphicalEditPart {
 
	@Override
	protected IFigure createFigure() {
		LineBorder border = new LineBorder();
		border.setColor(ColorConstants.green);
		border.setWidth(5);
 
		Figure f = new Figure();
                f.setOpaque(true);
		f.setBorder(border);
 
		ToolbarLayout layout = new ToolbarLayout(true);
		layout.setSpacing(10);
 
                f.setLayoutManager(layout);
 
                return f;
	}
 
        ...
 
	@Override
	protected List<Object> getModelChildren() {
		List<Object> resultList = new ArrayList<Object>();
		resultList.addAll(( (Organigram)getModel() ).getPrimaryNodes() );
		return resultList;
	}
 
}


Auf dem Bild sieht man das Ergebnis. Dabei sind die Farbzuordnungen zu beachten:

  • OrganigramEditPart - grün
  • NodeEditPart - grau
  • AssistantsSetEditPart - blau
  • ChildrenSetEditPart - rot
  • StringToLabelEditPart - schwarz und dünn


Dratva 1919 Beispiel1.png

Schritt 2 - Verbindungslinien

Jetzt fügen wir zum vorherigen Beispiel die Verbindungslinien zu.
Für das Verständnis dieses Schrittes ist es wichtig, dass die Verbindungslinien, die hier eingeführt werden, verbinden nicht die Figuren der Node-Objekten, sondern die zu den jeweiligen Node-Objekten gehörende Labels.
Das Model hat keine Objekte, die den Verbindungslinien entsprechen. Die Verbindungen können in diesem Model nicht hinzugefügt oder gelöscht werden, sondern existieren immer zwischen dem Eltern- und Kindobjekt. Deswegen wurde im Controller die Klasse ModelConnection eingeführt. Die Objekte dieser Klasse werden im Controller in der Klasse StringToLabelEditPart erstellt wie man hier sieht:

public class StringToLabelEditPart extends AbstractGraphicalEditPart {
	private Label label = null;
 
	@Override
	protected IFigure createFigure() {
		label = new Label( (String)getModel() );
		label.setBorder(new LineBorder() );
 
        	return label;
	}
 
        ...
 
	@Override
	protected List<ModelConnection> getModelSourceConnections() {
		ArrayList<ModelConnection> connections = new ArrayList<ModelConnection>();
 
		for (Node node : getNode().getChildren() ) {
			ModelConnection connection = ConnectionRegistry.getInstance().getConnectionForTarget(node);
 
			if(null == connection)
			{
				connection = new ModelConnection();
				ConnectionRegistry.getInstance().addConnection(node, connection);
			}
 
			connections.add(connection);
		}
 
		for (Node node : getNode().getAssistants() ) {
			ModelConnection connection = ConnectionRegistry.getInstance().getConnectionForTarget(node);
 
			if(null == connection)
			{
				connection = new ModelConnection();
				ConnectionRegistry.getInstance().addConnection(node, connection);
			}
 
			connections.add(connection);
		}
 
		return connections;
	}
 
	@Override
	protected List<ModelConnection> getModelTargetConnections() {
		ArrayList<ModelConnection> connections = new ArrayList<ModelConnection>();
 
		ModelConnection connectionToParent = ConnectionRegistry.getInstance().getConnectionForTarget(getNode() );
 
		if(null != connectionToParent) {
			connections.add(connectionToParent);
		}
 
		return connections;
	}
 
	protected Node getNode() {
		if(getParent() instanceof NodeEditPart)
		{
			return (Node)(getParent().getModel() );
		}
 
		return null;
	}
}


In diesem Beispiel wird eine weitere Klasse verwendet - ConnectionRegistry. Der Zweck dieser Klasse ist es zu ermöglichen, dass die ModelConnection-Objekte, die in getModelSourceConnections erstellt wurden, später in getModelTargetConnections für den Kind-Knoten wiedergefunden werden können.

Unser informelles Objekt-Diagramm aus dem Schritt 1 ändert sich wie im nächsten Bild:

Dratva 1919 Beispiel2 Obj.gif

Der Output mit Verbindungslinien ist im nächsten Bild zu sehen.

Dratva 1919 Beispiel2.png

Schritt 3 - Editor

Bis jetzt war unser Beispiel nur ein Viewer des gegebenen Model's, das nicht mal auf die Model-Änderungen reagieren kann. In diesem Schritt wird demonstriert, wie die Editierbarkeit hinzugefügt werden kann. Es werden lediglich die Möglichkeiten hinzugefügt, die Objekte zu selektieren und zu entfernen.
Das erste, was man dabei braucht, ist die Möglichkeit im Controller auf die Änderungen im Model zu reagieren. Um das zu ermöglichen, muss man das Model entsprechend erweitern (im Moment kann es gar nicht über seine Änderungen informieren).
Zunächst braucht man ein Listener-Interface:

public interface NodeSetListener {
 
	public void childAdded(Node child);
 
	public void childRemoved(Node child);
}


Die Klasse Node, die früher ein einfacher Container für die Kinder und Assistenten war, wird erweitert:

public class Node implements Serializable {
 
	private String name;
 
	private ArrayList<NodeSetListener> listeners = new ArrayList<NodeSetListener>();
 
	private ArrayList<Node> children = new Children();
	private ArrayList<Node> assistants = new Assistants();
 
	public Node(String name)
	{
		this.name = name;
	}
 
	public void addChild(Node node)
	{
		if(children.add(node) )
		{
			for (NodeSetListener listener : listeners) {
				listener.childAdded(node);
			}
		}
	}
 
	public ArrayList<Node> getChildren()
	{
		return children;
	}
 
	public boolean removeChild(Node node)
	{
		if(children.remove(node) )
		{
			for (NodeSetListener listener : listeners) {
				listener.childRemoved(node);
			}
 
			return true;
		}
 
		return false;
	}
 
	public void addAssistant(Node node)
	{
		if(assistants.add(node) )
		{
			for (NodeSetListener listener : listeners) {
				listener.childAdded(node);
			}
		}
	}
 
	public ArrayList<Node> getAssistants() {
		return assistants;
	}
 
	public boolean removeAssistant(Node node)
	{
		if(assistants.remove(node) )
		{
			for (NodeSetListener listener : listeners) {
				listener.childRemoved(node);
			}
 
			return true;
		}
 
		return false;
	}
 
	public String getName()
	{
		return name;
	}
 
	public void addListener(NodeSetListener listener)
	{
		listeners.add(listener);
	}
 
	public void removeListener(NodeSetListener listener)
	{
		listeners.remove(listener);
	}
}


Nun ist ein Node in der Lage, über die Änderungen in seiner Struktur seine "Listener" zu informieren.
Jetzt können die EditParts auf die Änderungen im Model reagieren. Dafür wird das NodeEditPart erweitert:

public class NodeEditPart extends AbstractGraphicalEditPart implements NodeSetListener {
 
	private Label label;
 
	@Override
	public void activate() {
		( (Node)getModel() ).addListener(this);
	}
 
	@Override
	public void deactivate() {
		( (Node)getModel() ).removeListener(this);
	}
 
	public Label getLabel() {
		return label;
	}
 
	public void setLabel(Label label) {
		this.label = label;
	}
 
	@Override
	protected IFigure createFigure() {
		LineBorder border = new LineBorder();
		border.setColor(ColorConstants.gray);
		border.setWidth(5);
 
		Figure f = new Figure();
        	f.setOpaque(true);
 
		ToolbarLayout layout = new ToolbarLayout(false);
		layout.setSpacing(10);
        	f.setLayoutManager(layout);
 
        	return f;
	}
 
	@Override
	protected void createEditPolicies() {
	}
 
	@Override
	protected List<Object> getModelChildren() {
		List<Object> modelChildren = new ArrayList<Object>();
		modelChildren.add( ( (Node)getModel() ).getName() );
		modelChildren.add( new AssistantsList( ( (Node)getModel() ).getAssistants() ) );
		modelChildren.add( new ChildrenList( ( (Node)getModel() ).getChildren() ) );
		return modelChildren;
	}
 
	@Override
	public void childAdded(Node child) {
		for (Object editPart : getChildren() ) {
			if(editPart instanceof AbstractEditPart) {
				( (AbstractEditPart)editPart).refresh();
			}
		}
	}
 
	@Override
	public void childRemoved(Node child) {
		for (Object editPart : getChildren() ) {
			if(editPart instanceof AbstractEditPart) {
				( (AbstractEditPart)editPart).refresh();
			}
		}
	}
}


Das NodeEditPart registriert sich jetzt während der Aktivierung bei Node als Listener. Bei der Deaktivierung meldet es sich ab. In den Methoden childAdded und childRemoved reagiert es auf die Änderungen im Model und veranlasst die Erzeugung oder die Entfernung der EditParts.
Das einzige sichtbare Objekt in unserem Editor ist das Label von StringToLabelEditPart (die farbigen Rahmen in vorherigen Schritten werden jetzt verborgen). Diese Klasse muss man jetzt modifizieren, um die Selektierbarkeit zu erreichen. Dafür wird die Klasse in diesem Schritt um diese zwei Methoden erweitert:

	@Override
	public void setSelected(int value) {
		super.setSelected(value);
 
		if(null != label)
		{
			if(value > 0)
			{
				label.setOpaque(true);
				label.setBackgroundColor(ColorConstants.red);
			}
			else
			{
				label.setOpaque(false);
			}
		}
	}
 
	@Override
	protected void createEditPolicies() {
		// allow removal of the associated model element
		installEditPolicy(EditPolicy.COMPONENT_ROLE, new NodeComponentEditPolicy() );
	}


So kann man die Objekte bereits selektieren. Die selektierten Labels werden den roten Hintergrund bekommen. Um die selektierten Objekte entfernen zu können, braucht man noch die Implementierung von hier verwendeten NodeComponentEditPolicy sowie ein Befehlsobjekt, das die Model-Objekte korrekt entfernt und als letztes die Möglichkeit, ein Request zum Entfernen des selektierten Objektes zu erstellen.
Zuerst die Implementierung von NodeComponentEditPolicy:

public class NodeComponentEditPolicy extends ComponentEditPolicy {
 
	protected Command createDeleteCommand(GroupRequest deleteRequest) {
		NodeEditPart host = (NodeEditPart)(getHost().getParent() );
		Node node = (Node)(host.getModel() );
 
		if(host.getParent().getParent() instanceof NodeEditPart)
		{
			Node parent = (Node)( (host.getParent().getParent() ).getModel() );
			return new DeleteCommand(node, parent);
		}
 
		return super.createDeleteCommand(deleteRequest);
	}
}


Das hier verwendete DeleteCommand sieht so aus:

public class DeleteCommand extends Command {
 
	private Node node, parent;
	private boolean executed, removedAssistant;
 
	public DeleteCommand(Node node, Node parent)
	{
		this.node = node;
		this.parent = parent;
		executed = false;
		removedAssistant = false;
	}
 
	@Override
	public void execute() {
		redo();
	}
 
	@Override
	public boolean canUndo() {
		return executed;
	}
 
	@Override
	public boolean canExecute() {
		if(!executed && 
				null != node && 
				null != parent && 
				(parent.getAssistants().contains(node) ||
			     parent.getChildren().contains(node) ) )
		{
			return super.canExecute();
		}
 
		return false;
	}
 
	@Override
	public void redo() {
		if(!executed)
		{
			ConnectionRegistry.getInstance().removeConnection(node);
 
			if(parent.removeAssistant(node) )
			{
				executed = true;
				removedAssistant = true;
			}
			else if(parent.removeChild(node) )
			{
				executed = true;
			}
		}
	}
	...
}


Die eigentliche Model-Anpassung passiert in der Methode redo.
Jetzt muss man das GUI mit der Erstellung des DeleteCommand verbinden. Dafür gibt es mehrere Wege. In den meisten GEF-basierten Editoren existiert dafür eine Palette mit Werkzeugen (Tools, siehe GEF-Plugin). In diesem Beispiel wird nur ein Kontextmenü mit der Delete-Option für die ausgewählten Objekte angeboten. Für die Kontextmenüsteuerung gibt es in GEF die Klasse ContextMenuProvider, die wie folgt erweitert wird:

public class OrganigramContextMenuProvider extends org.eclipse.gef.ContextMenuProvider {
	private ActionRegistry actionRegistry;
 
	public OrganigramContextMenuProvider(EditPartViewer viewer, ActionRegistry registry) {
		super(viewer);
		actionRegistry = registry;
	}
 
	@Override
	public void buildContextMenu(IMenuManager menu) {
		menu.add(new Separator(GEFActionConstants.GROUP_EDIT) );
		menu.appendToGroup(
				GEFActionConstants.GROUP_EDIT,
				actionRegistry.getAction(ActionFactory.DELETE.getId()));
	}
 
}


Dieser "Menu Provider" wird in OrganigramEditor#configureGraphicalViewer mit dem Aufruf von GraphicalViewer#setContextMenu eingefügt. Das Ergebnis kann man auf dem folgenden Bild sehen. Das selektierte Objekt ist rot markiert und im Kontextmenü ist die Aktion Entfernen des Objektes verfügbar.
Dratva 1919 Beispiel3.png

Ein Sequence-Diagramm hilft hier zu verstehen, wie ein Editier-Vorgang in diesem Beispiel abläuft:
Dratva 1919 command sequence.png
Man sieht hier, wie ein Request-Objekt in DeleteAction erzeugt wird, und wie dieses Request benutzt wird, um ein Command-Objekt zu erstellen und auszuführen.

Beispiel-Quellcode

Hier kann man das komplette Organigramm-Plugin wie im Schritt 3 herunterladen.

Datei:Dratva 1919 example.zip

Literatur

GEF Programmer's Guide auf help.eclipse.org
Moore, Bill ; Dean, David ; Gerber, Anna ; Wagenknecht, Gunnar ; Vanderheyden, Philippe: Eclipse Development using the Graphical Editing Framework and the Eclipse Modeling Framework. IBM (IBM Redbook).
Layout und Rendering von Figures mit Draw2d, TU Berlin 2008
GEF (Graphical Editing Framework) Tutorial, Jean-Charles MAMMANA, Romain MESON, Jonathan GRAMAIN, 2007