Exercice D.0 Pour réaliser ce TP vous devrez créer un nouveau répertoire DEMINEUR, et y copier l'ensemble du répertoire RESGRAF que vous trouverez à l'adresse : ~garreta/RessourcesDemineur/
Ce répertoire contient l'ensemble des ressources graphiques nécessaires au jeu, et notamment les thèmes SMILEY, INSECTE, SKI et HALLOWEEN placés dans des sous-répertoires de même nom, et qui servent à personnaliser l'apparence du jeu.
ATTENTION : ces thèmes comportent certaines images au format gif qui ne sont pas libres de droits, vous pouvez les garder pour votre usage personnel, mais ne vous en servez pas pour illustrer vos pages web par exemple.
La fenêtre comporte également une grille de taille
variable, qu'il va falloir explorer, ainsi qu'une barre de menus que nous
étudierons le moment venu, elle permettra de fixer la taille de
la grille, le niveau de difficulté, et le thème graphique
courant.
La case en haut et à gauche de la grille est assurée
de ne pas comporter de mine.
Attention : afin de vous éviter de taper inutilement du
code et pour que vous puissiez vous concentrer sur les parties importantes,
certaines méthodes vous sont intégralement fournies, mais
le rôle de chacune sera considérée comme acquis par
la suite.
Fichier FenetreSimple.java
import javax.swing.*;
import java.awt.event.*; public class FenetreSimple extends JFrame{
addWindowListener(new WindowAdapter()
public static void main(String args[])
|
Note : pour que vos programmes Java comportant des composants Swing fonctionnent correctement avec les threads (un thread peut être vu comme un processus en C, ou une tâche en Ada), il y une règle importante à respecter :lorsqu'un composant a été affiché, tout code qui affecte l'état de ce composant ou qui en dépend doit être effectué dans le thread qui a en charge la gestion des événements (typiquement dans une méthode actionPerformed ou équivalente, selon le cas).
Ceci signifie qu'un thread ne doit pas directement ajouter un composant à l'interface d'un autre thread.
Pour éviter de créer des applications qui peuvent poser des problèmes suivez dans vos programmes le schéma suivant :
public class MonApplication {
public static void main(String[] args) {
JFrame f = new JFrame(...);
...//Add components to the frame here...
f.pack();
f.setVisible(true);
//Après le pack et l'affichage, l'interface ne doit plus être directement modifiée.
}...
// Toutes les opérations sur les composants de l'interface -setText,
// getText, etc.- sont ensuite effectuées dans les gestionnaires
// d'événements tels que actionPerformed().
...
}Plus généralement, celà revient à dire que tous les composants d'un cadre doivent être mis en place avant l'affichage du cadre, et qu'ensuite, toutes les modifications ou consultations de l'état de ces composants doit être effectuée en réponse à des événements utilisateur.
Bien sûr il peut y avoir y avoir des exceptions, ainsi il peut parfois être utile d'effectuer ces opérations liées aux composants dans d'autres threads que le thread chargé de la gestion des événements. La classe SwingUtilities fournit deux méthodes dans ce but :
invokeLater (Runnable code) : qui provoque l'execution de code dans le thread chargé des événements. Cette méthode retourne immédiatement, sans attendre que le code soit éxécuté.
invokeAndWait(Runnable code) : qui fonctionne comme la méthode précédente, mais qui attend la fin d'éxécution de code.En conséquence, le pack() et le setVisible(true) de votre cadre devraient être les dernières instructions en rapport avec l'interface dans votre main(...).
Créez un nouveau fichier DemineurTheme.javaqui définit une classe publique DemineurTheme.
Un thème n'est rien d'autre qu'un ensemble d'icônes chargées dans un répertoire particulier, nous allons dans un permier temps mettre en place les différentes constantes et variables de notre classe.
Définissez les trois constantes suivantes dans votre classe :
public static final int NSELECT = 0;
public static final int SELECT = 1;
public static final int ROLL = 2;
elles serviront à accéder à l'icône
appropriée
du thème à l'aide de méthodes que nous définirons
par la suite.
private String nom_theme; // nom du theme et du repertoire qui le
contient
private ImageIcon icones_barre[] = new ImageIcon[9];
// icones pour boutons de la barre d'outils :
// 0,1,2 // marche, marque_mine, marque_cool, (version non
selectionnes)
// 3,4,5 // les memes en rouge (selectionnes)
// 6,7,8 // les memes en bleu (rollover)
//icones pour l'affichage des nbres d'adjacences
private ImageIcon icones_chiffres[] = new ImageIcon[9];
Les quatre icônes suivantes représentent une case de la grille dans l'un des états suivants :
private ImageIcon icones_grille[] = new ImageIcon[4];
// icones de la grille
// 0,1,2,3 // base, mine, stop, cool
private ImageIcon icones_dialogues[] = new ImageIcon[2];
//icones des dialogues
// 0,1 //perdu, gagne
// noms icones de la barre d'outils :
private String noms_icones_barre[]={"barre_marche", "barre_mine",
"barre_cool"};
// noms icones de la grille :
private String noms_icones_grille[]={"base", "mine", "stop",
"cool"};
// noms icones des dialogues :
private String noms_icones_dialogues[]={"gagne", "perdu"};
private void chargerIcones(String repertoire){
}
Le but de cette méthode est de charger dans les différents
tableaux les images des icônes stockées dans des fichiers.
L'argument repertoire sert à spécifier le nom
du répertoire qui contient lui-même les sous-répertoires
des différents thèmes.
Par exemple dans notre cas le sous-répertoire du thème
INSECTE
est placé dans le répertoire RESGRAF de l'application.
L'appel de la méthode sera donc, par exemple
chargerIcones("RESGRAF/");
mais
nous n'en sommes pas encore là, suivez bien les instructions suivante
pour charger correctement les icônes dans le bon ordre.
Note : toutes les images sont stockées au format gif,
n'oubliez pas d'ajouter ".gif" à la fin des noms de base.
Attention : la table icones_barre[] doit contenir dans l'ordre et à la suite :
Les icônes de la table icones_chiffres[] sont stockées dans l'ordre croissant, de 0 à 8. Le nom de chaque fichier s'obtient tout simplement à l'aide de l'opérateur de concaténation de chaines : repertoire+i+".gif"
Solution exo 6.3.2 [ Sujet ]
private void chargerIcones(String
repertoire){
for(int
i=0;i<noms_icones_barre.length;i++){
icones_barre[i]=new
ImageIcon(repertoire+noms_icones_barre[i]+".gif");
icones_barre[3+i] = new
ImageIcon(repertoire+noms_icones_barre[i]+"R.gif");
icones_barre[6+i] = new
ImageIcon(repertoire+noms_icones_barre[i]+"B.gif");
}
for(int
i=0;i<noms_icones_grille.length;i++){
icones_grille[i]=new
ImageIcon(repertoire+noms_icones_grille[i]+".gif");
}
for(int
i=0;i<noms_icones_dialogues.length;i++){
icones_dialogues[i]=new
ImageIcon(repertoire+noms_icones_dialogues[i]+".gif");
}
for(int
i=0;i<=8;i++){
icones_chiffres[i] = new
ImageIcon(repertoire+i+".gif");
}
}
DemineurTheme(String p_nom_theme, String repertoire){
nom_theme=p_nom_theme;
chargerIcones(repertoire+nom_theme+"/");
}
Les argument du constructeur sont les suivants :
p_nom_theme : représente le nom du thème,
et le nom du sous-répertoire qui contient l'ensemble des ressources
de ce thème.
repertoire : représente le nom du répertoire
qui contient l'ensemble des sous-répertoires des thèmes.
public ImageIcon iconeCool(){
return icones_grille[3]; } |
retourne l'icône qui marque une case non dangereuse |
public ImageIcon iconeMine(){
return icones_grille[1]; } |
retourne l'icône de la mine |
public ImageIcon iconeStop(){
return icones_grille[2]; } |
retourne l'icône qui marque une case dangereuse |
public ImageIcon iconeBase(){
return icones_grille[0]; } |
retourne l'icône de base d'une case non explorée |
public ImageIcon iconeChiffre(int i){
return icones_chiffres[i]; } |
retourn l'icône qui comporte le chiffre i, pour le marquage des adjacences |
public ImageIcon iconeBarreMarche(int type){
if ((type<0)||(type>2)) { System.out.println("Utilisation incorrecte de la methode ImageIcon iconeBarreMarche"); System.exit(-1); } return icones_barre[0+type*3]; } |
retourne l'icône destinée au premier bouton de la barre
d'outils (action = marcher sur les cases). L'argument type est une des
valeurs :
|
public ImageIcon iconeBarreMarqueMine(int type){
if ((type<0)||(type>2)) { System.out.println("Utilisation incorrecte de la methode ImageIcon iconeBarreMarqueMine"); System.exit(-1); } return icones_barre[1+type*3]; } |
retourne l'icône destinée au second bouton de la barre
d'outils (action = marquer les cases non dangereuses). L'argument type
est une des valeurs :
|
public ImageIcon iconeBarreMarqueCool(int type){
if ((type<0)||(type>2)) { System.out.println("Utilisation incorrecte de la methode ImageIcon iconeBarreMarqueCool"); System.exit(-1); } return icones_barre[2+type*3]; } |
etourne l'icône destinée au troisième bouton de
la barre d'outils (action = marquer les cases dangereuses). L'argument
type est une des valeurs :
|
public ImageIcon iconeDialogueGagne(){
return icones_dialogues[0]; } |
retourne l'icône destinée au dialogue qui s'affiche lorsque le joueur complète le déminage d'une grille. |
public ImageIcon iconeDialoguePerdu(){
return icones_dialogues[1]; } |
retourne l'icône destinée au dialogue qui s'affiche lorsque le joueur perd. |
public String getNomTheme(){
return nom_theme; } |
retourne simplement le nom du thème |
JPanel presentationTheme(){
}
Elle doit retourner un panneau de la classe JPanel qui comporte l'ensemble des informations présentées ci-dessous (sans la fenêtre, nous la définirons dans la section suivante):
Le gestionnaire de mise en page du panneau principal est un GridLayout, la grille utilisée est présentée sur l'image précédente. Chaque groupe d'icônes est placé dans un sous-panneau de la classe JPanel, dont le gestionnaire est FlowLayout. Les icônes sont placées sur les panneaux à l'aide d'étiquettes (JLabel). Utilisez les méthodes précédemment définies pour récupérer les icônes voulues, vous ne devez pas accéder directement aux variables privées de la classe Demineur. La méthode presentationTheme méthode servira notamment à tester visuellement le bon fonctionnement de votre classe.
Aidez-vous du support de TP Swing et/ou de la documentation en ligne pour effectuer cet exercice.
Solution exo 6.3.5 [ Sujet ] :
JPanel
presentationTheme(){
JPanel panneau = new
JPanel();
panneau.setLayout(new
GridLayout(4,2));
JPanel sous_panneau1 = new
JPanel();
sous_panneau1.setLayout(new
FlowLayout());
sous_panneau1.add(new
JLabel(iconeDialoguePerdu()));
sous_panneau1.add(new
JLabel(iconeDialogueGagne()));
panneau.add(new JLabel("Icones
dialogues"));
panneau.add(sous_panneau1);
JPanel sous_panneau2 = new
JPanel();
sous_panneau2.setLayout(new
FlowLayout());
for(int i=0;i<3;i++){
sous_panneau2.add(new
JLabel(iconeBarreMarche(i)));
sous_panneau2.add(new
JLabel(iconeBarreMarqueMine(i)));
sous_panneau2.add(new
JLabel(iconeBarreMarqueCool(i)));
}
panneau.add(new JLabel("Icones
barre d'outils"));
panneau.add(sous_panneau2);
JPanel sous_panneau3 = new
JPanel();
sous_panneau3.setLayout(new
FlowLayout());
sous_panneau3.add(new
JLabel(iconeBase()));
sous_panneau3.add(new
JLabel(iconeMine()));
sous_panneau3.add(new
JLabel(iconeStop()));
sous_panneau3.add(new
JLabel(iconeCool()));
panneau.add(new JLabel("Icones
de la grille"));
panneau.add(sous_panneau3);
JPanel sous_panneau4 = new
JPanel();
sous_panneau4.setLayout(new
FlowLayout());
for(int
i=0;i<=8;i++){
sous_panneau4.add(new
JLabel(iconeChiffre(i)));
}
panneau.add(new
JLabel("Chiffres"));
panneau.add(sous_panneau4);
return panneau;
}
Note : Dans le cas d'un problème avec l'une des icônes (nom de fichier incorrect par exemple), vous ne recevrez aucun message d'erreur, mais l'icône ne s'affichera pas. Pour nous concentrer sur la partie qui nous intéresse , nous laissons la gestion du suivi du chargement des images de côté, le contrôle se fera visuellement.
Solution exo 6.3.6 [ Sujet ] :
public static void main(String
args[]){
FenetreSimple cadre = new
FenetreSimple("Test
theme smiley");
DemineurTheme theme = new
DemineurTheme("SMILEY","RESGRAF/");
JPanel panneau_theme =
theme.presentationTheme();
cadre.getContentPane().add(panneau_theme);
cadre.pack();
cadre.setVisible(true);
}
Créez un nouveau fichier SelecteurTheme.java qui comportera la définition de la classe suivante :
public class SelecteurTheme extends JTabbedPane{
}
private DemineurTheme table_themes[];
SelecteurTheme(String table_noms_themes[], String repertoire)
les arguments sont les suivants :
String table_noms_themes[] : une table qui contient
la liste de tous les noms de thèmes existant.
String repertoire : le répertoire qui contient
les sous-répertoires des thèmes.
Le constructeur se charge ensuite :
Solution exo 6.4.1 [ Sujet ] :
SelecteurTheme(String
table_noms_themes[],
String repertoire){
table_themes = new
DemineurTheme[table_noms_themes.length];
for(int
i=0;i<table_themes.length;i++){
table_themes[i] = new
DemineurTheme(table_noms_themes[i],
repertoire);
addTab(table_themes[i].getNomTheme(),
table_themes[i].presentationTheme());
}
}
String table_themes[] = {"SMILEY", "INSECTE", "SKI", "HALLOWEEN"};
La méthode crée également une fenêtre simple, et ajoute à son panneau de contenu un objet de la classe SelecteurTheme qui devra présenter tous les thèmes dont les noms auront été placés dans le tableau précédent table_themes.
Vous devriez à présent obtenir une fenêtre contenant quatre onglets permettant de visualiser les icônes de chacun des quatre thèmes :
Solution exo 6.4.2 [ Sujet ] :
public static void main(String
args[]){
String table_themes[] = {"SMILEY",
"INSECTE", "SKI", "HALLOWEEN"};
FenetreSimple cadre = new
FenetreSimple("Test
demineur theme");
cadre.getContentPane().add(new
SelecteurTheme(table_themes, "RESGRAF/"));
cadre.pack();
cadre.setVisible(true);
}
Créez un nouveau fichier nommé ControleAction.java, et qui définit une classe publique de même nom, cette dernière étant une sous-classe de JToolBar qui définit les barres d'outils.
public static final int MARCHE = 1;
public static final int MARQUE_COOL = 2;
public static final int MARQUE_MINE = 3;
private DemineurTheme theme;
private int action;
Les trois variables suivantes concernent les trois boutons radio présents sur la barre : un tableau de boutons radio, un groupe, et une table qui comporte les intitulés des boutons. Les chaines de cette table serviront également dans la gestion des événements associés aux boutons.
private JRadioButton boutons[] = new JRadioButton[3];
private ButtonGroup groupe;
private String noms_boutons[]={"marche", "marque_cool",
"marque_mine"};
public int getAction(){
return action;
}
C'est l'unique méthode, avec le constructeur que nous définirons plus loin, que nous avons besoin d'atteindre depuis l'extérieur de cette classe.
Exercice 6.5.1 : Ecrivez le corps de la méthode dont la signature est la suivante :
private JRadioButton initialiserUnBouton(ImageIcon p_ic_nselect, ImageIcon p_ic_select, ImageIcon p_ic_roll, String p_nom);
Les arguments sont les suivants :
ImageIcon p_ic_nselect : icône affichée lorsque
le bouton n'est pas sélectionné (icône de base) ;
ImageIcon p_ic_select : icône affichée
lorsque le bouton est sélectionné ;
ImageIcon p_ic_roll : icône pour la
gestion du rollover ;
String
p_nom
: chaîne qui définit la commande d'action du bouton,
elle permettra d'identifier le bouton lors de la gestion des
événements.
La méthode doit créer un nouveau bouton radio, lui affecter de manière appropriée les icônes passées en arguments, activer le rollover, lui associer la commande d'action, et intialiser son état à non sélectionné. Le bouton ainsi créé sera retourné (pas comme une crêpe !) par la méthode.
Solution exo 6.5.1 [ Sujet ] :
private JRadioButton
initialiserUnBouton(ImageIcon
p_ic_nselect, ImageIcon p_ic_select, ImageIcon p_ic_roll, String
p_nom){
JRadioButton bouton = new
JRadioButton(p_ic_nselect);
bouton.setSelectedIcon(p_ic_select);
bouton.setRolloverIcon(p_ic_roll);
bouton.setRolloverEnabled(true);
bouton.setActionCommand(p_nom);
bouton.setSelected(false);
return bouton;
}
Exercice 6.5.2 : Ecrivez le corps de la méthode suivante :
private void CreerBoutons(){
}
son rôle est simple, elle doit créer les trois boutons de la barre d'outils, les stocker dans la table boutons[] déclarée plus haut, en partant du principe que la variable theme contient le thème courant. Vous utiliserez ici la méthode initialiserUnBouton(...) que nous venons de définir, et les diverses méthodes de la classe DemineurTheme pour obtenir les icônes voulues.
Les trois boutons font partie d'un groupe de boutons radios, instantiez un tel groupe (qui sera placé dans la variable groupe précédemment définie) et ajoutez les trois boutons à ce groupe. Rappelons que dès lors les boutons sont mutuellement exclusifs, en sélectionner un désélecitonnera les deux autres. C'est tout à fait le comportement qu'il nous faut, puisqu'un seul mode d'action peut être activé à la fois.
Par défaut le mode d'action doit être MARCHE, et le bouton radio correspondant de la barre doit être sélectionné.
Solution 6.5.2 [ Sujet ] :
private void
CreerBoutons()
{
boutons[0] =
initialiserUnBouton(
theme.iconeBarreMarche(DemineurTheme.NSELECT),
theme.iconeBarreMarche(DemineurTheme.SELECT),
theme.iconeBarreMarche(DemineurTheme.ROLL),
noms_boutons[0]);
boutons[1] =
initialiserUnBouton(
theme.iconeBarreMarqueCool(DemineurTheme.NSELECT),
theme.iconeBarreMarqueCool(DemineurTheme.SELECT),
theme.iconeBarreMarqueCool(DemineurTheme.ROLL),
noms_boutons[1]);
boutons[2] =
initialiserUnBouton(
theme.iconeBarreMarqueMine(DemineurTheme.NSELECT),
theme.iconeBarreMarqueMine(DemineurTheme.SELECT),
theme.iconeBarreMarqueMine(DemineurTheme.ROLL),
noms_boutons[2]);
boutons[0].setSelected(true);
action = MARCHE;
groupe = new
ButtonGroup();
for(int
i=0;i<3;i++){
groupe.add(boutons[i]);
}
}
public ControleAction(DemineurTheme p_theme);
Le rôle du constructeur se contente pour le moment à initialiser la variable theme avec le thème passé en argument, à créer les boutons, et à les ajouter à la barre d'outils que nous sommes en train de construire (n'oubliez pas que la classe ControleAction dérive de JToolBar).
Exercice 6.5.3 : Ecrivez le constructeur de la classe ControleAction!
Solution exo 6.5.3 [ Sujet ] :
public ControleAction(DemineurTheme
p_theme){
theme = p_theme;
CreerBoutons();
for(int
i=0;i<3;i++){
add(boutons[i]);
}
}
barre qui ne sert ... strictement à rien si nous ne gérons pas les événements utilisateur.
Précisons qu'on dit d'une classe C_A qu'elle est interne à une classe C_B lorsque C_A est définie dans C_B. Ainsi dans notre cas, où EcouteurRadio est interne à ControleAction, nous aurons la structure suivante :
Extrait du fichier ControleAction.java
[...]
public class ControleAction....{// méthodes, varialbles, etc. de la classe ControleAction
class EcouteurRadio.....{
// méthodes, variables, etc. de la classe interne EcouteurRadio
}
// autres méthodes éventuelles de la classe ControleAction
}
Les classes internes sont utiles lorsque l'on désire regrouper des classes qui font logiquement partie du même ensemble, tout en contrôlant leur visibilité. Dans notre cas, la classe créée pour la gestion des événements est en quelque sorte locale à la classe ControleAction, elle lui est logiquement associée puisqu' elle gère ses événements d'action, et n'a pas de raison d'être utilisée en dehors de cette classe.
Implémentez la (seule !) méthode
actionPerformed(ActionEvent
evt) de cette interface. Son rôle est de traiter les
événements
en provenance des boutons de la barre d'outils. Ce qui signifie qu'elle
va modifier le contenu de la variable action en fonction du bouton
qui aura généré l'événement.
Pour identifier ce bouton, vous utiliserez la commande d'action qui
lui a été associée (aidez-vous par exemple du tableau
noms_boutons
et
des méthodes de comparaison de chaînes de la classe
String...).
Solution 6.5.5 [ Sujet ]:
class EcouteurRadio implements
ActionListener{
public void actionPerformed(ActionEvent
evt){
int i=0;
while((i<3) &&
(evt.getActionCommand().compareTo(noms_boutons[i]) != 0))
i++;
switch (i){
case 0:
action=MARCHE;
break;
case 1 : action =
MARQUE_COOL;
break;
case 2 : action =
MARQUE_MINE;
break;
}
}
}
Il ne vous reste plus qu'à créer un écouteur d'événements EcouteurRadio dans votre constructeur ControleAction et à l'associer à chacun de vos boutons.
public ControleAction(DemineurTheme
p_theme){
theme = p_theme;
EcouteurRadio ecouteur = new
EcouteurRadio();
CreerBoutons();
for(int i=0;i<3;i++){
boutons[i].addActionListener(ecouteur);
add(boutons[i]);
}
}
Votre classe ControleAction est fin prête ! Et votre barre d'outils est opérationnelle, il suffira de l'associer à un cadre, et d'employer la méthode getAction() au bon moment pour l'exploiter.