3.2+54.5
Das Eclipse Graphical Editing Framework (GEF)
Das Graphical Editing Framework (GEF) besteht
aus mehreren Eclipse Plugins, mit deren Hilfe graphische Editoren erstellt werden
können, um damit beliebige Datenmodelle zu visualisieren. Diese Bibliothek ist sehr
populär, so dass QF-Test die Aufnahme und Wiedergabe von GEF Elementen bereits seit
Version 2.2 unterstützt. Dies ist auch ein gutes Beispiel für die Stärke des
ItemResolver
Konzepts (siehe Abschnitt 54.3), denn
das gef
Jython-Modul enthält eine Implementierung genau dieser
Schnittstelle.
Das gef
Modul unterstützt GEF-Editoren in generischer Weise und kann
sogar mit mehreren Editoren gleichzeitig umgehen. Zwar werden auch für GMF-Anwendungen
brauchbare Namen für Unterelemente vergeben, doch nicht immer sind diese hinreichend
gut. Je nachdem, wie die zugrunde liegenden Modell-Klassen aussehen, bleibt noch etwas
Arbeit zu tun, nämlich die Implementierung eigener Resolver, die brauchbare Namen und
Werte für die Unterelemente liefern.
Aufnahme von GEF Elementen
Die eigentliche GEF Komponente ist der FigureCanvas
. Dieser stellt
Figures
dar, die EditParts
repräsentieren. Nimmt man einen
Mausklick auf solch ein Element auf, registriert QF-Test nicht bloß ein
Mausevent für den Canvas mit entsprechenden (x,y) Werten für die Position,
sondern versucht, das Objekt unter dem Mauscursor zu erkennen. Die aufgenommene
QF-Test ID der Komponente sieht zum Beispiel so aus:
canvas@/Diagram/My ship/ShipLargeCargo (wine)
canvas@Connection-2
canvas@/Diagram/Rectangle 16329001
wobei "canvas" die QF-Test ID der FigureCanvas
Komponente ist, gefolgt vom
Element-Index des erkannten EditPart
Objekts (siehe Abschnitt 5.9). EditParts
sind in einer Baumstruktur
organisiert, erkennbar am Pfadtrenner '/'. Die Namen der einzelnen Elemente werden
folgendermaßen abgeleitet:
- Der Elementname ist getModel().toString(), wenn darin kein Hashwert (z. B. NodeImpl@2d862) enthalten ist.
- QF-Test versucht, im Modell einen Namen für das Element zu finden ("My ship" in den obigen Beispielen).
- Die Klasse zusammen mit einer Beschreibung wird aufgenommen, etwa "ShipLargeCargo (wine)".
- Wenn es keine Beschreibung aber mehrere Elemente einer Klasse gibt, wird an diese ein Index angehangen, z. B. "Connection-2" für die dritte Connection.
- Der Wurzelknoten heißt stets "Diagram".
Man kann sich denken, dass die generierten Namen nicht immer sinnvoll sind. Elemente könnten gelöscht werden und aufgezeichnete Indizes damit ihre Gültigkeit verlieren. Oder der Elementname ist instabil wie bei "Rectangle 16329001" aus dem GEF Shapes Beispiel: Die Zahl ist rein zufällig und wird bei einem Neustart der Anwendung neu ermittelt. Drei Möglichkeiten gibt es, um solche Probleme zu lösen:
-
Anstatt mit einem textuellen Index zu arbeiten, kann man es mit einem numerischen
versuchen. Dazu setzt man in den Aufnahmeoptionen das Format für die Unterelemente
auf den Wert 'Zahl' (siehe Abschnitt 41.2.4). Allerdings
ist diese Lösung nicht sehr befriedigend, denn ein numerischer Index wie
/0/1
sagt nichts über das Element aus. -
Man könnte sich an die Entwickler wenden und diese davon zu überzeugen versuchen,
eine brauchbare Implementierung der Methode
toString()
für das Datenmodell eines Elements zu liefern. Das würde Ihnen das Leben leicht machen, aber eben nur, wenn die Entwickler kooperativ sind. -
Implementierung der
ItemNameResolver2
Schnittstelle. Das ist nicht ganz einfach, doch leider oft unumgänglich. Dieses Thema wird im nächsten Abschnitt behandelt.
Implementierung eines ItemNameResolver2 für GEF
Wie in Abschnitt 54.1 ausgeführt, ist
ItemNameResolver2
die Schnittstelle, um Namen für Elemente zu ändern
oder überhaupt erst zu definieren. Ein erster Ansatz zur Implementierung ist das
folgende Jython SUT-Skript, das unter Extrasequenzen
eingefügt werden kann:
def getItemName(canvas, item, name): print "name: %s" %name print "item: %s" %(item.__class__) model = item.getModel() print "model: %s" %(model.__class__) resolvers.addItemNameResolver2("myGefItemNames", getItemName, "org.eclipse.draw2d.FigureCanvas")
Um die Installation des Resolvers zu vereinfachen, wird das in Abschnitt 54.1 beschriebene resolvers
Modul verwendet. Der
Resolver wird auf die Klasse FigureCanvas
registriert, die
EditParts
als Unterlemente enthält. Der von QF-Test generierte Name wird
der Funktion getItemName()
beim Aufruf als drittes Argument übergeben.
Wenn man das Skript nun ausführt und dann den Aufnahmeknopf drückt, werden beim
Überfahren der Elemente mit der Maus - man sollte zuvor ein paar davon erzeugt haben
- Informationen im Terminal von QF-Test ausgegeben, etwa wie folgt:
name: Rectangle 16329001
item: org.eclipse.gef.examples.shapes.parts.ShapeEditPart
model: org.eclipse.gef.examples.shapes.model.RectangularShape
Abgesehen von diesen Ausgaben ist der Resolver ohne Funktion. Die Frage ist nun: Gibt
es im Modell irgendeine Eigenschaft oder Methode, die einen vernüftigen Namen
für das Element liefert? Für das GEF Shapes Beispiel lautet die Antwort: Nein.
Hoffentlich sind Sie mit Ihrer Anwendung in einer besseren Lage. Um das
herauszufinden, fügen Sie der Funktion getItemName()
die Zeile
print dir(model)
hinzu und führen das Skript erneut aus. Nun werden beim Bewegen der Maus über die
Elemente (im Aufnahmenmodus) auch die Methoden des Modells angezeigt. Mit etwas
Glück tauchen hier Methoden wie getId()
oder getLabel()
auf, so dass man einen Resolver wie den folgenden implementieren kann.
def getItemName(canvas, item, name): model = item.getModel() return model.getId() resolvers.addItemNameResolver2("myGefItemNames", getItemName, "org.eclipse.draw2d.FigureCanvas")
Zurück zum GEF Shapes Beispiel, wo es solche Methoden nicht gibt. Hier sind nur Informationen über die Geometrie verfügbar, doch das ist wenig hilfreich. Zumindest lassen sich aber Rechtecke und Ellipsen unterscheiden. Um die Elementnamen eindeutig zu machen, fügen wir einfach den Index der Figur (des Kind-Knotens) an, wie im folgenden Resolver gezeigt:
def getItemName(canvas, item, name): name = None shapes = "org.eclipse.gef.examples.shapes" diagrammEditPart = shapes + ".parts.DiagramEditPart" shapeEditPart = shapes + ".parts.ShapeEditPart" connectionEditPart = shapes + ".parts.ConnectionEditPart" ellipticalShape = shapes + ".model.EllipticalShape" rectangularShape = shapes + ".model.RectangularShape" if qf.isInstance(item, shapeEditPart): siblings = item.getParent().getChildren() for i in range(len(siblings)): if (item == siblings[i]): if qf.isInstance(item.getModel(), ellipticalShape): name = "Ellipse " + str(i) elif qf.isInstance(item.getModel(), rectangularShape): name = "Rectangle " + str(i) elif qf.isInstance(item, connectionEditPart): source = item.getSource() target = item.getTarget() sourceName = getItemName(canvas, source, str(source.getModel())) targetName = getItemName(canvas, target, str(target.getModel())) name = "Connection " + sourceName + " " + targetName elif qf.isInstance(item, diagrammEditPart): name = "Diagram" return name resolvers.addItemNameResolver2("shapesItemNames", getItemName, "org.eclipse.draw2d.FigureCanvas")
Mit diesem Resolver wird der Element-Index zu sowas wie
/Diagram/Rectangle 1
wobei die abschließende Zahl der Index des Kind-Knotens ist. Diese Implementierung
liefert auch Namen für die Verbindungen, indem getItemName()
rekursiv
für das Quell- und Zielelement aufgerufen wird. Die Typüberprüfung erfolgt mit der
Methode qf.isInstance
(siehe Abschnitt 50.6), wodurch
einem das Importieren der GEF-Klassen (was nicht ganz einfach ist) erspart bleibt.
Sobald der Resolver funktionstüchtig ist, sollte man das Skript in die Vorbereitung Sequenz packen, direkt hinter den Warten auf Client Knoten. Auf diese Weise wird der Resolver automatisch registriert, wenn man das SUT startet.
Implementierung eines ItemValueResolver2 für GEF
Üblicherweise besteht ein GEF Editor aus zwei Teilen. Bislang hatten wir uns auf den
Canvas konzentriert, in den die Figuren gezeichnet werden. Nun werfen wir einen
Blick auf die Palette, in der die Art der zu zeichnenden Figur ausgewählt wird (z.
B. Rechteck, Ellipse oder Verbindung). Die Einträge sehen zwar aus wie Buttons, doch
tatsächlich ist die Palette ebenfalls ein FigureCanvas
.
Erfreulicherweise funktioniert hier alles, ohne dass besondere Vorkehrungen
getroffen werden müssten, d. h. ohne einen ItemNameResolver2
zu
implementieren. Wenn man etwa auf den 'Rectangle' Button klickt, erkennt QF-Test ein
/Palette Root/Palette Container (Shapes)/Palette Entry (Rectangle)
Element. Was wird wohl passieren, wenn man einen 'Object value' Check für diesen Button aufnimmt? Man könnte erwarten, den Text 'Rectangle' zu erhalten, doch tatsächlich ist der Wert des Elements
Palette Entry (Rectangle)
Der Grund dafür ist, dass Name und Wert eines Elements normalerweise gleich sind. Um
dieses Verhalten zu ändern und selbstdefinierte Werte zu erhalten, muss ein
ItemValueResolver2
implementiert werden. Diese Schnittstelle ist der
ItemNameResolver2
Schnittstelle ganz ähnlich. Für die Palette können
wir etwa das Folgende codieren:
def getItemValue(canvas, item, value): value = None paletteEditPart = \ "org.eclipse.gef.ui.palette.editparts.PaletteEditPart" if qf.isInstance(item, paletteEditPart): value = item.getModel().getLabel() return value resolvers.addItemValueResolver2("shapesItemValues", getItemValue, "org.eclipse.draw2d.FigureCanvas")
Die Methode getLabel()
liefert den Text des Elements, so wie er in der
Palette angezeigt wird.