Tutorial: GEF (Graphical Editing Framework) [Part 3]
Par Jean Charles MAMMANA, mercredi 23 mai 2007 à 18:26 :: Programmation :: #9 :: rss
Dans cette troisième partie, on va pouvoir agir sur le graph : sélectionner une box, la déplacer, la redimensionner...
GEF permet une représentation visuelle d'un modèle d'objets. Dans notre exemple (simple), nous avons en haut notre Entreprise qui contient une liste de services qui eux mêmes contiennent des employées.
GEF s'appuie sur une architecture orienté "Modèle - Vue - Contrôleur (MVC)". D'ailleur il faudrait plutôt dire "Modèle - Contrôleur - Vue"... C'est à dire qu'on fait une distinction entre notre modèle (notre entreprise) et notre vue (notre graph avec les boites...). Le contrôleur est au milieu des deux et arbitre le match! C'est lui qui pilote la vue en fonction du modèle et c'est lui qui modifie le modèle en fonction des actions effectué sur la vue...
Les packages java dans ce tutorial représentent justement les class selon leur emplacement dans le modèle MVC.
Le wiki de GEF (http://wiki.eclipse.org/index.php/GEF_Description) explique plus en détails le fonctionnement de GEF (Cela dit, vous avez tout mon respect si vous comprenez tout du premier coup...).
Dans cette partie du tutorial, nous allons voir comment agir sur le graph, comment le contrôleur exécute les commandes, comment les commandes modifient le modèle et comment le modèle informe la vue qu'il a changé pour qu'elle puisse se mettre a jours. Ne partez pas en courant, c'est plus simple que ca en a l'air...
Nous voulons pouvoir déplacer nos services dans l'entreprise et les employées dans leur service. Il faut pour cela créer une Commande pour Employe et une pour Service. (On cree les class dans un nouveau package "tutogef.commands")
Avant cela, on crée rapidement une petite class abstraite pour éviter certains problèmes plus tard :
Puis on écrit nos deux class qui héritent de notre class abstraite pour Employe et Service (c'est nos commandes.)
Ces commandes vont être appelé par une EditPolicy qui sera après appliqué aux editparts de nos objets.
On crée cette class comme ceci dans un autre package qu'on nomme "tutogef.editpolicies" :
Maintenant il faut appliquer cette EditPolicy aux editparts qui nous intéressent.
Dans les class EntreprisePart et ServicePart il faut ajouter cette ligne dans la méthode createEditPolicies():
Lancez le programme un peu pour voir :)

On peut sélectionner une boite (même plusieurs avec ctrl), On peut la déplacer et la redimensionner. Mais ?!!
Elles ne veulent pas rester en place et reviennent à leur place d'origine.. Pourquoi ?
la boucle n'est pas encore bouclé : la vue update le modèle, mais le modèle n'update pas la vue (ca fait louche dit comme ca!)...
Il faut mettre en place des listeners sur nos editparts pour qu'ils puissent détecter quand le modèle change et mettre a jours la vue.
On va commencer par modifier la class Node pour que celle-ci lève des évents lors de changements sur ces propriétés.
Puis on modifie la méthode setLayout() pour qu'elle "fire" une propriété quand on l'appelle.
A partir de maintenant, quand un élément de notre modèle va vouloir se déplacer, il va lancer un évent. Mais pour le moment, personne ne va le recevoir.
On va écrire une class abstraite qui va se charger de la gestion des évents. puis on modifiera nos editparts pour qu'ils héritent de cette class.
Puis pour les class EmployePart, ServicePart et EntreprisePart, on change la déclaration des class par :
Il faut aussi ajouter cette méthode à ces trois class :
C'est cette méthode qui recevra l'évent lancé tout à l'heure par firePropertyChange().
Si l'évent correspond a une propriété PROPERTY_LAYOUT. Alors on rafraichie l'affichage de l'élément du modèle qui a changé.
Lancez le programme et déplacez un service ou un employée :)

