Découverte de l'API de NetBeans Platform

Mhamed Ben Jmaa

Dans cet article, nous allons découvrir les principales API de NetBeans Platform, permettant de développer une application en utilisant le Framework de l'IDE.

- Titre : Découverte de l'API de NetBeans Platform

- Auteur : Mhamed Ben Jmaa

- Parution : 16 mai 2008

- Mise à jour : 22 février 2020

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2007 Mhamed Ben Jmaa. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Mise en place du projet

- III. L'Utilisation des actions

- IV. Réalisation d'un simple Wizard

- V. L'utilisation de l'Output

- VI. Remerciements

I - Introduction

La plateforme NetBeans est un outil très puissant pour la réalisation d'applications Java Swing (reportez-vous ici pour de plus amples informations sur cette plateforme). Si elle offre un gain de temps en matière de productivité, il reste à se familiariser avec ses outils rapidement. Je vais donc vous faire part de quelques trucs et astuces pour vous éviter beaucoup d'heures de recherche dans les forums et autres articles.

II - Mise en place du projet

1. Nous allons commencer par créer un nouveau projet : File -> new Project -> NetBeans plug-in modules -> Module suite Project.

Le module suite est un container de modules dans lequel vous pourrez déployer vos modules afin de les tester.

Il est tout à fait possible de passer outre cette étape et aller directement à l'étape 2 si vous voulez que vos modules soient directement greffés dans l'IDE NetBeans.

2. Cliquez sur next, donnez un nom à votre module suite, que l’on nommera ici « masuite » puis cliquez sur « finish ».

3. Lancez l'application. (Appuyez sur F6.)

On remarque que c'est la copie conforme de NetBeans : tous les modules sont là, ils sont chargés par défaut. On ne va pas avoir besoin de tous ces modules, on va donc les enlever et travailler avec une plateforme basique. On ajoutera les modules dont on aura besoin au fur et à mesure.

4. Fermez l'instance de « masuite ».

5. Faites un clic droit sur le projet -> properties -> applications -> Create a standalone application. Vous aurez un avertissement qui vous demandera d'exclure tous les modules relatifs à NetBeans, cliquez sur « exclude ». Vous pourrez ensuite changer le titre, l'icône, le splash screen « écran de démarrage » de votre application.

Quand vous avez fini, cliquez sur OK. Vous pouvez relancer l'application et constater que le temps de démarrage s’est considérablement réduit. Cela est dû au fait qu’il y a beaucoup moins de modules à charger lors du lancement.

5. Une fois que le modulesuite est en place, on va créer un module dans lequel on placera notre travail :
File->new project ->NetBeans plug-in modules->Module Project, cliquez sur Next

Nommez votre module comme vous voulez, dans mon cas j'ai choisi « moduledessai ». On va placer notre module dans le module suite « masuite » que l'on vient juste de mettre au point.

Si vous avez choisi de ne pas créer de module suite, il suffit de placer votre module dans « Standalone Module ».

6. Cliquez sur next, donnez un « code base name » à votre module. Prenez l'habitude de bien nommer vos modules afin d'avoir une bonne structuration et pour éviter des confusions.

7. Cliquez sur finish.

III - L'Utilisation des actions

Les actions sont un excellent moyen d'interagir avec le composant que l'on crée, que ce soit des éditeurs, des wizards (comme nous allons voir plus loin), ou autres. C'est le système qui gère la communication avec ces composants grâce au fichier Layer.XML. Cela nous évite donc d'écrire du code.

On va commencer par ajouter une action dans le menu 'File' à titre introductif. Cette action se contentera d'afficher un message :

1. Faites un clic droit sur « moduledessai », new -> file folder -> NetBeans module devlopment -> Action ;

2. Cliquez sur next, on dira que notre action peut être appelée à tout moment, on va donc choisir « always enabled » ;

3. Cliquez sur next :

On passe alors à la configuration de notre Action, sa catégorie, son emplacement, que ce soit dans la barre de menus, dans la barre d'outils, ou même les deux.On peut éventuellement ajouter un raccourci clavier pour l'appel de notre Action.

J'ai choisi de mettre mon action dans la catégorie Tool, dans le menu 'File' et de la faire suivre par un séparateur ;

4. Cliquez sur next :

On va donner un nom de classe à cette Action. Dans cet exemple, j'ai choisi « MonAction ». Il faut aussi choisir le texte qui va s'afficher dans le menu, le nom de package, et vous pouvez ajouter une icône au menu pour un résultat plus esthétique. Sachez que le format PNG est supporté, vous pouvez donc créer des icônes avec des effets de transparences ;

