(publié le 13/6/2007 -
Versions utilisées : Eclipse SDK 3.2.2 - JDK 5.0)
Tutorial : développer des plug-ins Eclipse (2ème
partie)
Que ce soit pour enrichir l'environnement de développement
ou bien pour développer des applications Eclipse RCP, la
notion de plugin est centrale.
Les plugins sont la concrétisation des
deux principaux objectifs des concepteurs d'Eclipse : modularité
et extensibilité.
Dans la
première partie de ce tutorial nous avons vu comment
créer des plugins en utilisant l'outillage intégré
(le PDE) et étudié la façon dont se concrétisait
la modularité des applications basées sur le framework
Eclipse. Pour cette seconde partie, nous allons nous focaliser sur
les fonctionnalités d'extensibilité dont peuvent bénéficier
tous les plugins Eclipse.
|
|
Eclipse
: un framework pour faire des applications extensibles
Le besoin
d'extensibilité
Le but initial du projet Eclipse était de fournir
un socle, écrit en Java, pour la création d'environnements
de développement. Depuis 2004, cet objectif a été
étendu en prenant en compte l'utilisation du framework Eclipse
pour tous les types d'applications (applications
Eclipse RCP).
Pour les concepteurs d'Eclipse, l'utilisation type du framework Eclipse
était qu'une équipe, maîtraisant un langage de développement
particulier, s'appuie sur le framework Eclipse pour fournir un environnement
de développement propre à ce langage et qu'ensuite d'autres
développeurs enrichissent cet environnement avec des briques supplémentaires
parfaitement intégrées.
Les plugins constituant le framework sont pour la plupart
extensibles et tout développeur de plugin peut rendre son plugin
extensible.
En résumé, le framework Eclipse propose un mécanisme
générique d'extensibilité : tout plugin peut se
déclarer extensible et tout autre plugin pour étendre un
plugin extensible.
Principes
d'extensibilité d'Eclipse
L'extensibilité du framework Eclipse se concrétise
par deux notions principales : les points d'extension et les extensions.
Un plugin qui veut être extensible doit déclarer
un point d'extension dans son fichier plugin.xml, il indiquera
notamment le nom d'un fichier au format XML Schema qui décrit la
grammaire XML du point d'extension. Les plugins qui veulent se brancher
sur ce point d'extension vont déclarer une extension dans
leur fichier plugin.xml (en respectant la grammaire associée au
point d'extension).
Pour la plupart des points d'extension, le plugin définissant
le point d'extension fournit aussi une interface que devront implémenter
les plugins contributeurs.
Illustration
avec un point d'extension d'Eclipse : les pages de préférences
Avant d'aborder les aspects pratiques illustrons les
principes de point d'extension et d'extension en étudiant le fonctionnement
du point d'extension qui permet d'ajouter des pages de préférences
dans la fenêtre des préférences d'Eclipse :
Le plugin
'org.eclipse.ui', fourni par le framework Eclipse, définit
un point d'extension nommé 'org.eclipse.ui.preferencePages'.
Le fichier 'preferencePages.exsd' contient la grammaire associée
à ce point d'extension, en voici un extrait :
<element name="page">
<complexType>
<attribute
name="id" type="string"
use="required" />
<attribute
name="name" type="string"
use="required" />
<attribute
name="class" type="string"
use="required">
<annotation>
<appInfo>
<meta.attribute
kind="java"
basedOn="org.eclipse.jface.preference.PreferencePage:
org.eclipse.ui.IWorkbenchPreferencePage"/>
</appInfo>
</annotation>
</attribute>
<attribute
name="category" type="string"/>
</complexType>
</element>
|
Cette grammaire définit un élément XML nommé
'page' qui possède quatre attributs : 'id', 'name',
'category' et 'class'.
Pour l'attribut 'class', l'interface à implémenter
est indiquée ('org.eclipse.ui.IWorkbenchPreferencePage')
et dans ce cas précis une superclasse est aussi proposée
('org.eclipse.jface.preference.PreferencePage').
Les attributs 'id', 'name' et 'class' sont obligatoires.
Pour ajouter
une page de préférences, un plugin doit donc définir,
dans son fichier plugin.xml, une extension sur le point d'extension 'org.eclipse.ui.preferencePages'
<?xml version="1.0"
encoding="UTF-8"?>
<?eclipse version="3.2"?>
<plugin>
<extension
point="org.eclipse.ui.preferencePages">
<page
id="com.eclipsetotale.tutorial.pagepreference.page1"
name="Tutorial
développement de plugin"
class="com.eclipsetotale.tutorial.pagepreference.TutorialPreferencePage"
/>
</extension>
</plugin>
|
Le plugin
doit aussi fournir la classe indiquée par l'attribut 'class'.
package
com.eclipsetotale.tutorial.pagepreference;
import org.eclipse.jface.preference.PreferencePage;
...
public
class TutorialPreferencePage extends
PreferencePage
implements IWorkbenchPreferencePage
{
protected
Control createContents(Composite parent) {
Composite
composite = new Composite(parent, SWT.NONE);
Color
jaune = parent.getDisplay().getSystemColor(SWT.COLOR_YELLOW);
composite.setBackground(jaune);
return
composite;
}
public
void init(IWorkbench workbench) {
}
}
|
Cette sous-classe de 'org.eclipse.jface.preference.PreferencePage'
a pour responsabilité la gestion du contenu spécifique à
la page de préférences (la partie en jaune sur cette capture
d'écran) :
Le plugin
qui gère les pages de préférences construit le cadre
graphique et utilisent des APIs du framework Eclipse pour :
- connaître la liste des extensions définies sur le point
d'extension 'org.eclipse.ui.preferencePages'.
- récupérer la valeur des attributs (id, name, class et
category).
- instancier la classe associée à chaque page de préférences
et appeler la méthode createContents pour déléguer
la gestion de la partie en jaune.
Des deux notions, point d'extension et extension, découlent
deux niveaux de mise en oeuvre de l'extensibilité du framework
Eclipse :
- l'utilisation de points d'extension définis par d'autres développeurs.
Le cas le plus courant étant de se brancher sur les points d'extensions
prédéfinis d'Eclipse (vue, éditeur, perspectives,
menus, pages de préférences, assistants, ...).
- la déclaration de nouveaux points d'extension sur lesquels
s'appuieront d'autres développeurs.
Dans la suite de ce tutorial nous allons mettre en pratique
ces deux niveaux.
Utiliser
un point d'extension
Le framework Eclipse propose de nombreux points d'extension,
notamment pour assurer l'extensibilité de son interface graphique.
Nous allons étudier les étapes permettant l'ajout d'une
nouvelle vue à l'environnement Eclipse.
Cette vue affichera l'heure, pour ce faire nous allons
réutiliser les plugins développés dans la première
partie de ce tutorial.
Définition
d'une extension
Créer un nouveau
plugin nommé 'com.eclipsetotale.tutorial.horloge' (ne pas
utiliser de template).
Dans l'éditeur
de fichiers manifestes de ce plugin, sélectionner l'onglet 'Extensions'.
Dans cet onglet cliquer sur le bouton 'Add...' : la liste de tous
les points d'extension est présentée. Choisir le point d'extension
'org.eclipse.ui.views' :
L'objectif
de l'onglet 'Extensions' est de générer la définition
XML de l'extension dans le fichier plugin.xml. Sélectionner le
point d'extension et utiliser le menu contextuel pour ajouter un des éléments
XML déclarés dans la grammaire associée au point
d'extension, dans notre cas un élément de type 'view':
La partie
de gauche de l'éditeur permet de voir les attributs associés
à cet élément XML et d'indiquer leurs valeurs :
La définition
de notre extension est terminée, le code XML correspondant a été
généré dans le fichier plugin.xml :
<extension
point="org.eclipse.ui.views">
<view
id="com.eclipsetotale.tutorial.horloge.vue"
name="Horloge"
class="com.eclipsetotale.tutorial.horloge.HorlogeView"
/>
</extension>
|
Classe
associée à l'extension
L'étape
suivante consiste à créer la classe indiquée par
l'attribut 'class'. Le PDE fournit une aide sympathique : le libellé
de l'attribut 'class' est un lien hypertexte capable d'ouvrir l'assistant
de création de classe et de pré-remplir les champs (notamment
le nom de l'interface à implémenter et/ou de la classe à
étendre) :
La classe est créée avec le code suivant
:
package
com.eclipsetotale.tutorial.horloge;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
public class HorlogeView extends
ViewPart {
public HorlogeView()
{
// TODO
Auto-generated constructor stub
}
@Override
public void createPartControl(Composite
parent) {
// TODO
Auto-generated constructor stub
}
@Override
public void setFocus()
{
// TODO
Auto-generated constructor stub
}
}
|
Pour que
cette vue affiche l'heure, nous allons réutiliser le plugin 'com.eclipsetotale.tutorial.horloge.texte'.
Dans l'éditeur de fichiers manifestes du plugin 'com.eclipsetotale.tutorial.horloge',
sélectionner l'onglet 'Dependencies' et ajouter le plugin
'com.eclipsetotale.tutorial.horloge.texte' dans la section 'Required
plugins'.
Une fois cette opération effectuée, modifier
le code de la classe 'HorlogeView' pour lui faire afficher l'horloge.
La méthode à modifier est la méthode createPartControl,
cette méthode est appelée par Eclipse à l'ouverture
de la vue :
package
com.eclipsetotale.tutorial.horloge;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import
com.eclipsetotale.tutorial.horloge.texte.HorlogeTexte;
public class HorlogeView extends
ViewPart {
public HorlogeView()
{
}
@Override
public void createPartControl(Composite
parent) {
new
HorlogeTexte(parent);
}
@Override
public void setFocus()
{
}
}
|
Tester
en lançant l'environnement Eclipse de test. La vue n'est pas ouverte
automatiquement, il faut demander son affichage à partir du menu
'Window->Show view->Other ...', sélectionner la catégorie
'Other', la vue 'Horloge' doit s'y trouver :
Créer
un nouveau point d'extension
Définition
d'un point d'extension
La notion de point d'extension n'est pas réservée
aux plugins propres au framework Eclipse : tout plugin peut définir
ses propres points d'extension. La définition d'un nouveau point
d'extension passe essentiellement par la définition de la grammaire
XML associée. Le PDE propose un éditeur spécifique
pour créer et éditer ces grammaires qui sont au format XML
Schema.
Nous allons définir un point d'extension permettant à des
plugins de contribuer de nouveaux types d'horloge. Notre plugin 'com.eclipsetotale.tutorial.horloge'
aura donc la responsabilité de gérer la vue 'Horloge' mais
délèguera l'affichage à un des plugins contributeurs.
La première étape est de définir un point d'extension
sur lequel se brancheront les plugins implémentant une Horloge.
Déclaration
du point d'extension
L'onglet 'Extension points' de l'éditeur
de fichiers manifestes aide à la création de nouveaux points
d'extension. Ouvrir cet onglet pour le plugin 'com.eclipsetotale.tutorial.horloge'.
Le bouton 'Add...' ouvre un assistant permettant d'indiquer l'ID
du nouveau point d'extension (l'ID réel sera la concaténation
de l'ID du plugin et de la valeur de ce champ), son nom et le nom du fichier
qui contiendra la grammaire :
Définition de la
grammaire
Notre point d'extension doit permettre à des plugins
de contribuer de nouveaux types d'Horloge. Nous proposons donc que la
grammaire soit composée d'un élément XML nommé
'Horloge' comprenant trois attributs 'id', 'nom'
et 'classe'.
Cette grammaire doit être définie dans un
fichier XMLSchema avec une extension .exsd. Le fichier a été
créé automatiquement lors de l'étape précédente
et l'éditeur de fichiers .exsd a été ouvert sur notre
fichier. L'éditeur dispose de plusieurs onglets, se placer dans
l'onglet 'Definition' et utiliser le bouton 'Add Element'
pour créer un élément XML que nous nommerons 'Horloge'
:
Utiliser le bouton 'New
Attribute' pour créer les attributs 'id' et 'nom'.
Indiquer que ces attributs sont 'required' et de type 'string'
:
Créer ensuite l'attribut
'classe'. Spécifier qu'il est 'required' et de type
'java'. Cet attribut sera utilisé par les plugins contributeurs
pour indiquer le nom de la classe qui gère l'affichage de l'Horloge.
Pour que notre plugin (celui définissant le point d'extension)
puisse manipuler les classes fournies par les plugins contributeurs, nous
allons leur imposer une interface commune. Le nom de cette interface est
à indiquer dans le champ 'Implements' :
Ajouter l'interface dans
le plugin 'com.eclipsetotale.tutorial.horloge', son code est le
suivant
package
com.eclipsetotale.tutorial.horloge;
import org.eclipse.swt.widgets.Composite;
public interface Horloge {
public void afficher(Composite
parent);
}
|
Cette interface devra être
utilisable par les plugins dépendants : dans l'éditeur
de fichier manifestes, sélectionner l'onglet 'Runtime' et
ajouter le package 'com.eclipsetotale.tutorial.horloge' dans la
section 'Exported Packages'.
La grammaire d'un point
d'extension se compose systématiquement d'un élément
parent nommé 'extension'. Il nous faut indiquer la relation
entre l'élément 'extension' et notre élément
'Horloge'. Dans notre cas l'élément 'extension' peut contenir
plusieurs sous-éléments 'Horloge' (un plugin peut fournir
plusieurs types d'horloge), pour définir ce lien il faut utiliser
le menu contextuel sur l'élément extension et sélectionner
'New->Compositor->Sequence' :
Ouvrir ensuite le menu contextuel sur la séquence
et sélectionner 'New->Reference->Horloge' :
Sélectionner la référence vers l'élement 'Horloge'
et cocher la case 'Unbounded' dans la partie de gauche:
Utilisation
du point d'extension
Le point d'extension 'com.eclipsetotale.tutorial.horloge.horloges'
est maintenant accessible aux plugins souhaitant y contribuer. Dans notre
cas nous allons déclarer une extension sur ce point dans nos plugins
'com.eclipsetotale.tutorial.horloge.texte' et 'com.eclipsetotale.tutorial.horloge.nebula'.
Pour ces deux plugins,
ouvrir l'éditeur de fichiers manifestes, se placer dans l'onglet
'Extensions' et sélectionner le bouton 'Add...'.
Dans l'assistant décocher la case 'Show only extension points
from required plugins' et sélectionner le point d'extension
'com.eclipsetotale.tutorial.horloge.horloges' :
Répondre oui à la boîte de dialogue
proposant d'ajouter automatiquement le plugin 'com.eclipsetotale.tutorial.horloge'
comme pré-requis.
Sachant que précédemment
nous avions mis le plugin 'com.eclipsetotale.tutorial.horloge.texte'
comme pré-requis de 'com.eclipsetotale.tutorial.horloge',
nous avons maintenant un cylce dans les dépendances qui empêche
la compilation. Dans l'onglet 'Dependencies' de l'éditeur
de fichiers manifestes du plugin 'com.eclipsetotale.tutorial.horloge'
supprimer le plugin 'com.eclipsetotale.tutorial.horloge.texte'
de la liste 'Required plugins'.
Dans l'éditeur de
fichiers manifestes des plugins 'com.eclipsetotale.tutorial.horloge.texte'
et 'com.eclipsetotale.tutorial.horloge.nebula, utiliser le menu
contextuel sur le point d'extension pour ajouter un élément
'Horloge' :
Indiquer la valeur des attributs 'id', 'nom' et 'classe'
:
Faire
créer les classes 'HorlogeImpl' en sélectionnant
le lien hypertexte 'classe*'. Compléter le code de ces deux
classes :
package
com.eclipsetotale.tutorial.horloge.texte;
import org.eclipse.swt.widgets.Composite;
import com.eclipsetotale.tutorial.horloge.Horloge;
public class HorlogeImpl implements
Horloge {
public void afficher(Composite
parent) {
new
HorlogeTexte(parent);
}
}
|
package
com.eclipsetotale.tutorial.horloge.nebula;
import org.eclipse.swt.widgets.Composite;
import com.eclipsetotale.tutorial.horloge.Horloge;
public class HorlogeImpl implements
Horloge {
public void afficher(Composite
parent) {
new
HorlogeNebula(parent);
}
}
|
NB : pour éviter de modifier les classes HorlogeTexte
et HorlogeNebula nous avons créé des classes intermédiaires.
La solution la plus standard aurait été de sélectionner
directement les classes 'HorlogeTexte' et 'HorlogeNebula' dans les champs
'classe*' puis de modifier le code de ces classes pour leur faire
implémenter l'interface 'com.eclipsetotale.tutorial.horloge.Horloge'.
APIs
associées au point d'extension
A ce niveau nous avons défini un point d'extension
et deux extensions sur ce point. Le framework Eclipse gère un registre
des extensions, accessible par programmation. Le registre est alimenté
au lancement d'Eclipse lors de l'analyse des fichiers plugin.xml.
Pour finaliser nos plugins, nous allons compléter
le plugin 'com.eclipsetotale.tutorial.horloge' pour lui faire
afficher dans une page de préférences la liste des types
d'horloges disponibles (liste des extensions sur le point d'extension
'com.eclipsetotale.tutorial.horloge.horloges'). L'Horloge sélectionnée
dans la page de préférences sera celle affichée par
la vue 'HorlogeView'.
Création
de la page de préférences
Dans l'éditeur de fichiers manifestes du plugin
'com.eclipsetotale.tutorial.horloge', sélectionner l'onglet
'Extensions' puis ajouter une extension de type 'org.eclipse.ui.preferencePages'.
Créer un élément de type 'page' (via le menu
contextuel 'New') et renseigner ses attributs :
Le code de la classe associée à la page de préférences
est le suivant :
package
com.eclipsetotale.tutorial.horloge;
import java.util.Date;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Label;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPreferencePage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
public class PrefPage extends
org.eclipse.jface.preference.PreferencePage
implements
IWorkbenchPreferencePage {
private static final String
HORLOGE = "horloge";
private Combo comboHorloges;
@Override
protected Control
createContents(Composite parent) {
Composite content = new
Composite(parent, SWT.NONE);
content.setLayout(new
GridLayout(2, false));
(new
Label(content, SWT.NONE)).setText("Type
d'horloge : ");
comboHorloges
= new Combo(content, SWT.DROP_DOWN);
comboHorloges.setItems(getNomsHorloges());
comboHorloges.setText(getHorlogeCourante());
return
content;
}
/**
* Liste des noms d'horloge apparaissant
dans le registre des extensions
*/
static private String[]
getNomsHorloges() {
String extensionPointId =
"com.eclipsetotale.tutorial.horloge.horloges";
IConfigurationElement[] contributions =
Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPointId);
String[] nomsHorloges = new
String[contributions.length];
for
(int i = 0; i < contributions.length;
i++) {
nomsHorloges[i]
= contributions[i].getAttribute("nom");
}
return
nomsHorloges;
}
/**
* Nom de l'horloge actuellement sélectionnée
dans la page de préférences.
* Si aucun retourne le nom de la première
horloge.
*/
static public String
getHorlogeCourante() {
IPreferenceStore prefStore =
Activator.getDefault().getPreferenceStore();
String nomHorloge = prefStore.getString(HORLOGE);
if
(nomHorloge.equals("")) {
nomHorloge = getNomsHorloges()[0];
prefStore.setValue(HORLOGE,
nomHorloge);
}
return
nomHorloge;
}
@Override
public boolean performOk()
{
// Stockage du nom de l'horloge dans les préférences du plugin
IPreferenceStore prefStore =
Activator.getDefault().getPreferenceStore();
prefStore.setValue(HORLOGE,
comboHorloges.getText());
return
true;
}
public void init(IWorkbench
workbench) {
}
}
|
La partie importante est la méthode 'getNomsHorloges'.
Cette méthode consulte le registre des extensions pour trouver
les contributions au point d'extension 'com.eclipsetotale.tutorial.horloge.horloges'
et récupère la valeur de l'attribut 'nom' de chaque
extension.
Le reste du code gère le contenu graphique de la page de préférences
et le stockage du nom d'horloge sélectionné par l'utilisateur.
La dernière
étape est de faire afficher l'horloge sélectionnée
par la vue 'Horloge'. Modifier le code de la classe 'HorlogeView'
de la façon suivante :
package
com.eclipsetotale.tutorial.horloge;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.part.ViewPart;
import
com.eclipsetotale.tutorial.horloge.texte.HorlogeTexte;
public class HorlogeView extends
ViewPart {
public HorlogeView()
{
}
@Override
public void createPartControl(Composite
parent) {
// Nom
d'horloge sélectionné dans la page de préférences
String nomHorlogeCourante =
PrefPage.getHorlogeCourante();
// Récupération
de l'extension associée au nom d'horloge
String extensionPointId = "com.eclipsetotale.tutorial.horloge.horloges";
IConfigurationElement[] contributions
=
Platform.getExtensionRegistry().getConfigurationElementsFor(extensionPointId);
IConfigurationElement extensionHorloge
= null;
for
(int i = 0; i < contributions.length;
i++) {
if(contributions[i].getAttribute("nom").equals(nomHorlogeCourante))
{
extensionHorloge
= contributions[i];
break;
}
}
// Si
une extension est disponible, la classe 'Horloge' correspondante
// est
instanciée via la méthode 'createExecutableExtension'
if(extensionHorloge
!= null) {
try
{
Horloge
horloge =
(Horloge)extensionHorloge.createExecutableExtension("classe");
horloge.afficher(parent);
} catch
(CoreException e) {
String
msg = "Impossible d'afficher l'horloge";
parent.setLayout(new
RowLayout());
(new
Label(parent, SWT.NONE)).setText(msg);
}
}
}
@Override
public void setFocus()
{
}
}
|
Tester.
Le changement d'horloge n'est pas géré dynamiquement par
notre code, donc à chaque modification du type d'horloge dans la
page de préférences, il faut fermer et ouvrir de nouveau
la vue 'Horloge'.
Conclusion
La notion de plugin intégrée au framework
Eclipse permet le développement et la livraison d'applications
modulaires et extensibles.
La première
partie de ce tutorial nous avait permis d'apprendre à manipuler
l'outillage de développement de plugins (le PDE) et de découvrir
comment le framework Eclipse assurait la modularité. Dans
cette deuxième partie nous avons étudié comment se
concrétisait l'extensibilité du framework Eclipse
avec les notions de points d'extension et d'extensions.
|