A l'étape du billet précédant [step6] la page Graphics de notre éditeur de workflow basée sur GEF a été initialisée. Dans ce billet et le suivant, nous allons afficher les informations du modèle EMF workflow dans la page Graphics :
Plus précisemment, dans ce billet nous allons initialiser les composants GEF (EditPart, EditPartFactory, figure Draw2d,GraphicalViewer) et nous occuper uniquement de l'instance EMF WorkflowType (pas de ces elements enfants StateType et ActionType). Pour vérifier que la mise en place de GEF soit faite correctement, nous représenterons l'instance EMF WorkflowType par un cadre rouge :

Vous pouvez télécharger le projet org.example.workflow_step7.zip présenté dans ce billet.
Lorsque j'ai démarré GEF, je ne savais pas par ou commencer car il y a une multitude de concepts à assimiler avant de pouvoir afficher/gérer son modèle. La documentation GEF propose plusieurs exemples qui m'ont beaucoup aidé une fois avoir passé un temps considérable à comprendre les principes de base de GEF. C'est pourquoi, je ne vais pas décrire GEF et ses principes d'un seul coup, mon idée est de les décrire principes au fur et à mesure que l'on avance dans les billets. Dans ce billet et le suivant nous souhaitons représenter graphiquement les instance EMF ActionType par des figures carrées et les instances EMF StateType par des ellipses. Ce billet et le suivant mettront en avant le concept Model View Controller (MVC) sur lequel GEF est basé.
GEF Programmer's Guide propose une vue d'ensemble GEF que j'aime bien :

Ce qui nous intéresse dans ce schéma c'est la partie de droite qui met en évidence le concept Model View Controller (MVC) sur lequel GEF est basé:
Concrètement voici en GEF comment sont implémentées les 3 couches MVC :
Le contrôleur joue le rôle d'édition.
Chacune des instances du modèle qui doit être représentée graphiquement (instance EMF WorkflowType, ActionType, StateType) est gérée par une instance controleur EditPart. Un EditPart est associé aussi à une instance IFigure (interface Draw2d qui représente une figure visuelle). L'instanciation des contrôleur des EditPart s'effectue via une factory d'Editart (EditPartFactory) que nous devons implémenter. Cette factory permet d'indiquer la classe EditPart à instancier pour une instance du modèle donné.
Voici un schéma qui met en évidence la factory d'EditPart :

Ce schéma montre bien qu'une instance EMF (Model) est associé à une instance EditPart (Contrôleur) qui est elle même est associée à une instance IFigure (Vue). Dans ce schéma nous pouvons voir la notion de Graphicalviewer qui utilise la factory d'EditPart. Il stocke les EditPart crés par la factory d'EditPart. Pour conclure, les concepts GEF abordés dans ce billet seront :
Voici le schéma ci-dessus personnalisé avec notre modèle EMF WorkflowType :

Ce schéma met en évidence les différentes classes que nous allons créer :
Dans ce billet nous souhaitons représenter graphiquement uniquement l'instance EMF WorkflowType avec un cadre rouge :