5. Cliquez sur finish.

On obtient trois fichiers à la création du module :

- le fichier Bundle.properties, dans lequel on place les informations concernant l'action. Dans notre cas ça sera le nom de l’Action.

Ce nom va être extrait grâce à cette méthode :

public String getName() {
return NbBundle.getMessage(MonAction.class, "CTL_MonAction");
}

- le fichier layer.xml, qui contient la description et la configuration de notre action : son emplacement dans le menu, l'instance, la catégorie, etc. Ce fichier contiendra également la configuration de tout ce que vous allez ajouter dans la barre de menus,

- enfin, le fichier Java, dans lequel on pourra implémenter notre code.

Nous allons utiliser l'objet NotifyDescriptor pour afficher un Dialog.

On va donc ajouter ce code dans la méthode performAction()

public void performAction() {
String msg = "Aimez-vous NetBeans ?";
NotifyDescriptor d = new NotifyDescriptor.Confirmation(msg, NotifyDescriptor.INFORMATION_MESSAGE,NotifyDescriptor.YES_NO_CANCEL_OPTION);
Object o = DialogDisplayer.getDefault().notify(d);

if(o==NotifyDescriptor.YES_OPTION)
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message("Vous aimez NetBeans !!"));

if(o==NotifyDescriptor.NO_OPTION)
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message("Vous n'aimez pas NetBeans !!?"));

if(o==NotifyDescriptor.CANCEL_OPTION)
DialogDisplayer.getDefault().notify(new NotifyDescriptor.Message("Vous n'avez pas donné d'avis !!"));
}

On remarque que DialogDisplayer n'est pas détecté. Il faut donc ajouter notifydescriptor et dialogdisplayer aux bibliothèques ;

6. Faites un clic droit sur le module -> properties -> libraries -> add dependency -> Dialogs API.

Il est aussi possible de taper le nom de la classe dans la zone du filtre pour que l'interface réduise le choix.

7. Cliquez sur OK ;

8. Cliquez encore une fois sur OK, on revient à MonAction.java. On fait un « fix imports » (ou alt+maj+f), et le problème est résolu ;

9. On lance le projet et on essaye !!

IV - Réalisation d'un simple Wizard

Maintenant qu'on a clarifié l’utilisation des actions, passons au Wizard :

1. Cliquez droit sur « moduledessai » new -> file folder -> NetBeans Module development -> Wizard.

On utilisera :

- Registration Type : Custom,

- Step Sequence : Static,

- Number of wizard panel : 2 ;

2. Cliquez sur next :

3. Cliquez sur finish.

NetBeans a créé pour nous un fichier Action, on va donc pouvoir placer notre wizard dans la barre de menus en ajoutant quelques lignes dans le fichier Layer.xml.

Dans la balise <folder name = action> on ajoute notre wizard dans la catégorie Window :

<folder name="Window">
<file name="org-yourorghere-moduledessai-monwizard-MonWizardWizardAction.instance"/>
</folder>

Dans la balise <folder name = Menu> on ajoute :

<folder name="Window">
<file name="org-yourorghere-moduledessai-monwizard-MonWizardWizardAction.shadow">
<attr name="originalFile" stringvalue="Actions/Window/org-yourorghere-moduledessai-monwizard-MonWizardWizardAction.instance"/>
</file>
<attr name="org-yourorghere-moduledessai-monwizard-MonWizardWizardAction.shadow/org-yourorghere-moduledessai-monwizard-separatorAfter.instance" boolvalue="true"/>
<file name="org-yourorghere-moduledessai-monwizard-separatorAfter.instance">
<attr name="instanceClass" stringvalue="javax.swing.JSeparator"/>
</file>
<attr name="org-yourorghere-moduledessai-monwizard-separatorAfter.instance/org-netbeans-modules-project-ui-logical-tab-action.shadow" boolvalue="true"/>
</folder>

Les lignes que vous ajouterez dans le fichier XML dépendent du nom du package que vous avez choisi. En ce qui me concerne, c'est « org.yourorghere.moduledessai.monwizard ». Dans le fichier XML il faut remplacer les « . » par des « - ».

Désormais notre wizard apparaîtra dans le menu Window.

Pour changer le titre du menu, il suffit de changer dans le fichier action le retour de la méthode :

public String getName() {
return "Start Sample Wizard";
}

On va plutôt mettre :

public String getName() {
return "Démarrer mon Wizard";
}

Si on veut faire les choses en respectant la philosophie de NetBeans, il faut créer un fichier Bundle.Properties et attribuer un nom, comme c'était le cas pour MonAction en première partie.