Vous pouvez télécharger l'archive du projet ICI
GEF s'appuie sur une architecture orienté "Modèle - Vue - Contrôleur (MVC)". D'ailleur il faudrait plutôt dire "Modèle - Contrôleur - Vue"... C'est à dire qu'on fait une distinction entre notre modèle (notre entreprise) et notre vue (notre graph avec les boites...). Le contrôleur est au milieu des deux et arbitre le match! C'est lui qui pilote la vue en fonction du modèle et c'est lui qui modifie le modèle en fonction des actions effectué sur la vue...
Les packages java dans ce tutorial représentent justement les class selon leur emplacement dans le modèle MVC.
Le wiki de GEF (http://wiki.eclipse.org/index.php/GEF_Description) explique plus en détails le fonctionnement de GEF (Cela dit, vous avez tout mon respect si vous comprenez tout du premier coup...).
Dans cette partie du tutorial, nous allons voir comment agir sur le graph, comment le contrôleur exécute les commandes, comment les commandes modifient le modèle et comment le modèle informe la vue qu'il a changé pour qu'elle puisse se mettre a jours. Ne partez pas en courant, c'est plus simple que ca en a l'air...
Nous voulons pouvoir déplacer nos services dans l'entreprise et les employées dans leur service. Il faut pour cela créer une Commande pour Employe et une pour Service. (On cree les class dans un nouveau package "tutogef.commands")
Avant cela, on crée rapidement une petite class abstraite pour éviter certains problèmes plus tard :
package tutogef.commands;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;
public abstract class AbstractLayoutCommand extends Command{
public abstract void setConstraint(Rectangle rect);
public abstract void setModel(Object model);
}
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;
public abstract class AbstractLayoutCommand extends Command{
public abstract void setConstraint(Rectangle rect);
public abstract void setModel(Object model);
}
Puis on écrit nos deux class qui héritent de notre class abstraite pour Employe et Service (c'est nos commandes.)
package tutogef.commands;
import org.eclipse.draw2d.geometry.Rectangle;
import tutogef.model.Employe;
public class EmployeChangeLayoutCommand extends AbstractLayoutCommand {
private Employe model;
private Rectangle layout;
public void execute() {
model.setLayout(layout);
}
public void setConstraint(Rectangle rect) {
this.layout = rect;
}
public void setModel(Object model) {
this.model = (Employe)model;
}
}
import org.eclipse.draw2d.geometry.Rectangle;
import tutogef.model.Employe;
public class EmployeChangeLayoutCommand extends AbstractLayoutCommand {
private Employe model;
private Rectangle layout;
public void execute() {
model.setLayout(layout);
}
public void setConstraint(Rectangle rect) {
this.layout = rect;
}
public void setModel(Object model) {
this.model = (Employe)model;
}
}
package tutogef.commands;
import org.eclipse.draw2d.geometry.Rectangle;
import tutogef.model.Service;
public class ServiceChangeLayoutCommand extends AbstractLayoutCommand {
private Service model;
private Rectangle layout;
public void execute() {
model.setLayout(layout);
}
public void setConstraint(Rectangle rect) {
this.layout = rect;
}
public void setModel(Object model) {
this.model = (Service)model;
}
}
import org.eclipse.draw2d.geometry.Rectangle;
import tutogef.model.Service;
public class ServiceChangeLayoutCommand extends AbstractLayoutCommand {
private Service model;
private Rectangle layout;
public void execute() {
model.setLayout(layout);
}
public void setConstraint(Rectangle rect) {
this.layout = rect;
}
public void setModel(Object model) {
this.model = (Service)model;
}
}
Ces commandes vont être appelé par une EditPolicy qui sera après appliqué aux editparts de nos objets.
On crée cette class comme ceci dans un autre package qu'on nomme "tutogef.editpolicies" :
package tutogef.editpolicies;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gef.requests.CreateRequest;
import tutogef.commands.AbstractLayoutCommand;
import tutogef.commands.EmployeChangeLayoutCommand;
import tutogef.commands.ServiceChangeLayoutCommand;
import tutogef.part.EmployePart;
import tutogef.part.ServicePart;
public class AppEditLayoutPolicy extends XYLayoutEditPolicy {
@Override
protected Command createChangeConstraintCommand(EditPart child, Object constraint) {
AbstractLayoutCommand command = null;
if (child instanceof EmployePart) {
command = new EmployeChangeLayoutCommand();
} else if (child instanceof ServicePart) {
command = new ServiceChangeLayoutCommand();
}
command.setModel(child.getModel());
command.setConstraint((Rectangle)constraint);
return command;
}
@Override
protected Command getCreateCommand(CreateRequest request) {
// TODO Auto-generated method stub
return null;
}
}
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
import org.eclipse.gef.requests.CreateRequest;
import tutogef.commands.AbstractLayoutCommand;
import tutogef.commands.EmployeChangeLayoutCommand;
import tutogef.commands.ServiceChangeLayoutCommand;
import tutogef.part.EmployePart;
import tutogef.part.ServicePart;
public class AppEditLayoutPolicy extends XYLayoutEditPolicy {
@Override
protected Command createChangeConstraintCommand(EditPart child, Object constraint) {
AbstractLayoutCommand command = null;
if (child instanceof EmployePart) {
command = new EmployeChangeLayoutCommand();
} else if (child instanceof ServicePart) {
command = new ServiceChangeLayoutCommand();
}
command.setModel(child.getModel());
command.setConstraint((Rectangle)constraint);
return command;
}
@Override
protected Command getCreateCommand(CreateRequest request) {
// TODO Auto-generated method stub
return null;
}
}
Maintenant il faut appliquer cette EditPolicy aux editparts qui nous intéressent.
Dans les class EntreprisePart et ServicePart il faut ajouter cette ligne dans la méthode createEditPolicies():
protected void createEditPolicies() {
installEditPolicy(EditPolicy.LAYOUT_ROLE, new AppEditLayoutPolicy());
}
installEditPolicy(EditPolicy.LAYOUT_ROLE, new AppEditLayoutPolicy());
}
Lancez le programme un peu pour voir :)

