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 :
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 :
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