Jetons un œil sur le fichier MonWizardWizardAction.java.

La méthode performAction contient l'initialisation du Wizard.

Pour la mise en place du titre, il faudra changer le code :

wizardDescriptor.setTitle("Your wizard dialog title here");

par :

setTitle("Premier Wizard");

Vient ensuite une condition très importante : « if(!Canceled) ». Si cette condition est vérifiée, autrement dit si l'utilisateur a cliqué sur finish, alors le traitement peut commencer. Il ne faut faire aucun traitement tant que l'utilisateur n'a pas fini, c'est l'esprit du wizard.

Toutes les informations rentrées au cours des étapes du wizard seront stockées grâce à l'objet wizardDescriptor. On va voir ça plus en détail ultérieurement.

Commençons par mettre au point nos VisualPanel.

MonWizardVisualPanel1 ressemblera à ça :

Jetons un œil sur le code source.
On va changer le nom de l'étape :

public String getName() {
return "Saisie du nom";
}

On ajoutera aussi un getter pour le JTextField. J'ai nommé le mien nomTxt, je vais donc avoir ceci :

public JTextField getNomTxt() {
return nomTxt;
}

Il suffit de faire ctrl+espace dans l'éditeur pour que NetBeans vous propose de créer le getter.

Le second VisualPanel ressemblera à ça :

Il faut ajouter un ButtonGroup et placer le ouiRdo et le nonRdo dedans.