Ici la couche modèle est représentée par notre instance EMF WorkflowType.
La couche vue est représentée par l'interface Draw2d org.eclipse.draw2d.IFigure. Il existe une classe d'implémentation de cette interface org.eclipse.draw2d.Figure qui implémente la plupart des méthodes de l'interface IFigure. GEF conseille d'utiliser cette classe au lieu d'implémenter soit même les méthodes de l'interface, ce qui permet d'assurer une compatibilité avec les versions futurs de GEF au cas où l'interface ajoute/supprime de nouvelle méthode. GEF conseille de suivre ce principe. En fait il est valable pour n'importe quel API.
Créer la classe org.example.workflow.presentation.graphical.figures.WorkflowTypeFigure comme suit :
package org.example.workflow.presentation.graphical.figures;
import org.eclipse.draw2d.Figure;
public class WorkflowTypeFigure extends Figure {
}
A ce stade cette classe n'affichera rien.
La couche contrôleur est représentée par l'interface GEF org.eclipse.gef.EditPart. GEF propose la classe abstraite org.eclipse.gef.editparts.AbstractGraphicalEditPart qui implémente la plupart des méthodes de l'interface org.eclipse.gef.EditPart. GEF conseille encore d'utiliser cette classe abstraite pour les mêmes raisons décrites ci dessus (assurer la compabilité futur avec l'API).
Créer la classe org.example.workflow.presentation.graphical.parts.WorkflowTypePart comme suit :
package org.example.workflow.presentation.graphical.parts;
import org.eclipse.draw2d.IFigure;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.example.workflow.presentation.graphical.figures.WorkflowTypeFigure;
public class WorkflowTypePart extends AbstractGraphicalEditPart {
@Override
protected IFigure createFigure() {
return new WorkflowTypeFigure();
}
@Override
protected void createEditPolicies() {
}
}
La méthode abstraite AbstractGraphicalEditPart#createFigure() est implémentée dans notre cas pour retourner une instance de la Vue WorkflowTypeFigure.
La méthode abstraite AbstractGraphicalEditPart#createEditPolicies() doit aussi être aussi implémentée. Pour l'instant elle est vode. Nous reviendront plus tard sur la notion GEF d'EditPolicies.
A cette étape nous avons les 3 couches MVC implémentées (Model=EMF WorkflowType, View=WorkflowTypeFigure et Controleur=WorkflowTypePart).
Nous devons maintant orchestrer ces 3 couches via le GraphicalViewer GEF, autrement dit :
Une factory d'EditPart s'effectue en implémentant l'interface GEF org.eclipse.gef.EditPartFactory. Pour cela créer la classe org.example.workflow.presentation.graphical.parts.WorkflowPartFactory comme suit :
package org.example.workflow.presentation.graphical.parts;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.EditPartFactory;
import org.example.workflow.model.WorkflowType;
public class WorkflowPartFactory implements EditPartFactory {
public static final EditPartFactory INSTANCE = new WorkflowPartFactory();
public EditPart createEditPart(EditPart context, Object modelElement) {
// get EditPart for model element
EditPart part = getPartForElement(modelElement);
// store model element in EditPart
part.setModel(modelElement);
return part;
}
/**
* Maps an object to an EditPart.
*
* @throws RuntimeException
* if no match was found (programming error)
*/
private EditPart getPartForElement(Object modelElement) {
if (modelElement instanceof WorkflowType) {
return new WorkflowTypePart();
}
throw new RuntimeException("Can't create part for model element: "
+ ((modelElement != null) ? modelElement.getClass().getName()
: "null"));
}
}
La méthode EditPartFactory#createEditPart(EditPart context, Object modelElement) est implémentée pour instancier une instance EditPart WorkflowTypePart dans le cas ou le modèle modelElement reçu est une instance EMF WorkflowType. Dans cette factory nous renseignons aussi le modèle EMF à l'EditPart créé via sa méthode EditPart#setModel(Object model)
Cette factory est utilisée par le GraphicalViewer. Ce dernier gère un registry qui permet de retrouver l'EditPart associé à un modèle via la Map<Model, EditPart> accéssible par la méthode GraphicalViewer#getEditPartRegistry(). La factory d'EditPart est sollicitée que quand le GraphicalViewer ne trouve pas d'EditPart associé au modèle dans son registry. Dans le cas ou la factory d'EditPart est sollicitée, le GraphicalViewer met ensuite à jour son registry avec son EditPart instancié.
La factory d'EditPart peut être un singleton, ceci se concrétise par le code :
public static final EditPartFactory INSTANCE = new WorkflowPartFactory();
A cette étape nous pouvons renseigner au GraphicalViewer de l'editor GEF la factory d'EditPart WorkflowPartFactory via la méthode GraphicalViewer#setEditPartFactory(EditPartFactory factory).
Une bonne pratique pour effectuer cette configuration est de le faire dans la méthode GraphicalEditor#configureGraphicalViewer() . Le GraphiCalViewer est une propriété de l'éditor GEF accéssible via la méthode protected GraphicalEditor#getGraphicalViewer().
Pour cela modifier la classe GraphicalWorkflowEditor en surchargeant la méthode configureGraphicalViewer() comme suit :
Modifier classe GraphicalWorkflowEditor
@Override
protected void configureGraphicalViewer() {
super.configureGraphicalViewer();
GraphicalViewer viewer = getGraphicalViewer();
viewer.setEditPartFactory(WorkflowPartFactory.INSTANCE);
}
A cette étape nous pouvons renseigner au GraphicalViewer de l'editor GEF notre modèle EMF racine WorkflowType à utiliser via la méthode GraphicalViewer#setContents(Object contents). Une bonne pratique pour effectuer ceci est de le faire dans la méthode GraphicalEditor#initializeGraphicalViewer().
Notre modèle EMF est chargé dans l'editor MultiPart WorkflowEditor et pas dans l'editor GEF GraphicalWorkflowEditor (contrairement aux examples GEF). Il faut donc dans un premier temps renseigner ce modèle dans notre editor GEF GraphicalWorkflowEditor. Une solution simple serait de le passer dans le constructeur de GraphicalWorkflowEditor, mais j'ai préféré passer l'editor WorkflowEditor au constructeur (car nous en aurrons besoin dans la suite de nos billets). A partir de l'editor WorkflowEditor nous pouvons ensuite récupérer l'instance EMF via l'EditingDomain EMF.
Pour cela, modifier le constructeur de la classe GraphicalWorkflowEditor pour renseigner l'editor WorkflowEditor comme suit :
...
private IEditorPart parent;
public GraphicalWorkflowEditor(IEditorPart parent) {
super.setEditDomain(new DefaultEditDomain(this));
this.parent = parent;
}
Modifer le code de la méthode WorkflowEditor#createAndAddGraphicalPage() pour renseigner l'instance WorkflowEditor au constructeur de GraphicalWorkflowEditor :
private void createAndAddGraphicalPage() {
GraphicalWorkflowEditor graphicalPage = new GraphicalWorkflowEditor(this);
...
Modifier ensuite la classe GraphicalWorkflowEditor pour récupérer l'instance EMF du multi-part WorkflowEditor et le renseigner au GraphicalViewer comme suit :
...
@Override
protected void initializeGraphicalViewer() {
super.initializeGraphicalViewer();
GraphicalViewer viewer = getGraphicalViewer();
viewer.setContents(getWorkflow()); // set the contents of this
// editor
}
protected WorkflowType getWorkflow() {
EditingDomain editingDomain = ((IEditingDomainProvider) parent)
.getEditingDomain();
Resource resource = editingDomain.getResourceSet().getResources()
.get(0);
return (WorkflowType) resource.getContents().get(0);
}
...
Voici la classe GraphicalWorkflowEditor en entiere :
package org.example.workflow.presentation.graphical;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.domain.IEditingDomainProvider;
import org.eclipse.gef.DefaultEditDomain;
import org.eclipse.gef.GraphicalViewer;
import org.eclipse.gef.palette.PaletteRoot;
import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
import org.eclipse.ui.IEditorPart;
import org.example.workflow.model.WorkflowType;
import org.example.workflow.presentation.graphical.parts.WorkflowPartFactory;
public class GraphicalWorkflowEditor extends GraphicalEditorWithFlyoutPalette {
private IEditorPart parent;
public GraphicalWorkflowEditor(IEditorPart parent) {
super.setEditDomain(new DefaultEditDomain(this));
this.parent = parent;
}
@Override
protected void configureGraphicalViewer() {
super.configureGraphicalViewer();
GraphicalViewer viewer = getGraphicalViewer();
viewer.setEditPartFactory(WorkflowPartFactory.INSTANCE);
}
@Override
protected void initializeGraphicalViewer() {
super.initializeGraphicalViewer();
GraphicalViewer viewer = getGraphicalViewer();
viewer.setContents(getWorkflow()); // set the contents of this
// editor
}
protected WorkflowType getWorkflow() {
EditingDomain editingDomain = ((IEditingDomainProvider) parent)
.getEditingDomain();
Resource resource = editingDomain.getResourceSet().getResources()
.get(0);
return (WorkflowType) resource.getContents().get(0);
}
@Override
protected PaletteRoot getPaletteRoot() {
return null;
}
@Override
public void doSave(IProgressMonitor monitor) {
}
}
Avec cette solution on s'interdit de pouvoir utiliser GraphicalWorkflowEditor dans un éditeur unique (sans être contenu par le multi-part). Dans tous les examples GEF, c'est l'editor GEF qui charge le modèle. Dans notre cas, le chargement du modèle s'effectue dans un autre éditor (editor multi-part WorkflowEditor). Un autre solution aurrait été de passer le modèle EMF de l'editor multi-part WorkflowEditor a l'editor GEF GraphicalWorkflowEditor dans la méthode WorkflowEditor#createAndAddGraphicalPage() via le code :
WorkflowType emfWorkflowType = ....
GraphicalViewer viewer = GraphicalWorkflowEditor.getAdapter(GraphicalViewer.class).
viewer.setContent(emfWorkflowType)
On peut remarquer que pour récupérer le GraphicalViewer on passe par la méthode GraphicalEditor#getAdapter(Object o) (voir article Adapters) et pas par la méthode GraphicalEditor#getGraphicalEditor() car celle-ci est en protected.
Le choix de passer le multi-part dans le constructeur GraphicalWorkflowEditor est discutable, mais les raisons de mon choix sont les suivantes :
A cette étape si vous relancez le plugin, la page GEF affichera une page blanche. Pour vérifier que tout le mécanisme GEF est bien mis en place, nous allons modifier la vue qui s'occupe de représenter l'instance EMF WorkflowType par une bordure rouge.
Pour cela modifier la classe WorkflowTypeFigure pour ajouter une bordure rouge a la vue qui représente l'instance EMF WorkflowType comme suit :
package org.example.workflow.presentation.graphical.figures;
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Figure;
import org.eclipse.draw2d.LineBorder;
public class WorkflowTypeFigure extends Figure {
public WorkflowTypeFigure() {
super.setBorder(new LineBorder(ColorConstants.red, 2));
}
}
Relancer le plugin et vous pourrez constater qu'il y a un cadre rouge qui s'affiche.

Tous les concepts GEF (EditPart, Figure Draw2d, GraphicalViewer, EditPartFactory) sont bien initialisés! :
On peut aussi vérifier que notre initialisation est effectuée correctement en positionnant des points d'arret dans notre code pour suivre en debug le processus :
Mettez les 4 points d'arret suivants :
Relancez le plugin en mode debug.
Le debug s'arrete dans la méthode GraphicalWorkflowEditor#configureGraphicalViewer() :

On peut constater que la première chose qui est effectuée est la configuration du GraphicalViewer. Dans notre cas nous configurons a factory d'EditPart à utiliser.
Le debug s'arrete ensuite dans la méthode GraphicalWorkflowEditor#initializeGraphicalViewer() :

On peut constater que l'initialisation du GraphicalViewer. est effectuée apres sa configuration. Dans notre cas nous renseignons le modèle EMF WorkflowType au GraphicalViewer. La couche Model est initialisée.
Le debug s'arrete ensuite dans la méthode WorkflowPartFactory#createEditPart(EditPart context, Object modelElement) :

Ici la couche Controlleur est initialisée.
Le debug s'arrete ensuite dans la méthode WorkflowTypePart#createFigure() :

Ici la couche View est initialisée.
Je ne vais pas détailler Draw2d dans cette section car le sujet est vaste et GEF encapsule déja pas mal de problématiques. Draw2D peut être utilisé indépendemment de GEF et fournit la possibilité de :
Draw2D Developers Guide propose une vue d'ensemble Draw2d que j'aime bien :
Ce schéma montre que Draw2d est basé sur un Canvas SWT, containeur SWT où il est possible de déssiner. Draw2d fournit et utilise la classe org.eclipse.draw2d.LightweightSystem qui fait le lien entre le Canvas SWT et les figures Draw2d. Cette classe s'occupe par exemple de dispatcher les évenements SWT (ex : positionnement, click de la souris SWT effectuée sur le Canvas SWT) au figures Draw2d (pour par exemple sélectionner la figure qui aurrait été cliquée).
La classe abstraite GEF org.eclipse.gef.ui.parts.GraphicalEditor que nous avons utilisés pour notre éditeur de workflow (GraphicalEditorWithFlyoutPalette étend GraphicalEditor) et gère en interne une instance LightweightSystem. Plus exactement son viewer GraphicalViewer est basé sur la classe ScrollingGraphicalViewer qui étend GraphicalViewerImpl et qui gère en interne une instance LightweightSystem..
Une figure Draw2D implémente l'interface org.eclipse.draw2d.IFigure. Draw2d fournit la classe abtraite org.eclipse.draw2d.Figure qui implémente IFigure. Draw2d fournit plusieurs figures par défaut (qui héritent de la classe abstraite Figure) comme :
Mais il est conseillé de créer ou d'étendre les figures par défaut pour avoir des figures plus complexes (voir exemple Display a UML Diagram using Draw2D).
Un gestionnaire de layout Draw2d est utilisé pour positionner et dimensionner les figures enfants d'unn figure parent. Il implémente l'interface org.eclipse.draw2d.LayoutManager. La plupart des layout étende la classe abstraite org.eclipse.draw2d.AbstractLayout (qui implémente LayoutManager).
Il n'y a malheureusement pas beaucoup de documentations sur Draw2d. Voici les liens que j'ai pu trouver concernant Draw2d :
Vous devez être identifié pour poster un commentaire.
| Lun | Mar | Mer | Jeu | Ven | Sam | Dim |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 |
Copyright © 2000-2012 - www.developpez.com