On peut sélectionner une boite (même plusieurs avec ctrl), On peut la déplacer et la redimensionner. Mais ?!!
Elles ne veulent pas rester en place et reviennent à leur place d'origine.. Pourquoi ?
la boucle n'est pas encore bouclé : la vue update le modèle, mais le modèle n'update pas la vue (ca fait louche dit comme ca!)...
Il faut mettre en place des listeners sur nos editparts pour qu'ils puissent détecter quand le modèle change et mettre a jours la vue.
On va commencer par modifier la class Node pour que celle-ci lève des évents lors de changements sur ces propriétés.
public class Node {
private PropertyChangeSupport listeners;
public static final String PROPERTY_LAYOUT = "NodeLayout";
(...)
public Node(){
(...)
this.listeners = new PropertyChangeSupport(this);
}
(...)
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.addPropertyChangeListener(listener);
}
public PropertyChangeSupport getListeners() {
return listeners;
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
listeners.removePropertyChangeListener(listener);
}
}
private PropertyChangeSupport listeners;
public static final String PROPERTY_LAYOUT = "NodeLayout";
(...)
public Node(){
(...)
this.listeners = new PropertyChangeSupport(this);
}
(...)
public void addPropertyChangeListener(PropertyChangeListener listener) {
listeners.addPropertyChangeListener(listener);
}
public PropertyChangeSupport getListeners() {
return listeners;
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
listeners.removePropertyChangeListener(listener);
}
}
Puis on modifie la méthode setLayout() pour qu'elle "fire" une propriété quand on l'appelle.
public void setLayout(Rectangle newLayout) {
Rectangle oldLayout = this.layout;
this.layout = newLayout;
getListeners().firePropertyChange(PROPERTY_LAYOUT, oldLayout, newLayout);
}
Rectangle oldLayout = this.layout;
this.layout = newLayout;
getListeners().firePropertyChange(PROPERTY_LAYOUT, oldLayout, newLayout);
}
A partir de maintenant, quand un élément de notre modèle va vouloir se déplacer, il va lancer un évent. Mais pour le moment, personne ne va le recevoir.
On va écrire une class abstraite qui va se charger de la gestion des évents. puis on modifiera nos editparts pour qu'ils héritent de cette class.
package tutogef.part;
import java.beans.PropertyChangeListener;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import tutogef.model.Node;
public abstract class AppAbstractEditPart extends AbstractGraphicalEditPart implements PropertyChangeListener {
public void activate() {
super.activate();
((Node) getModel()).addPropertyChangeListener(this);
}
public void deactivate() {
super.deactivate();
((Node) getModel()).removePropertyChangeListener(this);
}
}
import java.beans.PropertyChangeListener;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import tutogef.model.Node;
public abstract class AppAbstractEditPart extends AbstractGraphicalEditPart implements PropertyChangeListener {
public void activate() {
super.activate();
((Node) getModel()).addPropertyChangeListener(this);
}
public void deactivate() {
super.deactivate();
((Node) getModel()).removePropertyChangeListener(this);
}
}
Puis pour les class EmployePart, ServicePart et EntreprisePart, on change la déclaration des class par :
//Remplacez XXXXPart par le nom de la class.
public class XXXXPart extends AppAbstractEditPart{
(...)
}
public class XXXXPart extends AppAbstractEditPart{
(...)
}
Il faut aussi ajouter cette méthode à ces trois class :
@Override
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(Node.PROPERTY_LAYOUT)) refreshVisuals();
}
public void propertyChange(PropertyChangeEvent evt) {
if (evt.getPropertyName().equals(Node.PROPERTY_LAYOUT)) refreshVisuals();
}
C'est cette méthode qui recevra l'évent lancé tout à l'heure par firePropertyChange().
Si l'évent correspond a une propriété PROPERTY_LAYOUT. Alors on rafraichie l'affichage de l'élément du modèle qui a changé.
Lancez le programme et déplacez un service ou un employée :)

Vous pouvez télécharger l'archive du projet ICI

Commentaires
1. Le jeudi 16 août 2007 à 16:01, par Jean-Philippe
2. Le jeudi 16 août 2007 à 17:46, par Psykokwak
3. Le dimanche 24 mai 2009 à 10:51, par Skiv
4. Le dimanche 24 mai 2009 à 11:10, par Skiv
5. Le mercredi 31 mars 2010 à 13:04, par kim
6. Le mardi 27 juillet 2010 à 20:42, par pggp
7. Le mardi 27 juillet 2010 à 21:22, par pggp
8. Le mercredi 28 juillet 2010 à 00:00, par pggp
Ajouter un commentaire