Il faut aussi ajouter un getter pour le ouiRd, le nonRdo et le nomLbl (on placera dans ce label, le nom qu'aura saisi l'utilisateur dans l'étape précédente).

Par la même occasion on changera le titre de l'étape.

On remplace :

public String getName() {
return "Step #2";
}

par :

public String getName() {
return "Question à propos du tuto";
}

Occupons-nous maintenant de la transmission d'informations d'une étape à une autre.

Dans le fichier MonWizardWizardPanel1.java, on remarque que la méthode getComponent retourne un nouvel objet de type MonWizardVisualPanel1 si ce dernier n'existe pas. On va donc changer la déclaration de component, qui n'est plus de type Component, mais de type MonWizardVisualPanel1. On aura besoin de l'instance de cet objet pour avoir accès à la méthode getNomTxt() que l'on a créée tout à l'heure dans MonWizardVisualPanel1, pour pouvoir stocker les informations de l'utilisateur.

Afin de stocker ou récupérer ces informations, on utilisera les méthodes, storeSettings et readSettings comme suit :

public void readSettings(Object settings) {
}

public void storeSettings(Object settings) {
((WizardDescriptor)settings).putProperty("Le nom",component.getNomTxt().getText());
}

En ce qui concerne MonWizardWizardPanel2.java, on fera la même chose, mais pour le ouiRdo cette fois-ci :

MonWizardWizardPanel2.java

public class MonWizardWizardPanel2 implements WizardDescriptor.Panel {

/**
* The visual component that displays this panel. If you need to access the
* component from this class, just use getComponent().
*/
private MonWizardVisualPanel2 component;

// Get the visual component for the panel. In this template, the component
// is kept separate. This can be more efficient: if the wizard is created
// but never displayed, or not all panels are displayed, it is better to
// create only those which really need to be visible.
public Component getComponent() {
if (component == null) {
component = new MonWizardVisualPanel2();
}
return component;
}

public HelpCtx getHelp() {
// Show no Help button for this panel:
return HelpCtx.DEFAULT_HELP;
// If you have context help:
// return new HelpCtx(SampleWizardPanel1.class);
}

public boolean isValid() {
// If it is always OK to press Next or Finish, then:
return true;
// If it depends on some condition (form filled out...), then:
// return someCondition();
// and when this condition changes (last form field filled in...) then:
// fireChangeEvent();
// and uncomment the complicated stuff below.
}

public final void addChangeListener(ChangeListener l) {}
public final void removeChangeListener(ChangeListener l) {}
/*
private final Set<ChangeListener> listeners = new HashSet<ChangeListener>(1);
public final void addChangeListener(ChangeListener l) {
synchronized (listeners) {
listeners.add(l);
}
}
public final void removeChangeListener(ChangeListener l) {
synchronized (listeners) {
listeners.remove(l);
}
}
protected final void fireChangeEvent() {
Iterator<ChangeListener> it;
synchronized (listeners) {
it = new HashSet<ChangeListener>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
it.next().stateChanged(ev);
}
}
*/

// You can use a settings object to keep track of state. Normally the
// settings object will be the WizardDescriptor, so you can use
// WizardDescriptor.getProperty & putProperty to store information entered
// by the user.
public void readSettings(Object settings) {
//récupération du nom de l'utilisateur
component.getNomLbl().setText("Votre nom est : "+(String) ((WizardDescriptor)settings).getProperty("Le nom"));
}
public void storeSettings(Object settings) {
Boolean reponse = new Boolean(component.getOuiRdo().getModel().isSelected());
((WizardDescriptor)settings).putProperty("Reponse",reponse);
}

}

On va maintenant ajouter la partie traitement dans MonWizardWizardAction.java.

On affiche un Dialog récapitulatif de notre saisie, on ajoute donc ce bout de code dans la condition (if(!cancelled)) :

if (!cancelled) {
boolean reponse = ((Boolean)wizardDescriptor.getProperty("Reponse")).booleanValue();
String msg = "Votre nom est : "+(String)wizardDescriptor.getProperty("Le nom")+" et votre réponse fut : "+
(reponse?"Oui":"non");
NotifyDescriptor d = new NotifyDescriptor.Message(msg);
DialogDisplayer.getDefault().notify(d);
}

On lance l'application, dans le menu tools -> menu « Démarrer mon Wizard ».

Notre wizard s'affiche et notre récapitulatif est valable, mais le passage d'une étape à une autre se fait sans vérification. On peut donc cliquer sur next sans entrer aucune donnée. On va corriger ça.

Jetons un œil du côté de MonWizardWizardPanel1.java.

Il y a une méthode isValid() qui renvoie constamment un true. C'est le retour de cette méthode qui détermine s'il est possible de passer à l'étape suivante. Il faut donc enlever le return true, et ajouter des listeners sur les composants pour signaler les modifications que fait l'utilisateur et vérifier la validité des entrées.

On ajoute implements « DocumentListener ».

On ajoute la déclaration d'une variable boolean isValid.

On enlève les commentaires sur les méthodes « addChangeListener », « removeChangeListener » et « fireChangeEvent() ».

Vous allez devoir upgrader les sources vers la version 1.5 pour ajouter le support des « generics » : faites un clic droit sur moduledessai dans l'onglet « project » -> properties -> sources -> source level -> 1.5. -> enable Warnigns -> Cliquez sur OK

Et on ajoute une méthode de vérification. On obtient le résultat suivant :

MonWizardWizardPanel1

public class MonWizardWizardPanel1 implements WizardDescriptor.Panel, DocumentListener {

private MonWizardVisualPanel1 component;
//la variable qui décide de la validité de Panel
private boolean isValid;

public Component getComponent() {
if (component == null) {
component = new MonWizardVisualPanel1();
//grâce à cette ligne on pourra écouter les changements sur le JTextField!
component.getNomTxt().getDocument().addDocumentListener(this);
}
return component;
}

public HelpCtx getHelp() {
// Show no Help button for this panel:
return HelpCtx.DEFAULT_HELP;
// If you have context help:
// return new HelpCtx(SampleWizardPanel1.class);
}

public boolean isValid() {
// on va enlever ce renvoi, car la validité n'est pas toujours vérifiée
//return true;
return isValid;

}
//on enlève ces deux déclarations
//public final void addChangeListener(ChangeListener l) {}
//public final void removeChangeListener(ChangeListener l) {}

//voilà pourquoi il faut upgrader vers la version1.5
private final Set<ChangeListener> listeners = new HashSet<ChangeListener>(1);
public final void addChangeListener(ChangeListener l) {
synchronized (listeners) {
listeners.add(l);
}
}
public final void removeChangeListener(ChangeListener l) {
synchronized (listeners) {
listeners.remove(l);
}
}
protected final void fireChangeEvent() {
Iterator<ChangeListener> it;
synchronized (listeners) {
it = new HashSet<ChangeListener>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
it.next().stateChanged(ev);
}
}

public void readSettings(Object settings) {
}
public void storeSettings(Object settings) {
((WizardDescriptor)settings).putProperty("Le nom",component.getNomTxt().getText());
}

public void insertUpdate(DocumentEvent e) {
verifierLaValidite();
}

public void removeUpdate(DocumentEvent e) {
verifierLaValidite();
}

public void changedUpdate(DocumentEvent e) {
verifierLaValidite();
}

private void verifierLaValidite() {
String currentText = component.getNomTxt().getText();
boolean isValidInput = currentText != null && currentText.length() > 0;
if (isValidInput) {
setValid(true);
} else {
setValid(false);
}
}

private void setValid(boolean b) {
if (isValid != b) {
isValid = b;
//il faut absolument appeler cette méthode pour que le panel rappelle
//la méthode isValid
fireChangeEvent();
}
}

}

On ajoute la même chose pour le MonWizardWizardPanel2, sauf qu'on ne mettra pas de « DocumentListener », mais plutôt un « ItemListener ». On obtient le résultat suivant :

public class MonWizardWizardPanel2 implements WizardDescriptor.Panel, ItemListener {

private MonWizardVisualPanel2 component;
private boolean isValid;

public Component getComponent() {
if (component == null) {
component = new MonWizardVisualPanel2();
component.getOuiRdo().addItemListener(this);
component.getNonRdo().addItemListener(this);
}
return component;
}

public HelpCtx getHelp() {
// Show no Help button for this panel:
return HelpCtx.DEFAULT_HELP;
// If you have context help:
// return new HelpCtx(SampleWizardPanel1.class);
}

public boolean isValid() {
return isValid;
}

//public final void addChangeListener(ChangeListener l) {}
//public final void removeChangeListener(ChangeListener l) {}

private final Set<ChangeListener> listeners = new HashSet<ChangeListener>(1);
public final void addChangeListener(ChangeListener l) {
synchronized (listeners) {
listeners.add(l);
}
}
public final void removeChangeListener(ChangeListener l) {
synchronized (listeners) {
listeners.remove(l);
}
}
protected final void fireChangeEvent() {
Iterator<ChangeListener> it;
synchronized (listeners) {
it = new HashSet<ChangeListener>(listeners).iterator();
}
ChangeEvent ev = new ChangeEvent(this);
while (it.hasNext()) {
it.next().stateChanged(ev);
}
}

public void readSettings(Object settings) {
component.getNomLbl().setText("Votre nom est : "+(String) ((WizardDescriptor)settings).getProperty("Le nom"));
}
public void storeSettings(Object settings) {
Boolean reponse = new Boolean(component.getOuiRdo().getModel().isSelected());
((WizardDescriptor)settings).putProperty("Reponse",reponse);
}

public void itemStateChanged(ItemEvent e) {
verifierLaValidite();
}

private void verifierLaValidite() {
boolean oui = component.getOuiRdo().getModel().isSelected();
boolean non = component.getNonRdo().getModel().isSelected();
boolean isValidInput = oui != non;
if (isValidInput) {
setValid(true);
} else {
setValid(false);
}
}

private void setValid(boolean b) {
if (isValid != b) {
isValid = b;
fireChangeEvent();
}
}

}

On lance notre application, on remarque que les boutons next et finish ne sont plus constamment actifs.

V - L'utilisation de l'Output

Dans cette section, on verra comment utiliser la fameuse fenêtre Output de NetBeans. Pour ce faire, on va utiliser le résultat du wizard qu'on a créé précédemment, mais au lieu de l'afficher dans un dialog, on va placer le résultat dans la fenêtre Output.

On va d'abord ajouter les modules nécessaires.

Faites un clic droit sur masuite -> properties -> libraries -> nœud plateforme6 -> on coche « IO/APIs » et OutPutWindow -> Cliquez sur OK.

Faites un clic droit sur moduledessai -> properties -> Add Dependency -> IO/APIs -> Cliquez sur OK -> required Tokens Add… -> org.openide.windows.IOProvider -> Cliquez sur OK -> Cliquez à nouveau sur OK.

On remplace le code de la condition (if(!canceled))

if (!cancelled) {
//cette instruction recherche une instance de InputOutput, si elle n'existe pas elle en crée une
// le false c'est pour que l'affichage dans l'output window se fasse dans l'onglet "Mon Wizard"
//s’il existe, si on met true, alors un nouvel onglet apparaîtra.
InputOutput io = IOProvider.getDefault().getIO("Mon Wizard", false);
io.select();
boolean reponse = ((Boolean)wizardDescriptor.getProperty("Reponse")).booleanValue();
String msg = "Votre nom est : "+(String)wizardDescriptor.getProperty("Le nom")+" et votre réponse fut : "+
(reponse?"Oui":"non");
OutputWriter w;
if (reponse)
//pour un affichage normal
w = io.getOut();
else
//pour afficher une information importante, le texte apparaîtra en rouge
w = io.getErr();
w.println(msg);
}

Ajoutez les imports nécessaires, cliquez droit sur masuite->clean and build all.

Lancez le projet, et voilà le travail.

VI - Remerciements

Un grand merci à l'équipe developpez.com pour sa patience, à David Gimelle pour la relecture.

Ben Jmaa Mhamed