Welcome to Bukkit France

Inscrivez-vous maintenant pour profiter d'un accès total à tout le contenu offert par la meilleur communauté Bukkit française ! Une fois inscrit et connecté, vous pourrez contribuez à la communauté en postant vos propres sujets et questions ou en répondant à ceux existants. Vous pourrez aussi customiser votre profil, recevoir des points de réputations, communiquer avec les autres membres via le chat, et plus encore! 

  • Annonces

    • Pskyco

      Bukkit France passe sous Discord !   02/20/16

      Bukkit France est désormais passé sur Discord, au revoir donc notre vieux Teamspeak ! Téléchargez le client et venez nous rejoindre sur notre salon en suivant les instructions suivantes.
      M-à-j du 25/02/2017 : Désormais, seuls les comptes actifs sur le forum se verront donner l'accès au Discord, ce dernier n'est pas une plateforme d'aide de la même manière que le chat.
  • billets
    47
  • commentaires
    40
  • vues
    31513

À propos de ce blog

Cours de Java

Billets dans ce blog

SystemGlitch

Prologue

blog-0426494001431880602.png

Bonjour chers/chères Bukkitiens/Bukkitiennes !

 

Bienvenue dans le cours de développement Java! Que pensez-vous de commencer par l'introduction? :lol:

 

 

 

Java32324.png

 

 

Java, non, on ne parle pas de la danse ridicule des années 1920!

 

Le Java est un langage de programmation apparu en 1995, et oui! 1995! :o Mais il était alors bien différent de celui que nous connaissons aujourd'hui! Il fut développé par l'entreprise Sun MicroSystems qui fut rachetée par Oracle Corporation en 2009. Il n'y pas si longtemps que ça finalement!

 

Java est un langage orienté objet robuste, puissant et surtout, très portable car il est multi-plateforme.

Sa grande portabilité réside dans un concept utilisé auparavant par le Pascal, il utilise une machine virtuelle que l'on nomme Java Runtime Environment (JRE), qui va faire l'intermédiaire entre le bytecode (écrit par le compilateur grâce au code de l'utilisateur), et va le transformer en langage compréhensible par la machine concernée. Il y a un autre aspect de sa "portabilité", se fût à sa création, sa syntaxe ressemble beaucoup à celle du C++ et ce n'est pas une coïncidence, les ingénieurs de l'époque travaillaient essentiellement en C, reprendre cette syntaxe permettait à une large communauté de pouvoir utiliser leur langage très rapidement, et c'est en grande partie grâce à cela que le Java est devenu vite populaire.

Sa puissante API permet de gérer un grand nombre de domaines comme les interfaces graphiques, la gestion des fichiers, que ce soit en local ou sur une base de données, et bien plus encore!

 

Ses possibilités sont très diversifiées. Les bases sont faciles à intégrer mais maîtriser les différents domaines demande plusieurs années de pratique... Mais ne vous inquiétez pas, nous irons pas-à-pas.

 

Contrairement à d'autres cours sur Internet, nous ne nous contenterons pas de dresser une liste exhaustive des possibilités du Java et de Bukkit car ce serait trop long et inutile! Nous ne somme pas là pour faire du bourrage de crane! Nous voulons vous fournir les outils nécessaires pour que vous sachiez vous débrouiller tout seul, sans être un assisté qui se contente de recopier un tutoriel!

Et plus important encore, vous comprendrez cette superbe vidéo!

 

 

 

 

 

 

 

Ne plongez pas la tête la première!

 

Afin de développer avec ce merveilleux langage, il vous faut d'abord installer Java, mais pas une simple JRE, la version Java SE Development Kit (JDK).

Nous tenons aussi à signaler que rien ne vous empêche d'installer plusieurs JDK et de choisir celle que vous voulez pour votre projet, via votre IDE.

 

Il est évidemment possible de développer à l'aide d'un simple éditeur de texte, mais avouez que l'idée même en est repoussante! Pour développer, il vous faudra des couleurs, de l'ordre et un environnement agréable, afin de mieux s'y retrouver au sein de son code. On appelle ces logiciels IDE (Integrated development environment). Nous allons explorer le monde du Java avec un IDE répondant au nom d'Eclipse. Vous pouvez télécharger la version dont nous aurons besoin ici!

 

Vous voila prêt à commencer me direz-vous! Et bien désolé de vous décevoir, mais non, il y a encore des choses à savoir avant de débuter. Vous vous doutez que ce genre d'outil n'est pas ce qu'il y a de plus simple à utiliser. :mellow:

 

Je pense que vous n'aurez pas trop de difficultés à apprendre les différents raccourcis clavier, ceux-ci sont spécifiés dans les menus.

Ceux-ci sont les plus importants:

  • Ctrl + Shift + O : Il vous permet de rapidement générer les imports dont vous aurez besoin pour faire fonctionner votre code.
  • Ctrl + S : Il permet de sauvegarder la classe sur laquelle vous travaillez actuellement.
  • Ctrl + Shift + S : Il permet de tout sauvegarder. Toutes les classes modifiées depuis la dernière sauvegarde seront sauvegardées !

 

Jetez un œil à cette rapide visite guidée de votre IDE :

 

Se8dlHS.png

 

 

Vous pourrez trouver plus d'informations à propos de votre IDE en consultant la documentation officielle d'Eclipse. Nous apprendrons tout de même quelques unes de ses fonctionnalités ensemble!

 

Lancez Eclipse. Une boite de dialogue apparaîtra. Spécifiez le dossier dans lequel vous voulez que tous vos projets soient sauvegardés. On appelle ce dossier "Workspace".

 

Faites un clic droit dans l'explorateur de packages et créez un nouveau projet :

 

LGske6A.png

 

Une boite de dialogue s'ouvrira pour vous demander de définir les paramètres de votre projet. Assurez-vous que ces derniers soient bien les mêmes que sur l'image suivante :

 

Akv3Wo5.png

 

Cliquez sur "Finish". Votre projet est créé! Nous le remplirons plus tard! Maintenant, passons aux choses sérieuses!

 

 

JeremGamer et JackBlue

 

tNIpRtq.png

SystemGlitch

Bonjour à toutes et tous,

 

Lors du passage à la dernière version d'IPBoard il y a quelques mois, il y a eu un problème avec les balises codes et le format en général dans les blogs. Parmi ces problèmes, on peut retrouver la perte de police, ou les retours à la ligne dans les balises code. Cela rendait les cours parfaitement illisibles.

 

J'ai récemment reformaté l'intégralité des cours, des TPs et des Hors-série pour qu'ils soient de nouveau lisibles ! :) Cela a pris beaucoup de temps mais je pense que cela en vaut la peine.

 

Si vous n'avez pas encore commencé votre aventure dans le monde de Java, c'est le moment !

 

Votre prof de Java préféré,

JeremGamer

Jackblue

blog-0778262001447837675.png

Salut,

 

J'ai vu que certains était intéressé par Forge dernièrement, ayant pas mal bossé avec, je ne pense pas faire une série de tutoriel car ça serai plutôt long et l’intérêt serai assez faible, vu le nombre de ressource déjà disponible mais peu exploité, et c'est avant tout du modding, donc un certains investissement et une qualité de recherche est nécessaire, bien que quelques bons tuyaux est appréciable c'est pourquoi je fais ce post aujourd'hui :)

 

Et cela resssemble plus à un recueil de sites/blog vraiment intéressant pour ceux qui veulent s'y mettre et comprendre un peu mieux le fonctionnement global de Minecraft, c'est assez riche en enseignement cependant il vous faut vraiment connaître le Java, se lancer dans le modding sans rien connaître du Java est une énorme perte de temps ;)

 

Vous pouvez déjà faire un tour sur la page de Download en cliquant sur le logo ci-dessous, afin de télécharger le package universal afin de développer votre mod.

 

 

ptiy1lQ.png?1

 

Et faire un petit tour sur les tutos de bases du wiki :

http://www.minecraftforge.net/wiki/Category:Generic_Mod

 

On continue avec le blog de wuppy29, qui est un modder qui tient depuis déjà un bon moment un blog avec les bases du modding sous Forge: http://www.wuppy29.com/minecraft/modding-tutorials/forge-modding/

 

Ensuite il y'a le site français minecraftforgefrance.fr qui peut vous apportez des informations sur ce que vous recherchez, leur tutoriel ne sont pas encore mis à jour en 1.8 mais ça reste intéressant de jeter un coup d'oeil:

http://www.minecraftforgefrance.fr/showthread.php?tid=16

 

Le blog de TheGreyGhost est pour moi une référence, car il traite de façon abstraite (peu de code, mais pas mal d'explication) les concept de Minecraft et c'est une vraie mine d'or si vous prenez le temps de lire et de comprendre ses articles: http://greyminecraftcoder.blogspot.fr/p/list-of-topics.html

 

Le blog de Jabelar, lui proposera des articles avec des cas concrets et du code complet, peut être plus rassurant pour les débutants, lui aussi très riche en enseignement : http://jabelarminecraft.blogspot.fr/

 

Le blog de CoolAlias propose des tutos un peu plus avancé, peut être plus vraiment à jour, surement quelques nom de méthode à changer ou d'objet différent mais l'esprit est là: http://www.minecraftforge.net/wiki/CoolAliasTutorials

 

Il y'a deux autres blogs intéressants mais que je n'ai personnellement pas utilisé mais qui peuvent être intéressant de faire un tour:

- Bedrockminer : http://bedrockminer.jimdo.com/modding-tutorials/

- TheXFactor117 : http://www.minecraftforum.net/forums/mapping-and-modding/mapping-and-modding-tutorials/2282788-1-7-1-8-thexfactor117s-forge-modding-tutorials-20

 

Voilà pour la liste exhaustive des blogs incontournables si vous voulez vous lancez dans le modding sous Forge, avec en plus l'arrivé prochaine de Sponge. Je rajouterai que Forge permet de faire des mods assez facilement si vous vous contentez des bases mais demandera un bien meilleur niveau dés que vous voudrez changé certains éléments de Minecraft, et donc tout comme le NMS vous serrez amenez à utilisé de la reflection, ou les Access transformer (aussi appelé ASM byte manipulation) utilisé par Forge pour changer l'accessibilité de certaines variables.

 

C'est tout pour ce petit hors-série sur Forge afin de bien débuté avec, et Vendredi nouveau chapitre sur la série Spigot !

 

Jackblue

ilicos

blog-0436006001446747183.jpg

Bonjour tout le monde,

 

Jackblue semblant être bien trop occupé pour avancer les cours de spigot actuellement, je me suis décidé à m'y mettre et contribuer à l'avancement du programme! :)

Étant programmeur étudiant dans le domaine du jeux-vidéo, je risque de généraliser un peu des concepts avant d'aborder le développement craftbukkit/spigot.

 

I - Introduction

 

Listener, écouteur en français... mais à quoi servent-il donc? à écouter, certes mais quoi? Des événements.

Les listeners vont capter un événement puis exécuter une callBack (fonction).

Un joueur ouvre son inventaire, l'événement InventoryOpenEvent va être dispatché et les listeners qui écouteront cet événement vont le capter et exécuter leurs fonctions.

 

II - Pourquoi utiliser des listeners && events

 

Imaginons que je programme un plateformer... mon joueur meurt, je pourrais lui faire appeler une fonction public de mon HUD pour changer le nombre de vies restantes.

Cependant, imaginons que plus tard je veuille que tout les ennemis aux alentours se réjouissent de la mort de mon héros. Je vais être contraint de rajouter cela sur mon joueur et de rajouter des fonctions publiques sur mes ennemis. Est-ce vraiment à un élément de mon jeu d'appliquer à tout les autres éléments les comportements qu'ils devraient avoir? Non, c'est plutôt à eux d'observer // d'écouter un événement PlayerDieEvent et de changer leurs animations en conséquence.

 

En supplément, utiliser un événement permet de simplifier le travail en équipe.

Je m'occupe du Player, JeremGamer des ennemis, mon player meurt:

- Dans le cas où je décide d'appeler une fonction publics des ennemis : "Hey jerem', tu pourrais rajouter cette fonction publique?"

- Dans le cas d'un événement, JeremGamer rajoute un listener et fait ce qu'il veut avec.

 

Cela permet d'avoir plus de liberté en séparant mieux les causes et les conséquences.

 

III - En pratique, les listeners

 

Avec bukkit, il faut créer sa propre class Listener qui va écouter un ou plusieurs événements et attacher cette class au pluginManager.

On commence par la création de la classe:

 
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class MonSuperListener implements Listener { 
}

 

La méthode pour attacher la class au pluginManager prend en paramètre un Listener d'où la nécessité d'implémenter Listener à votre Listener

 

Pour l'exemple, nous allons envoyer un message au joueur quand il se connecte.

 
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class MonSuperListener implements Listener {
    @EventHandler
    public void onPlayerConnection(PlayerJoinEvent event){        
        event.getPlayer().sendMessage("Bienvenue à toi! :D");    
    }
}

 

@EventHandler est une annotation permettant de signaler la fonction qui suit comme étant une callBack de listener. Le type d'event passé en paramètre déterminera quel événement est écouté. Le nom de la fonction importe peu, toujours est-il qu'il est préférable qu'elle soit compréhensible.

La documentation vous permettra de trouver l’événement qui correspond à ce que vous souhaitez écouter.

Ce même objet passé en paramètre contient différent informations et méthodes pour les récupérer. Certains événements peuvent être annulé par exemple BlockBreakEvent.

 
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class MonSuperListener implements Listener {    
    @EventHandler    
    public void onBlockBreak(BlockBreakEvent event){        
        event.setCancelled(true);    
    }
}

 

Le joueur va casser un block, l'évènement va être dispatché, intercepté par le listener, mon callBack va annuler l’événement et donc le bloc va ré-apparaître.

 

Faites attention, là j'écris qu'une seule fonction à chaque fois, mais je peux en mettre plusieurs, par contre il doit y avoir toujours @EventHandler devant chaque fonction. Sinon elles ne seront pas prise en compte comme callback.

 

Maintenant que l'on a créé un belle classe Listener, il va falloir l'ajouter au plugin manager, sinon, ça ne risque pas de fonctionner.

Pour ce faire, on récupère le pluginManager, on lui assigne le listener et on lui passe en second paramètre le plugin qui ajoute ce listener:

 
monInstanceDePlugin.getServer().getPluginManager().registerEvents(monInstanceDeListener, monInstanceDePlugin);

// - ou -

Bukkit.getPluginManager().registerEvents(monInstanceDeListener, monInstanceDePlugin);

// - ou -

Bukkit.getServer().getPluginManager().registerEvents(monInstanceDeListener, monInstanceDePlugin);

 

J'aurais tendance à conseiller le 2ème, le chemin de fonctions étant le plus cours.

 

Vous pouvez également supprimer un Listener

 
HandlerList.unregisterAll(monInstanceDeListener);

 

ou supprimer l'écoute d'un événement particulier sur un listener;

 
PlayerJoinEvent.getHandlerList().unregister(monInstanceDeListener);

 

Attention! L'instance doit être la même entre l'ajout et la suppression.

 

Il est bon de savoir que l'annotation @EventHandler peut prendre un paramètre: la priorité:

il y a 6 étapes:

  • EventPriority.LOWEST
  • EventPriority.LOW
  • EventPriority.NORMAL
  • EventPriority.HIGH
  • EventPriority.HIGHEST
  • EventPriority.MONITOR

 

 

Lorsqu'un évènement survient, Craftbukkit appelle les méthodes d'écoute qui ont une faible priorité en premier, et celle qui ont une forte priorité en dernier, afin que ces dernières aient le dernier mot sur le sort de l'évènement (notamment s'il est annulé ou pas). Par défaut la priorité est NORMAL.

 

Attention : La priorité MONITOR existe pour de l'observation, il ne faut pas l'utiliser pour avoir le dernier mot sur une modification//annulation d'événement.

Pour déclarer une propriété, au lieu d'écrire:

 
@EventHandler

 

on écrit:

 
@EventHandler(priority = EventPriority.HIGH)

 

Bien entendu, vous choisissez la propriété qui vous convient et vous utilisé une propriété seulement si vous en avez besoin. (Plusieurs plugins qui écoutent le même événement)

 

IV - Création d'événements

 

Vous pouvez également ajouter vos propres événements, pour cela, il faut créer sa classe qui étends Event.

 
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; 

public class MonSuperEvent extends Event {    
    private static final HandlerList handlers = new HandlerList();     
  
    @Override    
    public HandlerList getHandlers() {        
        return handlers;    
    }     
  
    public static HandlerList getHandlerList() {        
        return handlers;    
    }
}

 

Attention, il faut conserver ces méthodes et cette propriété. C'est elle qui contient tout les listeners.

Je peux rajouter d'autres méthodes et d'autres propriétés.

Je peux aussi implémenter Cancellable qui m'obligera à implémenter les méthodes

 
public boolean isCancelled()
public void setCancelled(boolean cancel)

 

J'aurais alors à les compléter pour les rendre fonctionnelles

 
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList; 

public class MonSuperEvent extends Event implements Cancellable{    
    private static final HandlerList handlers = new HandlerList();     

    @Override    
    public HandlerList getHandlers() {        
        return handlers;    
    }     

    public static HandlerList getHandlerList() {        
        return handlers;    
    }    

    public boolean isCancelled() {       
        return cancelled;    
    }     

    public void setCancelled(boolean cancel) {       
        this.cancelled = cancel;    
    }
}

 

Maintenant que j'ai créé mon Event, je peux tout à fait l'écouter comme un Evenement classique. Cependant, faut-il encore le dispatcher pour que mes Listener reçoivent l'événement.

Pour ce faire:

 
MonSuperEvent event = new MonSuperEvent();
Bukkit.getPluginManager().callEvent(event); 
//Je dispatche l'événement, tout mes listeners qui écoute l'évènement le captent et agissent éventuellement dessus, décident éventuellement de l'annuler...

if (event.isCancelled()){    
// Par conséquent, si mon évènement est annulé, J'annule l'action.
}

 

Rien ne m'empêche de rajouter des paramètres au constructeur de l’événement pour y ajouter des informations.

Je pourrais créer l'événement TeamWinEvent, et avoir des méthodes .getTeam() ou .getPlayersTeam() qui me renvoient des informations importantes et qui sont intéressantes.

 

V - Une chose à savoir mais pas forcément à faire

Au lieu de créer une class pour votre listener, vous pouvez directement implémenter Listener à votre plugin et utiliser votre propre classe plugin comme listener

 
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;

public class MonSuperPlugin extends JavaPlugin implements Listener {    
    @Override    
    public void onEnable(){        
        getServer().getPluginManager().registerEvents(this, this);    
    }    

    @EventHandler    
    public void onPlayerJoin(PlayerJoinEvent event){        
        event.getPlayer().sendMessage("Koukou twa ! :D");    
    }
}

 

C'est bien si c'est juste pour tester quelque chose rapidement... mais ça devient vite le foutoir sur votre classe principale.

 

Résumé:

  • Événements et listeners offrent une plus grande liberté d'action en séparant cause et conséquences.
  • Une fois notre classe listener créée, on lui ajoute les méthodes d'écoutes sans oublier l'annotation @EventHandler et on enregistre notre listener auprès du PluginManager
  • Possibilité d'annuler certains événements : setCancelled(true);
  • Possibilité de créer ses propres événements, de les dispatcher quand on veut et de les écouter comme les événements classiques

 

JeremGamer a également créé des cours sur les évènements et les listeners relatifs aux guis pour javax.swing et java.awt.

http://www.bukkit.fr/blog/3/entry-61-chapitre-28-les-%C3%A9v%C3%A8nements-partie-1/

http://www.bukkit.fr/blog/3/entry-62-chapitre-29-les-%C3%A9v%C3%A8nements-partie-2/

 

ilicos

SystemGlitch

blog-0400991001439390756.png

Vous les connaissez, il s'agit de ces petites fenêtres servant à afficher une information comme un avertissement ou une erreur, à demander une validation du style "Voulez-vous vraiment quitter? -> Oui / Non" ou encore demander une information à l'utilisateur.

En Java, elles portent le nom de JDialog tout simplement! Le JDK vous en propose déjà plusieurs toutes prêtes, ce qui est très pratique, mais si jamais vous avez besoin d'une boite de dialogue plus spécifique, il faudra alors la faire vous-même! N'ayez crainte, rien de bien difficile, c'est la même chose que pour les JFrame à une ou deux différences près.

 

Première différence: le reste du programme est figé tant que la boite de dialogue n'est pas fermée, à moins d'utiliser un Thread à part, mais nous n'avons pas encore vu comment faire.

Seconde différence: il est possible de rendre les JDialog modales, c'est à dire que tant qu'elles seront ouvertes, on ne pourra rien faire d'autre sur le logiciel que d’interagir avec la boite de dialogue. Vous en avez aussi rencontré j'en suis sûr.

 

Commençons par la partie la plus simple : les boites de dialogue informatives. Attention, pour les dialogs préfabriquées on utilisera JOptionPane et non pas JDialog ! Nous allons ajouter trois boutons à notre content pane, chacun ouvrira une boite de dialogue différente. Ces dernières sont modales, ce qui vous permettra de vous faire d'ores et déjà un idée de ce que cela signifie.

 
JButton info = new JButton("Ouvrir boite d'information");
info.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent arg0) {
		JOptionPane.showMessageDialog(null, "Message d'information", "Information", JOptionPane.INFORMATION_MESSAGE); //message d'information
	}
});

JButton warn = new JButton("Ouvrir boite d'avertissement");
warn.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent arg0) {
		JOptionPane.showMessageDialog(null, "Message préventif", "Attention", JOptionPane.WARNING_MESSAGE); //Message d'avertissement
	}

});

JButton err = new JButton("Ouvrir boite d'information");
err.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent arg0) {
		JOptionPane.showMessageDialog(null, "Message d'erreur", "Erreur", JOptionPane.ERROR_MESSAGE); //Message d'erreur
	}

});

 

kiF8YQb.pngYSZhJgv.pngQHrE7Sy.png

 

Revenons plus en détails sur la méthode que j'ai utilisé :

JOptionPane.showMessageDialog(null, "Message d'information", "Information", JOptionPane.INFORMATION_MESSAGE);

 

Le premier argument est le JComponent parent. C'est à dire le composant au dessus du quel devra s'ouvrir la fenêtre. S'il y en a pas comme ici, on met null. On aurait cependant pu mettre this pour centrer la boite de dialogue au dessus du content pane, même si la fenêtre principale avait été déplacée.

Le second argument est le message que l'utilisateur verra dans la boite de dialogue.

Le troisième est le titre de la boite de dialogue, affiché dans la barre d'action.

Et le dernier est celui qui définira s'il s'agit d'un message d'information, d'erreur ou d'avertissement. D'ailleurs il est possible de simplement mettre un message, sans icône, à l'aide de JOptionPane.PLAIN_MESSAGE, ou alors une question avec JOptionPane.QUESTION_MESSAGE.

 

Cette méthode est surchargée. Voyons un peu ce que l'on peut faire de plus ! :) Nous avons deux surcharges à notre disposition, l'une rajoute un argument de type ImageIcon, qui nous permettra donc de changer l'icône, l'autre permet de créer un boite de dialogue la plus simple qui soit car seuls les deux premiers arguments sont conservés.

 

Voici comment changer l'icône qui sera affichée :

ImageIcon img = new ImageIcon("check.png");
JOptionPane.showMessageDialog(null, "Sauvegarde réussie!", "Succès", JOptionPane.INFORMATION_MESSAGE , img);

 

4BPvWff.png

 

Jetons maintenant un œil aux boites de confirmation. Cette fois la méthode utilisée sera showConfirmDialog(). La différence c'est que celle-ci retourne un int indiquant le choix de l'utilisateur. Vous verrez ce sera tout de suite plus clair en voyant le code :

int option =JOptionPane.showConfirmDialog(null, "Êtes-vous sûr de vouloir quitter?", "Vraiment?" , JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); //Vous pouvez encore changer l'icône en ajoutant une argument du type ImageIcon
if(option == JOptionPane.YES_OPTION)
	System.out.println("Quitter");
else if(option == JOptionPane.NO_OPTION)
	System.out.println("Ne pas quitter");

 

tDbYkvK.png

 
 
int option =JOptionPane.showConfirmDialog(null, "Le fichier est introuvable, continuer?", "Fichier introuvable" , JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE);
if(option == JOptionPane.YES_OPTION)
	System.out.println("Continuer");
else if(option == JOptionPane.NO_OPTION)
	System.out.println("Ne pas continuer");
else if(option == JOptionPane.CANCEL_OPTION)
	System.out.println("Annuler");
else if(option == JOptionPane.CLOSED_OPTION) //L'utilisateur a fermé la boite de dialogue sans passer par les boutons "Oui" "Non" ou "Annuler"
        System.out.println("Fermer");

 

eDGwsly.png

 

Le principe est acquis, on passe à la suite! En effet ici on stockait une int renvoyée par la méthode, mais si on faisait une boite de dialogue qui demande à l'utilisateur d'écrire une chaine de caractères? Et bien c'est pareil! Sauf qu'on aura une String en retour :

String answer = JOptionPane.showInputDialog(null, "Comment vous appelez-vous?", "Vos identifiants" , JOptionPane.QUESTION_MESSAGE);
System.out.println("L'utilisateur s'appelle \""+ answer + "\".");

 

W60Bb0P.png

 

 

Allons un peu plus loin et proposons différents choix via un menu déroulant. On créera un tableau contenant les valeurs possibles et le donnera en argument :

String[] sex = { "Homme" , "Femme" , "Autre" };
String answer = (String) JOptionPane.showInputDialog(null, "Veuillez indiquer votre sexe.", "Vos identifiants" , JOptionPane.QUESTION_MESSAGE , null , sex , sex[0]);
System.out.println("Sexe de l'utilisateur: " + answer);

 

AlhcOuR.png

 

Le dernier argument est celui qui sera sélectionné par défaut dans le menu déroulant. Attention si l'utilisateur ferme la boite de dialogue autrement qu'en appuyant sur "OK", la valeur retournée sera null, et cela est aussi valable pour le champ de saisie vu juste au dessus! Il faudra donc bien vérifier vos résultats! Quand à l'argument null donné dans la méthode, il s'agit de l'icône modifiée. On ne souhaite pas la changer donc on donne null. ^_^

 

Voici la dernière variante de boite de dialogue préfabriquée: c'est le même principe que celle que nous venons de voir mais cette fois il y aura plusieurs boutons et non pas un menu déroulant.

String[] sex = { "Homme" , "Femme" , "Autre" };
int answer = JOptionPane.showOptionDialog(null, "Veuillez indiquer votre sexe.", "Vos identifiants" , JOptionPane.YES_NO_CANCEL_OPTION , JOptionPane.QUESTION_MESSAGE, null , sex , sex[0]);
//answer est le numéro du bouton qui a été cliqué par l'utilisateur, -1 si la fenêtre à été fermée par un autre moyen
//Vous pouvez donc mettre plus ou moins de trois boutons
if(answer >= 0)
	System.out.println("Sexe de l'utilisateur: " + sex[answer]);

 

14EDh1L.png

 

Bien, vous avez déjà de quoi faire. Mais certains cas ne sont pas gérés, ou alors, vous souhaitez simplement faire un boite de dialogue ayant un style visuel bien particulier. Vous savez déjà faire tout ça! Le procédé est exactement le même que pour la création d'une JFrame, sauf que votre classe héritera de JDialog. ;)

Votre code cadeau sera une boite de dialogue d'erreur faite maison! Pensez à l'utiliser avec le code cadeau donné dans le chapitre sur les exceptions ! :)

package fr.bukkit.jeremgamer.cours;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

import javax.swing.Box;
import javax.swing.Icon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;

public class CustomDialog extends JDialog {

    private JLabel message = new JLabel("<html><b>Une erreur est survenue.</b><br>Certaines fonctionnalités risquent des disfonctions.<br>Il est conseillé de redémarrer le logiciel.<br><em>Support: [email protected]<script cf-hash='f9e31' type="text/javascript">
/*  */</script></em></html>");
    private JButton copy = new JButton("Copier le rapport");
    private JButton close = new JButton("Ok");
    
    private Toolkit toolkit = Toolkit.getDefaultToolkit();
    private Clipboard cb = toolkit.getSystemClipboard();
    
    private DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    private Date date;

    private StringWriter sw = new StringWriter();
    private PrintWriter pw = new PrintWriter(sw);
    
    private Throwable e;
    
    public CustomDialog(final Throwable e) {
        
        date = new Date();
        this.e = e;
        
        this.setModal(true); //On rend notre boite de dialogue modale
        this.setTitle("Erreur");
        this.setResizable(false);
        this.setSize(new Dimension(400, 150));
        this.setLocationRelativeTo(null);
        
        JPanel messageContent = new JPanel() {

            /**
             *
             */
            private static final long serialVersionUID = 3692136522118161020L;

            protected void paintComponent(final Graphics g) {
                Icon icon = UIManager.getIcon("OptionPane.errorIcon");    
                BufferedImage image = new BufferedImage( icon.getIconWidth() , icon.getIconHeight() , BufferedImage.TRANSLUCENT );
                icon.paintIcon(null, image.getGraphics() , 0 , 0 );
                g.drawImage(image, 20 , 35, 48 , 48 , null);
                super.paintComponents(g);
            }
        };

        messageContent.add(Box.createRigidArea(new Dimension(400 , 0)));
        messageContent.add(Box.createRigidArea(new Dimension(70 , 0)));

        FlowLayout flow = new FlowLayout();
        flow.setAlignment(FlowLayout.RIGHT);
        flow.setVgap(5);
        flow.setHgap(5);
        JPanel buttons = new JPanel(flow);
        buttons.add(copy);
        buttons.add(close);
        final Container glass = (Container) this.getGlassPane();
        glass.setVisible(true);
        glass.setLayout(new BorderLayout());
        glass.add(buttons , BorderLayout.SOUTH);

        messageContent.add(message);

        close.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                dispose();
            }            
        });

        copy.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent event) {
                cb.setContents( new StringSelection(getReport()) , null );             
            }            
        });

        this.setContentPane(messageContent);        
        
        this.setVisible(true);
    }
    
    private String getReport() {
        e.printStackTrace(pw);
        return new String( "Rapport d'erreur :\n"
                + "Version : " + "0.0.1" + "\n"
                + "Date : " + dateFormat.format(date) + "\n"
                + "Système d'exploitation : " + System.getProperty("os.name") + " " + System.getProperty("os.version") +"\n\n"
                + "StackTrace :\n" + sw.toString());
    }
    
}

 

TKuM2ih.png

 

 

Résumé :

 

  • Les boites de dialogue sont de petites fenêtres permettant d'afficher une information ou d'en demander une à l'utilisateur.
  • Elles figent le reste du programme tant qu'elles sont ouvertes.
  • Les boites de dialogues modales empêchent toute interaction avec le reste du logiciel tant qu'elle sont ouvertes.
  • Pour les boites de dialogue préfabriquées, on utilisera JOptionPane.
  • Pour les boites de dialogue personnalisées, on créera une classe qui hérite de JDialog.

 

 

JeremGamer

 

2cp1ED5.png

SystemGlitch

blog-0359394001438853478.png

Dans ce chapitre, vous apprendrez à créer des barres d'outils, des listes sous forme graphique ainsi qu'à utiliser un explorateur de fichiers. Vous n'en serez pas surpris vu le titre du chapitre. ^_^

Commençons par les barres d'outils. Vous les connaissez bien et elles vous servent tous les jours! Ce sont en effet ces barres regroupant un certain nombres d'icônes cliquables qui permettent donc des actions rapides comme changer la police ou le gras, souligné, etc... dans les logiciels de traitement de texte! Nous utiliserons l'objet JToolBar. C'est très simple : le principe est d'y ajouter des JComponent comme les JButtons et d'indiquer où elle apparaitra. Car il faudra le spécifier lors de sa création.

 
JToolBar toolBar = new JToolBar();

JToggleButton bold = new JToggleButton("<html><b>B</b></html>");
JToggleButton italic = new JToggleButton("<html><i>I</i></html>");
JToggleButton underlined = new JToggleButton("<html><u>U</u></html>");
JComboBox<String> font = new JComboBox<String>();

GraphicsEnvironment e = GraphicsEnvironment.getLocalGraphicsEnvironment(); //On récupère l'environnement graphique
Font[] fonts = e.getAllFonts(); //Puis toutes les polices installées
for (Font f : fonts) { 
	font.addItem(f.getName()); //Afin de les ajouter dans le menu déroulant
}

toolBar.add(bold);
toolBar.add(italic);
toolBar.add(underlined);
toolBar.addSeparator(); //On ajoute une barre de séparation
toolBar.add(font);

this.setLayout(new BorderLayout());
this.add(toolBar , BorderLayout.NORTH); //On positionne la barre en haut du panneau

 

YKlMDeS.png

 

Certains auront remarqué que la barre d'outil est déplaçable à l'aide de la petite zone sur la gauche de la barre.

 

5tEBweX.png

 

En effet il sera possible de la repositionner en bas du panneau, à sa gauche, à sa droite, etc... et même dans une petite boite de dialogue à part ! :D

Il est possible de désactiver la possibilité de la déplacer ainsi :

toolBar.setFloatable(false);

 

Vous aurez peut-être remarqué que les boutons ne se "surlignent" pas lors du survol de la souris. Il faut activer cette fonctionnalité qui ne l'est pas par défaut sur les barres d'outils :

toolBar.setRollover(true);

 

Passons aux listes graphiques. L'objet que nous utiliserons s'appelle JList. La généricité est importante dans l'utilisation de cet objet. Nous utiliserons un argument générique de type String la plupart du temps. Mais attention, pour une fois c'est un tout petit peu plus compliqué car nous devrons passer par un autre objet, un modèle pour la liste : DefaultListModel.

JList<String> productList = new JList<String>();
DefaultListModel<String> data = new DefaultListModel<String>();
data.addElement("Aspirateur");
data.addElement("Moteur");
data.addElement("Tondeuse");
data.addElement("Machine à laver");
data.addElement("Fer à repasser");
data.addElement("Mixeur");
data.addElement("Micro-ondes");
data.addElement("Four");
data.addElement("Plaque de cuisson");
//...
		
productList.setModel(data); //On applique le modèle à la liste

 

9jMJtEg.png

 

Voila l'utilisation la plus basique d'une JList. Maintenant voyons les autres possibilités...

Pour commencer nous allons modifier les paramètres de sélection. Par défaut, la sélection est unique, voici comment gérer la sélection multiple :

productList.setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); //Sélection unique
productList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); //Sélection multiple 

 

k3aDQ6y.png

 

Vous devrez utiliser CTRL ou SHIFT pour effectuer une sélection multiple comme vous avez l'habitude de le faire avec n'importe quelle autre application. ;)

Et si on changeait la disposition de notre liste?

productList.setLayoutOrientation(JList.VERTICAL); //Verticale, par défaut
productList.setLayoutOrientation(JList.VERTICAL_WRAP); //Du haut vers le bas, puis affiche une autre colonne lorsqu'on a atteint le bas de la liste
productList.setLayoutOrientation(JList.HORIZONTAL_WRAP); //Horizontale, de gauche à droite, puis retour à la ligne

 

Le screenshot suivant montre le résultat avec JList.HORIZONTAL_WRAP :

 

Taz4S4Y.png

 

Nous allons mettre notre liste dans un JScrollPane, puis définir le nombre d'items visibles à la fois :

productList.setVisibleRowCount(4); //Mettre -1 pour ne pas donner de limite, 8 par défaut
JScrollPane listScroller = new JScrollPane(productList);

this.add(listScroller);

 

BOhQG12.png

 

Vous pouvez aussi changer le layout complet des listes afin d'avoir un rendu complètement personnalisé. Attention, cette fois on utilisera des JComponent directement ajoutés à la liste, plus de modèle! Mais enfin, vous avouerez que c'est n'est pas très utile, autant utiliser un JPanel. Surtout que le layout ne fonctionne pas vraiment comme on l'espérait sur la liste :

JList<String> productList = new JList<String>();
productList.setLayout(new GridLayout(5,5));


productList.add(new JButton("Aspirateur"));
productList.add(new JButton("Moteur"));
productList.add(new JButton("Tondeuse"));
productList.add(new JButton("Machine à laver"));
productList.add(new JButton("Fer à repasser"));
productList.add(new JButton("Mixeur"));
productList.add(new JButton("Micro-ondes"));
productList.add(new JButton("Four"));
productList.add(new JButton("Plaque de cuisson"));
//...
		
JScrollPane listScroller = new JScrollPane(productList);
		
this.add(listScroller);

 

Pc0Cb7i.png

 

Pour faire cela correctement on utilisera plutôt un ListCellRenderer que vous devrez écrire vous-même! Celui par défaut n'affiche que des String.

Tout ce que vous avez à faire c'est créer une classe qui implémente ListCellRenderer puis appeler la méthode setCellRenderer() et donner une instance de cette classe en argument ! ;)

Voici un petit exemple pour pouvoir afficher des JLabel (ce sera votre code cadeau :) ) :

public class CustomCellRenderer extends JLabel implements ListCellRenderer<Object> {

	/**
	 * 
	 */
	private static final long serialVersionUID = -6145242041026326434L;


	private String[] texts = { "Allemagne" , "Belgique" , "France" , "Italie" };
	private ImageIcon[] images = new ImageIcon[texts.length];

	public CustomCellRenderer() {
		setOpaque(true);
		setVerticalAlignment(CENTER);
		for(int i = 0; i < texts.length ; i++) {
			images[i] = new ImageIcon(texts[i] + ".png");
			images[i].setDescription(texts[i]);
		}
	}

	@Override
	public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {


		if (isSelected) {
			setBackground(list.getSelectionBackground());
			setForeground(list.getSelectionForeground());
		} else {
			setBackground(list.getBackground());
			setForeground(list.getForeground());
		}

		ImageIcon icon = images[index];
		String text = texts[index];
		setIcon(icon);
		if (icon != null) {
			setText(text);
			setFont(list.getFont());
		}
		return this;
	}
}

 

Et instanciez votre JList de cette manière :

JList countryList = new JList(new Integer[4]); //4 étant le nombre d'items
CustomCellRenderer renderer = new CustomCellRenderer();
countryList.setCellRenderer(renderer);

 

wj8Z1Lu.png

 

Il reste une chose à voir avec les JList. Comment les écouter? Nous utiliserons un MouseAdapter. Mais il faudra faire attention à plusieurs choses !

productList.addMouseListener(new MouseAdapter() {

	@Override
	public void mouseClicked(MouseEvent e) { //Nous allons effectuer quelque chose lors du double clic sur un item
		int index = productList.locationToIndex(e.getPoint()); //égal à -1 si l'utilisateur clique en dehors d'un item
		if (e.getClickCount() == 2 && index != -1) { //Double clic et on s'assure que l'utilisateur clique bien sur un item
			//...
		} else if (e.getClickCount() == 3 && index != -1) { //Supporter le triple clic
			//...
		}
	}
});

 

C'est tout pour les listes! Il y a bien d'autres choses à faire avec mais ce serait trop long de tout détailler alors je vous laisse vous renseigner par vous-même. ;)

Pour finir ce chapitre, je vais vous présenter un outil très pratique, à votre disposition: j'ai nommé le JFileChooser.

Le principe est de faire choisir un fichier par l'utilisateur, vous en rencontrez souvent, par exemple lorsque vous faites "Enregistrer sous...". C'est très simple en soi, mais de nombreuses subtilités peuvent être ajoutées :

String path = null;
JFileChooser chooser = new JFileChooser();
int option = chooser.showOpenDialog(null);
if(option == JFileChooser.APPROVE_OPTION) { //Si l'utilisateur clique sur "Ouvrir" ou appuie sur Entrée
	path = chooser.getSelectedFile().getAbsolutePath();
	System.out.println(path);
}

 

eiTKlkt.png

 

Une GUI toute faite ! Pourquoi s'en priver? :)

Sans plus tarder, voici les subtilités évoquées il y a un instant :

Tout d'abord, vous pouvez modifier ce qui est sélectionnable :

chooser.setFileSelectionMode(JFileChooser.FILES_ONLY); //Fichiers uniquement
chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); //Dossiers uniquement 
chooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES); //Fichiers ou dossiers
chooser.setMultiSelectionEnabled(true); //Pour activer les sélections multiples

 

Ensuite, vous pouvez appliquer des filtres. C'est à dire que vous pouvez choisir les extensions de fichiers qui seront affichées. Avec le code suivant, seules les images seront affichées dans le sélecteur :

FileNameExtensionFilter filter = new FileNameExtensionFilter("Images", "jpg", "png", "gif", "jpeg" , "bmp");
//Le premier argument sera l'indication affichée à l'utilisateur
chooser.setFileFilter(filter);
chooser.setAcceptAllFileFilterUsed(false); //Permet d'empêcher l'utilisateur de retirer le filtre grâce au menu déroulant

 

A l'aide de setCurrentDirectory(File dir), vous pouvez rediriger le sélecteur vers un dossier donné.

Sinon, vous pouvez définir dans quel dossier s'ouvrira le sélecteur. Pour cela il faudra directement le spécifier dans le constructeur :

JFileChooser chooser = new JFileChooser("C:\\Cours Java");

 

 

Résumé :

 

  • Les JToolBar peuvent accueillir tous les JComponent.
  • Les JList s'utilisent avec un modèle en général, du type DefaultListModel.
  • Pensez à prévoir un JScrollPane pour les JList au cas où il y aurait beaucoup d'items.
  • Les JFileChooser permettent à l'utilisateur de sélectionner un fichier.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0266210001438172279.png

Dans ce chapitre, vous apprendrez à créer vos propres menus! Nous verrons les barres de menu que vous avez l'habitude de voir en haut des fenêtres, là où se trouve "Fichier", "Édition", etc... ainsi que les menus pop-up, aussi appelés menus contextuels, pouvant apparaître n'importe où! Vous avez l’habitude de voir ces derniers apparaitre lorsque vous cliquez-droit. ;)

 

Comme tout ce que nous voyons depuis un moment, il ne s'agit que de l'utilisation de composants Swing, ce qui veut dire que ce sera très simple une fois de plus. :P

 

Voici le principe: une barre de menu JMenuBar, des menus JMenu et des items à mettre dedans, les JMenuItem. Attention cependant : cette fois-ci nous travaillons sur l'objet de notre fenêtre et non sur le content pane. Il existe des variantes de JMenuItem qui permettent d'insérer les autres composants de formulaire que vous avez vu jusque là: le JCheckBoxMenuItem et JRadioButtonMenuItem.

 

En pratique, cela donne quelque chose qui ressemble à ça :

 
JMenuBar menuBar = new JMenuBar();
		
JMenu file = new JMenu("Fichier");
JMenuItem new_ = new JMenuItem("Nouveau");
JMenuItem open = new JMenuItem("Ouvrir");
JMenuItem save = new JMenuItem("Sauvegarder");
		
JMenu subMenu = new JMenu("Sous-menu");
JRadioButtonMenuItem rb0 = new JRadioButtonMenuItem("Choix n°1");
JRadioButtonMenuItem rb1 = new JRadioButtonMenuItem("Choix n°2");
ButtonGroup bg = new ButtonGroup();
bg.add(rb0);
bg.add(rb1);
subMenu.add(rb0);
subMenu.add(rb1);

file.add(new_);
file.add(open);
file.addSeparator(); //On ajoute une petite barre de séparation
file.add(subMenu);
file.addSeparator();
file.add(save);
		
JMenu edit = new JMenu("Édition");
JCheckBoxMenuItem cb0 = new JCheckBoxMenuItem("Option n°1");
JCheckBoxMenuItem cb1 = new JCheckBoxMenuItem("Option n°2");
edit.add(cb0);
edit.add(cb1);
		
menuBar.add(file);
menuBar.add(edit);
		
this.setJMenuBar(menuBar); //On applique la barre à la fenêtre

 

I30RHmW.png

 

Je vous laisse expérimenter par vous-même. ;) Vous aurez surement deviné la manière d’interagir avec nos items. Car pour l'instant, ils ne font rien, absolument rien. Comme d'habitude, nous utiliserons les Listener :

JMenuItem new_ = new JMenuItem("Nouveau");
new_.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("Menu \"" + new_.getText() + "\" cliqué!");
	}
	
});

 

Allons plus loin : ajoutons des raccourcis clavier. Il en existe deux types: les accélérateurs, qui permettent un simple raccourci vers un élément du menu, et les mnémoniques qui permettent de simuler un clic sur un point de menu, ce qui revient presque au même. Ces derniers seront utilisés pour les menus et non pour les items.

JMenu file = new JMenu("Fichier");
JMenuItem new_ = new JMenuItem("Nouveau");
new_.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("Item \"" + new_.getText() + "\" cliqué!");
	}
	
});
new_.setAccelerator(KeyStroke.getKeyStroke('n')); //Touche n
		
		
JMenuItem open = new JMenuItem("Ouvrir");
open.addActionListener(new ActionListener() {

       @Override
       public void actionPerformed(ActionEvent e) {
               System.out.println("Item \"" + open.getText() + "\" cliqué!");
       }
            
});
open.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O , KeyEvent.CTRL_DOWN_MASK + KeyEvent.SHIFT_DOWN_MASK)); //CTRL+SHIFT+O

JMenuItem save = new JMenuItem("Sauvegarder");
save.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		System.out.println("Item \"" + save.getText() + "\" cliqué!");
	}
	
});
save.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S , KeyEvent.CTRL_MASK)); //CTRL+S

 

GOFRLz4.png

 

Vous pouvez voir que les raccourcis s'affichent à côté de l'item en plus de ça ! :D

 

Pour les mnémoniques, c'est le même principe, sauf qu'il faudra presser Alt + la touche définie pour que le menu se déroule :

JMenu file = new JMenu("Fichier");
file.setMnemonic('F');
//...
JMenu edit = new JMenu("Édition");
edit.setMnemonic('É');

 

ojxsRCF.png

 

Cette fois le caractère défini est souligné s'il est présent dans le texte du menu.

 

Vous pouvez aussi initialiser les JMenu avec un mnémonique directement :

JMenuItem open = new JMenuItem("Ouvrir" , 'O');

 

J'ai une bonne nouvelle : ça ne s'arrête pas là ! Il est possible, en réalité, d'ajouter n'importe quel JComponent au menu !

file.add(new JButton("Bouton"));
file.add(new JLabel("Label"));
file.addSeparator();
JPanel pan = new JPanel();
pan.setBackground(Color.RED);
file.add(pan);

 

y1pSTuv.png

 

Et mieux encore : il est même possible de leur attribuer un mnémonique ! (Mais pas d'accélérateur) Ce dernier fonctionnera uniquement si le menu est déjà déroulé. Il y a quand cependant quelques exceptions : les JLabel ou les JPanel par exemple, car ils ne sont pas censé être des composants avec lesquels l'utilisateur interagit.

 

 

Passons aux menus contextuels. Vous savez l'essentiel, plus qu'à rajouter quelques fonctionnalités comme la position où apparaitra le menu ainsi que le fait qu'il apparaisse à l'aide d'un clic droit. Commençons par préparer notre menu :

JPopupMenu menu = new JPopupMenu();
        
JMenuItem new_ = new JMenuItem("Nouveau");
JMenu subMenu = new JMenu("Sous-menu");
JRadioButtonMenuItem rb0 = new JRadioButtonMenuItem("Choix n°1");
JRadioButtonMenuItem rb1 = new JRadioButtonMenuItem("Choix n°2");
ButtonGroup bg = new ButtonGroup();
bg.add(rb0);
bg.add(rb1);
subMenu.add(rb0);
subMenu.add(rb1);
	
menu.add(new_);
menu.add(open);
menu.addSeparator();
menu.add(subMenu);
menu.addSeparator();
menu.add(save);

 

Parfait. Maintenant plus qu'à faire en sorte qu'il apparaisse avec le clic droit :

JPanel content = new JPanel();
		
content.addMouseListener(new MouseAdapter(){

	public void mouseReleased(MouseEvent event){
		if(event.isPopupTrigger())   //Clic droit    
			menu.show(content, event.getX(), event.getY()); //Afficher le menu sur le contentPane 
	}

});

this.setContentPane(content);

 

EARUm3b.png

 

Pour vous montrer une autre application possible des JPopupMenu, voici un petit code cadeau que je vous invite fortement à tester. Ce n'est pas le meilleur moyen de faire ce qu'il fait, pas non plus le plus optimisé, mais ça illustre bien ce que pourrait faire cet objet.

ArrayList<String> producList = new ArrayList<String>();
productList.add("Aspirateur");
productList.add("Moteur");
productList.add("Tondeuse");
productList.add("Machine à laver");
productList.add("Fer à repasser");
productList.add("Mixeur");
productList.add("Micro-ondes");
productList.add("Four");
productList.add("Plaque de cuisson");
//...

JPopupMenu suggestions = new JPopupMenu();
suggestions.setMinimumSize(new Dimension(200 , 25));
suggestions.setFocusable(false);

JTextField searchField = new JTextField();
searchField.setPreferredSize(new Dimension(200 , 25));

searchField.addCaretListener(new CaretListener() {

	@Override
	public void caretUpdate(CaretEvent e) {
		suggestions.removeAll();
		for(String s : productList)
			if(s.startsWith(searchField.getText()))
				suggestions.add(new JMenuItem(s));
		if(suggestions.getComponents().length > 0) {
			suggestions.show(searchField, searchField.getX(), searchField.getY() + searchField.getHeight() - 7);
			suggestions.pack();
			suggestions.revalidate();
			suggestions.repaint();
		}
	}
});

 

 

Résumé :

 

  • Les JMenu sont placés dans une JMenuBar ou dans un JPopupMenu.
  • Ils contiennent des JMenuItem.
  • Tous les JComponent peuvent être ajoutés aux JMenu.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0469592001437294857.png

Vous possédez un Raspberry Pi et vous souhaitez en faire un peu plus qu'un simple media-center? Il est tant de s'amuser un peu! Nous allons programmer notre Raspberry Pi en Java et le faire interagir avec le monde réel à l'aide de capteurs et de dipôles.

Notre projet sera le suivant: nous souhaitons allumer une LED lorsqu'un capteur détecte un mouvement, en passant par le logiciel afin de pouvoir étendre les perspectives d'évolution du prototype. :)

 

Cette vidéo que j'ai réalisée illustre le résultat final :

 

 

Je tiens à préciser que je considèrerai que vous disposez de connaissances basiques en électricité ainsi que de tout ce que je vous ai appris sur le Java jusque là. ;)

 

Matériel :

 

  • Un Raspberry Pi, de n'importe quel modèle. Ici j'utilise le modèle 2 B, le plus récent.
  • Tout ce qu'il vous faut pour le faire fonctionner pour une utilisation basique, à savoir:
    • Un câble d'alimentation 5V 2A
    • Un écran
    • Un clavier
    • Une souris
    • Un dongle Wifi (ou un câble Ethernet, nous aurons besoin d'une connexion internet)
    • Une carte Micro-SD avec Raspbian installé dessus
  • 3 câbles de montage femelle/femelle

  • 2 câbles de montage mâle/femelle

  • 1 câble de montage mâle/mâle

  • Une résistance 270Ω

  • Une LED

  • Un capteur PIR (de mouvement)

  • Une breadboard pour tout assembler

 

 

Vous pourrez trouver tout ce matériel sur le site ModMyPi pour des prix raisonnables et une livraison rapide. ;) Même si vous ne faites pas le montage de vos mains, rien ne vous empêche de quand même suivre ce chapitre. :lol:

 

Pour commencer, nous allons installer Java sur la machine. Ouvrez un terminal depuis votre Raspberry Pi (en considérant que vous le faites tourner sous Raspbian) et tapez la commande suivante, en vous assurant au préalable de bien être connecté à internet :

sudo apt-get update && sudo apt-get install oracle-java7-jdk

 

En attendant que tout cela s'installe, lancez Eclipse, nous allons préparer notre projet.

Nous aurons besoin de l'API Pi4J. Créer un nouveau projet et ajoutez l'API fraichement téléchargée au Build Path. Tout est prêt, passons désormais à la partie qui vous est la plus étrangère: le montage.

 

Tout d'abord, il faut savoir quelques petites choses. Le Raspberry Pi 2 B est doté de 40 broches dont 26 GPIOs (General Purpose Input/Output). Nous utiliserons ces broches pour connecter nos capteurs et dipôles. Nous ne pourrons pas utiliser certaines de ces broches, ces dernières sont prévues pour d'autres utilisations. Cependant attention, il faut brancher les choses là où il faut! Certaines broches ne sont pas des GPIOs comme je l'ai dis juste au dessus. Certaines délivreront simplement une tension (3.3V ou 5V), d'autres ne délivreront rien (on les appelle Ground, pour fermer le circuit électrique). Chaque GPIO peut être utilisé soit en entrée, soit en sortie, comme son nom l'indique. Le principe est simple, comme dans un ordinateur classique, le Raspberry interprètera la tension qui se trouve dans les câbles quand il s'agit d'une entrée, et la fera changer lorsqu'il s'agit d'une sortie. Tout cela sera géré par votre code! N'est-ce pas fabuleux? ^_^

Comment savoir quelle broche porte quel numéro? Encore une fois, la documentation est là pour nous sauver la vie !

ZxTsV62.png

Nous voila mieux renseignés ! Cependant il reste un problème en suspens... Comment savoir de quel côté commencer à compter? Cette fois c'est le site du constructeur qui nous sauve ! Il faut avoir les bons réflexes quand on ne sait pas quelque chose! ;) Mais vu que je le sais, je vous le dis, ce serait égoïste sinon. :lol:

La broche n°1 (délivrant du 3.3V, le carré orange sur l'image ci-dessus) est la broche la plus proche du port pour carte Micro-SD sur le Raspberry. C'est à dire la broche à l'extrême opposé des ports USB et vers l'intérieur de la carte. Vous voyez de laquelle il s'agit? Très bien, passons à la suite ! :P

 

Le montage de la LED sera effectué sur la breadboard. J'imagine que vous ne savez pas de quoi il s'agit, et encore moins comment ça fonctionne ! C'est normal, pas de panique, c'est vraiment très simple. ;)

 

Voici une illustration qui devrait beaucoup vous aider à comprendre le principe. (Source: sparkfun.com)

a3ahdje.jpg

A gauche, la breadboard telle que vous la voyez, et à droite, on lui a retiré son cache en plastique. Vous pouvez donc facilement voir les zones conductrices et celles qui sont reliées entre elles ou non. Les flèches rouges pointes les rails d'alimentation. Vous brancherez les câbles directement reliés à l'alimentation (les broches 1, 2, 4 et 17) sur le rail + et le câble fermant le circuit sur le rail -, pour ensuite relier ce rail vers une broche Ground sur le Raspberry. Au centre, on a donc toutes les lignes, pas besoin de vous expliquer comment circule le courant électrique! Cependant je tiens quand même à préciser que les branchements devront donc s'effectuer d'une certain manière afin de ne pas générer de court-circuit! Cette image l'illustre très bien: (Source: pbworks.com)

NqU2Ldo.gif

Dernière chose: ces planches sont extrêmement pratiques car elles nous permettent de faire des montages sans avoir de faire quelque soudure que ce soit! De plus, on peut facilement revenir en arrière si on s'est trompé, ou si on veut rajouter quelque chose. :D

 

Voila vous savez désormais vous servir d'une breadboard ! ;)

Intéressons-nous maintenant à notre montage! Voici à quoi il devrait ressembler :

2k1O67e.jpg

cWA1exT.jpg

BqVSDY5.jpg

 

Nous connectons le capteur (à droite) directement sur les broches sans passer par la breadboard; cela ne servirait à rien à part utiliser des câbles supplémentaires. De plus, vu la taille du montage, nous ne sommes pas restreints en broches. ^_^ J'ai soigneusement choisi la couleur des câbles afin de bien s'y retrouver.

En noir, il s'agit des câbles reliés aux broches Ground; en rouge, il s'agit des câbles reliés à l'alimentation; le câble jaune est le câble à connecter sur un GPIO afin que le capteur puisse communiquer avec le Raspberry; et le câble bleu est la fermeture du circuit sur le rail - de la breadboard.

 

Histoire de rende ça plus lisible pour vous, voici une version schématisée du montage : ;)

 

BsD9J8h.png

 

La broche 2 fournira une tension de 5V au capteur PIR, on refermera ce premier petit circuit sur la broche 6 Ground. Le câble jaune pour communiquer avec le Raspberry sera branché sur le GPIO 22, qui se trouve être la broche 31! Attention à cela! Pour bien effectuer le branchement de votre capteur, voici une photo qui devrait vous aider. Il serait dommage de griller votre capteur tout neuf... -_-

 

vJG06xC.jpg

 

Occupons nous désormais du second circuit: celui de la LED. Branchons un câble femelle/mâle sur le GPIO 26, sur la broche 32, juste en face du câble jaune. De l'autre côté, on le branchera sur la breadboard dans la colonne 'a'. Sur la même ligne, branchez la résistance. Cette dernière protège la LED, qui sera régulièrement allumée puis éteinte. Cela risquerait de la griller assez rapidement si l'on ne mettait pas de résistance. Le deuxième pôle de la résistance ira se brancher dans la même colonne mais quatre lignes plus loin. Place à LED désormais. La patte la plus longue (pôle +) ira se brancher sur la même ligne que le pôle - de la résistance. Quand à l'autre, on le branchera sur la ligne juste à côté comme montré sur le schéma. Finissons avec un câble mâle/mâle qui reliera cette dernière ligne au rail -. Plus qu'à ajouter le câble qui refermera le circuit sur le Raspberry sur la broche 6 Ground. Le montage est prêt ! :D

 

Le code :

 

Vous attendiez surement ce moment. Cela vous paraitra d'une simplicité déconcertante vous verrez. ;)

Pour commencer, il faut initialiser et paramétrer tout ça. Créons trois variables d'instance que nous initialiserons dans une méthode :

 
private GpioController gpio;
private GpioPinDigitalInput sensor;
private GpioPinDigitalOutput led;

private void initGPIOs() {
	System.out.println("Initializing GPIOs...");
	gpio = GpioFactory.getInstance();
	led = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_26); //La led est branchée en sortie sur le GPIO 26 (câble rouge de gauche)		

	sensor = gpio.provisionDigitalInputPin(RaspiPin.GPIO_22 , PinPullResistance.PULL_DOWN); //Le capteur est branché en entrée sur le GPIO 22 (câble jaune)
}

 

Voila qui est fait ! ^_^ Mais il manque quelque chose. Comment récupérer les informations venant du capteur? :lol:

Figurez-vous que c'est bien fait! On a tout à notre disposition dans cette formidable API ! :P Nous allons utiliser un event.

sensor.addListener(new GpioPinListenerDigital() { //On utilise un event plutôt qu'une boucle while, c'est bien plus optimisé
 
	@Override
	public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent e) {
		//...
	}
});

 

Nous sommes presque prêts. Créons une petite méthode supplémentaire afin de gérer cela. Il faut savoir que le capteur peut également envoyer un signal pour dire qu'il ne voit pas de mouvement ! Ce qui nous permettra d'éteindre la LED. En réalité, le capteur scanne périodiquement son champ de vision et augmente la tension dans le câble lorsqu'il voit quelque chose. L'event sera alors déclenché assez régulièrement.

private void handleSensorInput(final GpioPinDigitalOutput led, final GpioPinDigitalStateChangeEvent event) {
	if(event.getState().isHigh()) { //Si la tension dans le câble jaune est élevée -> Mouvement détecté par le capteur
		System.out.println("Motion detected");
		led.high(); //On allume la LED en augmentant la tension dans le câble
	} else { //Si la tension dans le câble jaune est faible -> Pas de mouvement
		led.low(); //On éteint la LED en retirant la tension dans le câble
	}
}

 

Je pense qu'il n'y a pas besoin de vous expliquer d'avantage ce code d'un simplicité enfantine. ;)

 

Dernière chose, il faut penser à tout bien faire correctement. Alors pensez à appeler cette méthode lorsque votre logiciel se termine afin de libérer les GPIOs. ^_^

public void shutdown() {
	led.low(); //Au cas où le logiciel soit terminé alors qu'un mouvement est détecté, la LED resterait allumée à l'infini sinon
	gpio.shutdown();
}

 

Le code complet nous donnera quelque chose qui ressemble à cela :

 
private GpioController gpio;
private GpioPinDigitalInput sensor;
private GpioPinDigitalOutput led;
	
private void initGPIOs() {
	System.out.println("Initializing GPIOs...");
	gpio = GpioFactory.getInstance();
	led = gpio.provisionDigitalOutputPin(RaspiPin.GPIO_26); //La led est branchée en sortie sur le pin 26 (câble rouge de gauche sur la vidéo)		
 
	sensor = gpio.provisionDigitalInputPin(RaspiPin.GPIO_22 , PinPullResistance.PULL_DOWN); //Le capteur est branché en entrée sur le pin 22 (câble jaune sur la vidéo)
	sensor.addListener(new GpioPinListenerDigital() { //On utilise un event plutôt qu'une boucle while, c'est bien plus optimisé

		@Override
		public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent e) {
			handleSensorInput(led, e);
		}

	});
}
 
private void handleSensorInput(final GpioPinDigitalOutput led, final GpioPinDigitalStateChangeEvent event) {
	if(event.getState().isHigh()) { //Si la tension dans le câble jaune est élevée -> Mouvement détecté par le capteur
		System.out.println("Motion detected");
		led.high(); //On allume la LED en augmentant la tension dans le câble
	} else { //Si la tension dans le câble jaune est faible -> Pas de mouvement
		led.low(); //On éteint la LED en retirant la tension dans le câble
	}
}
	
public void shutdown() {
	led.low(); //Au cas où le logiciel soit terminé alors qu'un mouvement est détecté, la LED resterait allumée à l'infini sinon
	gpio.shutdown();
}

 

Exportez votre projet dans un Runnable Jar file et stockez-le dans votre Raspberry.

Ouvrez un terminal depuis ce dernier et tapez la commande suivante, en adaptant le nom du Jar ainsi que son chemin :

java -jar /home/pi/Desktop/BukkitPi.jar

 

Cela lancera votre programme! Vous pourrez commencer à tester !

Hum le plaisir aura été de courte durée... <_< Le capteur ne semble pas très bien fonctionner... -_-

Il n'est pas calibré. Vous avez vraiment de la chance de m'avoir ! :lol: Moi qui ai passé bien une heure à la calibrer...

 

Bref, vous avez sans doute remarqué deux petites bornes oranges sur votre capteur. A l'aide d'un petit tournevis, vous pouvez les tourner afin de calibrer le capteur par rapport à vos besoins. L'un modifie la sensibilité, l'autre le délai entre chaque balayage. Terminez votre programme avant de modifier cela, il semblerait que cela fonctionne mieux, pour une raison que je ne saurais expliquer. :lol: Voici mes réglages (faites bien attentions aux repères, il serait dommage d'inverser les deux bornes ^_^ ) :

 

OZpzeoA.jpg

 

Et voila ! Vous pouvez relancer votre programme ! Vous avez réalisé votre premier capteur de mouvement intelligent avec votre Raspberry Pi ! B)

 

JeremGamer

 

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0123823001437053856.png

Jusque là, vos GUI restent tout de même assez fades, nous allons y remédier !

Et en plus de cela, nous verrons aussi de nouveaux objets ouvrant la porte à de nouvelles possibilités, il s'agira essentiellement de conteneurs. ;)

 

Pour commencer, nous allons créer des bordures de toutes sortes autour de nos panneaux !

Voici l'objet que nous allons utiliser: BorderFactory. Ce n'est pas bien compliqué alors je ne vais pas trop m'attarder sur les explications, jetez simplement un œil à l'exemple ci-dessous :

 

 

 
	private String[] list = {
			"Bevel Border",
			"Etched Border",
			"Line Border",
			"Matted Border",
			"Raised Bevel Border",
			"Title Border",
			"Compound Border"
	};

	//C'est à cette partie qu'il faut s'intéresser
	private Border[] listBorder = { //Pour limiter le nombre de lignes de code
			BorderFactory.createRaisedBevelBorder(),
			BorderFactory.createTitledBorder("Titre"),
			BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.black, Color.red),
			BorderFactory.createEtchedBorder(Color.BLUE, Color.GRAY),
			BorderFactory.createLineBorder(Color.RED),
			BorderFactory.createMatteBorder(5, 2, 5, 2, Color.MAGENTA),
			BorderFactory.createCompoundBorder( 
					BorderFactory.createBevelBorder(BevelBorder.LOWERED, Color.black, Color.blue), 
					BorderFactory.createMatteBorder(5, 2, 5, 2, Color.MAGENTA))
	};

	public ExamplePanel() {
		for(int i = 0 ; i < list.length ; i++) {
			JPanel pan = new JPanel();
			pan.setBorder(listBorder[i]);
			JLabel label = new JLabel(list[i]);
			label.setPreferredSize(new Dimension(150, 50));
			label.setAlignmentX(JLabel.CENTER);
			label.setHorizontalAlignment(JLabel.CENTER);
			pan.add(label);
			this.add(pan);
		}
	}

 

vp5kyMt.png

 

Ici j'ai décidé d'appliquer les bordures sur les panneaux, mais il est possible de les appliquer à n'importe quel composant héritant de JComponent, à l'aide de la même méthode setBorder(). Sachez également que vous pouvez choisir de donner null en argument afin de retirer les bordures. ;)

 

Vous vous souvenez du texte qui ne s'affichait pas entièrement des les JTextArea lorsque ce dernier était trop long?

Sur les applications que vous utilisez, vous auriez vu apparaitre des barres d'ascenseurs sur le côté et en bas, appelées scroll bars. A notre tour de les mettre en place ! Nous utiliserons le conteneur JScrollPane et étudierons rapidement quelques unes de ses méthodes.

 

Il s'agit d'un conteneur, alors nous allons procéder de la même manière qu'avec les JPanel. Cependant, je vous conseille plutôt d'ajouter un JPanel au JScrollPane. ;)

Pourquoi cela? Tout simplement car notre conteneur est dynamique et peut faire apparaitre ou disparaitre les scroll bars en fonction de la taille de ce qu'il contient. Si son contenu ne dépasse pas sa propre taille, alors les barres n'apparaitront pas! (Par défaut, il sera possible de les laisser affichées en permanence si vous le souhaitez. Nous verrons comment faire juste après.)

JTextArea text = new JTextArea();
JScrollPane scroll = new JScrollPane(text);

this.setLayout(new BorderLayout());	
this.add(scroll , BorderLayout.CENTER);

 

uPXbGyF.png

 

Voici un exemple concernant les panneaux. Ce conteneur étant très adapté à l'utilisation du BoxLayout, il sera judicieux de l'utiliser même si la taille par défaut est suffisante pour tout afficher, afin de réaliser des GUI plus modulables pour l'utilisateur. ^_^

JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel , BoxLayout.LINE_AXIS));

for(int i = 0 ; i < 5 ; i++)
	panel.add(new JButton("Bouton n°" + i));

JScrollPane scroll = new JScrollPane(panel);
scroll.setPreferredSize(new Dimension(300 , 50));
		
this.setLayout(new BorderLayout());	
this.add(scroll , BorderLayout.CENTER);

 

MXN9rx1.png

 

Comme je le disais plus haut, il est possible de changer la "politique d'affichage" des scroll bars. C'est tout simple :

scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS); //La barre verticale est toujours affichée
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED); //Par défaut, affiche la barre si besoin 
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);  //N'affiche jamais la barre verticale
		
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS); //La barre horizontale est toujours affichée
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); //Par défaut, affiche la barre si besoin
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); //N'affiche jamais la barre horizontale

 

Les JTabbedPane vous permettrons, à l'image du CardLayout, d'empiler plusieurs panneaux. Cependant, l'utilisateur pourra facilement y accéder grâce aux onglets qui seront disposés en haut de l'écran! :)

C'est vraiment tout simple à utiliser :

JTabbedPane tabs = new JTabbedPane();
		
JPanel red = new JPanel();
red.setBackground(Color.RED);
		
JPanel green = new JPanel();
green.setBackground(Color.GREEN);
		
JPanel blue = new JPanel();
blue.setBackground(Color.BLUE);	
		
tabs.addTab("Rouge", red);
tabs.addTab("Vert", green);
tabs.addTab("Bleu", blue);

 

7MVrZo9.png

 

Grâce à addTab(), vous pouvez ajouter des onglets; de la même manière, vous pouvez en retirer à l'aide de removeTab(int index)

Mais ça ne s'arrête pas là! Il y a pleins d'autres possibilités comme changer l'orientation de la barre d'onglets :

//A l'initialisation
JTabbedPane tabs = new JTabbedPane(JTabbedPane.LEFT); //Affichera les onglets à gauche
//ou avec la méthode
tabs.setTabPlacement(JTabbedPane.RIGHT); //Affichera les onglets à droite
tabs.setTabPlacement(JTabbedPane.BOTTOM); //Affichera les onglets en bas

 

92pS3ta.png

 

Avez-vous essayé de réduire la fenêtre pour voir ce qu'il se passait si les onglets occupent plus d'espace qu'ils en ont à leur disposition? Les onglets se redisposerons sur plusieurs lignes ou colonnes selon l'orientation. Il est possible de changer ce comportement pour le remplacer par l'ajout d'une petite flèche faisant défiler les onglets. ;)

tabs.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT); //Par défaut, redisposera les onglets sur plusieurs lignes si besoin
tabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); //Ajoutera un scroll si besoin

 

jwFrc5I.png

 

Le screenshot ci-dessus utilise SCROLL_TAB_LAYOUT.

 

Et pour finir en beauté avec ces onglets, voyons comment insérer des icônes !

JTabbedPane tabs = new JTabbedPane(JTabbedPane.LEFT);
		
JPanel red = new JPanel();
red.setBackground(Color.RED);
		
JPanel green = new JPanel();
green.setBackground(Color.GREEN);
		
JPanel blue = new JPanel();
blue.setBackground(Color.BLUE);	
		
tabs.addTab("Accueil", red);
tabs.setIconAt(0, new ImageIcon("home.png"));
		
tabs.addTab("Rechercher", green);
tabs.setIconAt(1, new ImageIcon("search.png"));
		
tabs.addTab("Informations", blue);
tabs.setIconAt(2, new ImageIcon("infos.png"));

 

WgcKk0I.png

 

Vous avez sans doute déjà rencontré des applications avec des fenêtres internes, comme bloquées dans la fenêtre principale. En Java, on combinera des JInternalWindow avec un JDesktopPane. Ce dernier est un conteneur du type bureau comme son nom l'indique. Le principe est simple une fois de plus : un bureau et des fenêtres ! Voici un petit exemple :

public ExamplePanel() {
		
	this.setPreferredSize(new Dimension(300 , 300));
		
	JDesktopPane desktop = new JDesktopPane();
		
	for(int i = 1 ; i < 6 ; i++)
		desktop.add(new CustomInternalFrame(i));
		
	this.setLayout(new BorderLayout());	
	this.add(desktop , BorderLayout.CENTER);
}
	
class CustomInternalFrame extends JInternalFrame {

	CustomInternalFrame(int number) {
	     this.setTitle("Fenêtre n°"+ number);
	      this.setClosable(true);
	      this.setResizable(true);
	      this.setSize(150, 80);
	      this.setVisible(true);
	}
}

 

qUMHYGF.png

 

Dans l'image, j'ai repositionné et redimensionné les cinq fenêtres, car à l'initialisation, elles apparaissent toutes au même endroit et à la même taille, vu que je n'ai pas pris le temps de m'occuper de ça dans le code. Il y a de nombreuses possibilités que je ne vais pas vous expliquer, je vous invite donc à jeter un œil à la documentation, vous êtes devriez commencer à avoir ce réflexe. ;) Pour ce qui est d'habiller tout ça, vous savez déjà le faire car JDesktopPane hérite de JLayeredPane.

 

On enchaîne ! Voici désormais les JEditorPane ! Ces conteneurs permettent d'afficher du html, mais de façon un peu limitée tout de même. C'est largement suffisant pour du texte riche et des images comme dans cet exemple :

 

SBvXMOt.png

 

Oui vous saurez faire cela plus tard ! Sympa n'est-ce pas? ^_^ Le panneau à droite contenant le texte est un JEditorPane contenant une mise en page html. Voici un exemple plus simple :

JEditorPane html = new JEditorPane();
JScrollPane scroll = new JScrollPane(html);
		
html.setEditable(false);
html.setEditorKit(new HTMLEditorKit());
html.setText("<html>Du <b>texte</b> <i>riche</i> en <u>HTML</u>.</html>");		
		
this.setLayout(new BorderLayout());	
this.add(scroll, BorderLayout.CENTER);

 

PNNhKLw.png

 

Ce conteneur peut également se comporter comme un JTextArea si l'on n'utilisait pas la méthode setEditable(false), de même que le JTextPane que je vous invite à essayer rapidement. ;)

 

Pour terminer ce chapitre, nous allons nous intéresser aux JSplitPane. Ils permettent de scinder votre conteneur en plusieurs "compartiments". Un exemple vaut mieux que de nombreux mots, je sais que vous reconnaitrez tout de suite de quoi il s'agit.

 

9m8gTOx.png

 

La barre de séparation peut être déplacée afin d'agrandir l'un des deux panneaux, et bien évidemment, de réduire l'autre.

Voici le code correspondant :

JPanel red = new JPanel();
red.setBackground(Color.RED);
	      
JPanel green = new JPanel();
green.setBackground(Color.GREEN);

JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT , red , green);

 

Rien de bien compliqué n'est-ce pas? ^_^

Comme d'habitude, nous n'allons pas nous arrêter là!

 

Tout d'abord, ici nous avons séparé horizontalement, à l'aide de JSplitPane.HORIZONTAL_SPLIT; pour séparer verticalement, on utilisera JSplitPane.VERTICAL_SPLIT.

Ici on a utilisé des JPanel, mais il peut s'agir de n'importe quel composant ! ;)

 

Nous allons améliorer notre barre de séparation et y ajouter deux petits boutons permettant de déplacer cette dernière à une extrémité de l'écran. Je conseille fortement d'essayer par vous-même car cela n'est pas illustrable par captures d'écran. C'est vraiment très simple :

split.setOneTouchExpandable(true);

 

G1qMdPp.png

 

Maintenant nous allons définir la taille de la barre. Encore une fois, rien de plus simple :

split.setDividerSize(20); //20 pixels

 

NydV9L4.png

 

Et pour en finir avec cette barre, voici comment choisir sa position. J'imagine que vous avez remarqué que cette dernière apparait quasiment entièrement à gauche lors de l’initialisation. Nous allons la faire apparaitre de façon à ce que le panneau de gauche soit deux fois plus grand que celui de droite.

split.setDividerLocation(0.75); //De manière proportionelle, ici le panneau de gauche occupera 75% de l'espace
//Ou en pixels si vous donnez un int

 

Attention, cette méthode ne fonctionnera pas si vous l'appelez avant que la fenêtre soit affichée, c'est à dire qu'il faut que JFrame.setVisible(true) soit appelé avant l'appel de la méthode.

 

Vous vous êtes peut-être demandé : et si je voulais combiner les deux séparations en mettant plusieurs panneaux? :lol:

C'est possible bien-sûr ! Il suffit de combiner plusieurs JSplitPane !

JPanel red = new JPanel();
red.setBackground(Color.RED);
	      
JPanel green = new JPanel();
green.setBackground(Color.GREEN);
	    
JPanel blue = new JPanel();
blue.setBackground(Color.BLUE);
	    
JSplitPane split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT , red , green);
	
JSplitPane split2 = new JSplitPane(JSplitPane.VERTICAL_SPLIT , split , blue);
		
contentPanel.setLayout(new BorderLayout()); 	
contentPanel.add(split2, BorderLayout.CENTER);

 

45NoNYp.png

 

Nous allons nous arrêter là pour ce chapitre. Dans le prochain chapitre, nous nous pencherons sur les menus ! ;)

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

Maintenant que vous avez un certain nombre de compétences en matière d'interfaces graphiques, vous serez en mesure de créer des logiciels bien plus "user-friendly" que de vulgaires consoles. Vous savez déjà plus ou moins ce qui vous attend dans ce TP, alors sans plus tarder, voici votre cahier des charges ! Lisez-le attentivement et surtout, dans son intégralité !

 

Nous voulons réaliser une petite interface graphique permettant de personnaliser un personnage de jeu de rôle. De nombreux champs de formulaires devront être utilisés afin de pouvoir paramétrer les caractéristiques suivantes :

  • Nom du personnage
  • Sexe du personnage
  • Tranche d'âge du personnage
  • Taille du personnage
  • Couleur de peau
  • Couleur de cheveux
  • Couleur des yeux


Vous pouvez ajouter des caractéristiques si vous le souhaitez.
En option, vous pouvez rajouter une image sur le côté, pour représenter le personnage, et mieux encore, vous pouvez la faire réagir avec les caractéristiques telles que le sexe ou la couleur de peau.
Ensuite nous souhaitons également que l'interface soit la plus claire possible, tout en étant bien structurée et organisée. Pour cela il vous faudra combiner de nombreux panneaux ayant des layout différents.

 

Histoire de ne pas perdre son travail, nous souhaitons également que toutes les caractéristiques soient sauvegardées et rechargées au lancement du programme. Le fichier de sauvegarde sera localisé dans le même dossier que le logiciel, histoire de s'y retrouver pendant les tests. On pourra éventuellement changer ça plus tard. Je dois vous obliger à une chose cependant: je souhaite que vous sauvegardiez toutes ces données à l'aide d'un ObjectOutputStream ! Les données devront être rechargées dans tous les champs de formulaire au lancement du logiciel si un fichier existe.

 

Lors de la fermeture de la fenêtre, les données doivent être sauvegardées. Voici une petite astuce pour effectuer quelques instructions lors de différents événements liés à une fenêtre :

this.addWindowListener(new WindowAdapter() {

	@Override
	public void windowClosing(WindowEvent e) {
		//Effectué juste avant que la fenêtre se ferme et que le processus se termine	
	}

	@Override
	public void windowDeiconified(WindowEvent e) {
		//Lorsque la fenêtre est restaurée après réduction
	}

	@Override
	public void windowIconified(WindowEvent e) {
		//Lorsque la fenêtre est réduite
	}
			
});


Comme d'habitude la documentation peut vous servir pour savoir quelles autres méthode proposent les WindowListener. ;)

 


Vous savez tout ! Maintenant fini de rêvasser, au travail ! J'apprécierais voir vos travaux une fois terminés alors n'hésitez pas à me les envoyer par message privé ;) Si vous avez besoin d'aide, je suis là également pour vous aiguiller !
N'oubliez pas ce que je vous ai dis pendant les deux autres TPs : prenez votre temps, réfléchissez bien avant de vous jeter la tête la première dans le code, etc... Cela s'applique pour tous vos projets ! Il y a cependant une petite différence : vous travaillez maintenant sur GUI, alors pensez bien à la forme qu'elle prendra. N'hésitez pas à vous faire un petit dessin si ça peut vous aider à bien visualiser. De plus, choisissez judicieusement les composants que vous allez utiliser; ça n'aurait aucun sens d'utiliser un champ de saisie pour choisir le sexe du personnage. ^_^

 

Je vous souhaite bonne chance !

 

Correction :

Spoiler

Cette correction ne comprend pas les petits extras que je vous ai proposé comme l'image sur le côté, je sais que vous savez le faire par vous-même! ;) Prenez bien le temps de la lire, et faites-le attentivement! Je quasiment certain que vous n'avez pas procédé ainsi. ^_^ Mais si c'est le cas alors je vous félicite allègrement! Je vous félicite quand même si vous avez réussi l'exercice par votre propres moyens sans regarder la correction!

 

Class de la fenêtre :


package fr.bukkit.jeremgamer.cours;

import java.awt.Dimension;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.JFrame;

public class MainFrame extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5897453038271365972L;

	public MainFrame() {
		this.setSize(300 , 250);		
		this.setTitle("Personnage");		
		this.setLocationRelativeTo(null);		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

		ContentPanel content = new ContentPanel();
		this.setContentPane(content);		
		this.pack();
		
		this.addWindowListener(new WindowAdapter() {

			@Override
			public void windowClosing(WindowEvent e) {
				content.save();
			}

		});
		
		this.setVisible(true);
		
		this.setMinimumSize(getSize());
		this.setMaximumSize(new Dimension(500 , 500)); //Ne fonctionne pas à cause d'un bug connu de l'API Java
	}

}


Class du panneau principal : (Accrochez-vous bien ^_^ )


package fr.bukkit.jeremgamer.cours;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;

import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JSlider;
import javax.swing.JSpinner;
import javax.swing.JTextField;
import javax.swing.SpinnerNumberModel;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class ContentPanel extends JPanel {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7311158212818984619L;

	private RPCharacter rpc; 

	private ObjectInputStream ois;
	private BufferedInputStream bis;
	private FileInputStream fis;

	private ObjectOutputStream oos;
	private BufferedOutputStream bos;
	private FileOutputStream fos;

	private JTextField nameField = new JTextField();
	private JSpinner ageSpinner = new JSpinner(new SpinnerNumberModel(25 , 10 , 80 , 1));
	private	JComboBox<String> sexCombo = new JComboBox<String>();
	private JSlider sizeSlider = new JSlider();

	private int selectedEye;
	private int selectedSkin;
	private int selectedHair;
	
	public ContentPanel() {	

		try {
			initCharacter();
		} catch (IOException | ClassNotFoundException e) {
			System.err.println("Erreur lors de la lecture du fichier de sauvegarde.");
			System.out.println("Une sauvegarde vierge sera utilisée.");
			rpc = new RPCharacter();
		}

		BorderLayout bl = new BorderLayout();
		bl.setHgap(5);
		this.setLayout(bl);


		//------------------------------
		//Création des composants pour les caractérisitques
		//------------------------------


		//----------------

		JLabel nameLabel = new JLabel("Nom : ");

		//----------------		

		JLabel sexLabel = new JLabel("Sexe : ");
		sexCombo.addItem("Homme");
		sexCombo.addItem("Femme");
		sexCombo.addItem("Autre");

		//----------------

		JLabel ageLabel = new JLabel("Âge : ");

		//----------------

		JLabel sizeLabel = new JLabel("Taille : ");				
		JLabel sizeDisplay = new JLabel("175" , JLabel.RIGHT);

		sizeSlider.setMinimum(100);
		sizeSlider.setMaximum(250);
		sizeSlider.setValue(175);
		sizeSlider.setPaintTicks(true);
		sizeSlider.setPaintLabels(true);
		sizeSlider.setMinorTickSpacing(25);
		sizeSlider.setMajorTickSpacing(50);
		sizeSlider.addChangeListener(new ChangeListener() {

			@Override
			public void stateChanged(ChangeEvent e) {
				sizeDisplay.setText(String.valueOf(sizeSlider.getValue()));
			}

		});

		//----------------

		JPanel eyeColor = new JPanel();
		eyeColor.setLayout(new BoxLayout(eyeColor , BoxLayout.PAGE_AXIS));

		JLabel eyeColorLabel = new JLabel("Yeux");
		JRadioButton brown = new JRadioButton("Bruns");
		JRadioButton blue = new JRadioButton("Bleus");
		JRadioButton green = new JRadioButton("Verts");
		JRadioButton yellow = new JRadioButton("Jaunes");
		JRadioButton grey = new JRadioButton("Gris");
		JRadioButton hazel = new JRadioButton("Noisette");
		JRadioButton amber = new JRadioButton("Ambre");

		ArrayList<JRadioButton> eyeList = new ArrayList<JRadioButton>();
		eyeList.add(brown);
		eyeList.add(blue);
		eyeList.add(green);
		eyeList.add(yellow);
		eyeList.add(grey);
		eyeList.add(hazel);
		eyeList.add(amber);

		ButtonGroup grp = new ButtonGroup();
		int i = 0;
		for(JRadioButton b : eyeList) {
			grp.add(b);
			b.addActionListener(new TraitListener(i , TraitListener.EYE_GROUP));
			i++;
		}


		eyeColor.add(eyeColorLabel);
		for(JRadioButton b : eyeList)
			eyeColor.add(b);

		//----------------

		JPanel hairColor = new JPanel();
		hairColor.setLayout(new BoxLayout(hairColor , BoxLayout.PAGE_AXIS));

		JLabel hairColorLabel = new JLabel("Cheveux");
		JRadioButton brown2 = new JRadioButton("Bruns");
		JRadioButton blond = new JRadioButton("Blonds");
		JRadioButton red = new JRadioButton("Roux");

		ArrayList<JRadioButton> hairList = new ArrayList<JRadioButton>();
		hairList.add(brown2);
		hairList.add(blond);
		hairList.add(red);

		ButtonGroup grp2 = new ButtonGroup();
		i = 0;
		for(JRadioButton b : hairList) {
			grp2.add(b);
			b.addActionListener(new TraitListener(i , TraitListener.HAIR_GROUP));
			i++;
		}

		hairColor.add(hairColorLabel);
		for(JRadioButton b : hairList)
			hairColor.add(b);

		//----------------

		JPanel skinColor = new JPanel();
		skinColor.setLayout(new BoxLayout(skinColor , BoxLayout.PAGE_AXIS));

		JLabel skinColorLabel = new JLabel("Peau");
		JRadioButton light = new JRadioButton("Blanche");
		JRadioButton dark = new JRadioButton("Noire");
		JRadioButton yellow2 = new JRadioButton("Jaune");

		ArrayList<JRadioButton> skinList = new ArrayList<JRadioButton>();
		skinList.add(light);
		skinList.add(dark);
		skinList.add(yellow2);

		ButtonGroup grp3 = new ButtonGroup();
		i = 0;
		for(JRadioButton b : skinList) {
			grp3.add(b);
			b.addActionListener(new TraitListener(i , TraitListener.SKIN_GROUP));
			i++;
		}

		skinColor.add(skinColorLabel);
		skinColor.add(Box.createRigidArea(new Dimension(1, 3))); //Pour ajouster un tout petit écart
		for(JRadioButton b : skinList)
			skinColor.add(b);

		//------------------------------





		//------------------------------
		//Assemblage des composants de caractéristiques
		//------------------------------

		JPanel traitsContainer = new JPanel(new GridBagLayout());

		GridBagConstraints gbc = new GridBagConstraints(); 
		gbc.insets = new Insets(3 , 3 , 3 , 3);

		gbc.weightx = 1;
		gbc.weighty = 1;
		gbc.fill = GridBagConstraints.BOTH;

		gbc.gridx = 0;
		gbc.gridy = 0;

		gbc.gridwidth = 1;
		gbc.gridheight = 1;

		traitsContainer.add(nameLabel , gbc);

		gbc.gridwidth = GridBagConstraints.HORIZONTAL;
		gbc.gridx = 1;
		traitsContainer.add(nameField, gbc);

		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.gridx = 0;
		gbc.gridy = 1;
		traitsContainer.add(sexLabel , gbc);

		gbc.gridx = 1;
		traitsContainer.add(sexCombo, gbc);

		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.gridx = 0;
		gbc.gridy = 2;
		traitsContainer.add(ageLabel , gbc);

		gbc.gridx = 1;
		traitsContainer.add(ageSpinner , gbc);

		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.gridx = 0;
		gbc.gridy = 3;
		traitsContainer.add(sizeLabel , gbc);

		gbc.gridx = 1;
		traitsContainer.add(sizeDisplay , gbc);

		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.gridx = 0;
		gbc.gridy = 4;
		traitsContainer.add(sizeSlider , gbc);

		gbc.gridx = 2;
		gbc.gridy = 5;
		traitsContainer.add(hairColor , gbc);

		gbc.gridy = 6;
		traitsContainer.add(skinColor , gbc);

		gbc.gridheight = GridBagConstraints.VERTICAL;
		gbc.gridwidth = 2;
		gbc.gridx = 0;
		gbc.gridy = 5;
		traitsContainer.add(eyeColor , gbc);

		//------------------------------



		//------------------------------
		//Chargement des paramètres sauvegardés
		//------------------------------
		nameField.setText(rpc.name);
		sexCombo.setSelectedItem(rpc.sex);
		ageSpinner.setValue(rpc.age);
		sizeSlider.setValue(rpc.size);
		sizeDisplay.setText(String.valueOf(sizeSlider.getValue()));
		eyeList.get(rpc.eyes).setSelected(true);
		selectedEye = rpc.eyes;
		hairList.get(rpc.hair).setSelected(true);
		selectedHair = rpc.hair;
		skinList.get(rpc.skin).setSelected(true);
		selectedSkin = rpc.skin;
		//------------------------------


		this.add(traitsContainer , BorderLayout.CENTER);

	}
	
	private void initCharacter() throws IOException, ClassNotFoundException {
		File save = new File("character.save");
		if(save.exists()) {
			fis = new FileInputStream(save);
			bis = new BufferedInputStream(fis);
			ois = new ObjectInputStream(bis);

			rpc = (RPCharacter) ois.readObject();
			ois.close();
		} else
			rpc = new RPCharacter();
	}

	public void save() {
		rpc.name = nameField.getText();
		rpc.sex = (String) sexCombo.getSelectedItem();
		rpc.age = (int) ageSpinner.getValue();
		rpc.size = sizeSlider.getValue();
		rpc.eyes = selectedEye;
		rpc.hair = selectedHair;
		rpc.skin = selectedSkin;
				
		try {
			File file = new File("character.save");
			if(!file.exists())
				file.createNewFile();
			fos = new FileOutputStream(file);
			bos = new BufferedOutputStream(fos);
			oos = new ObjectOutputStream(bos);

			oos.writeObject(rpc);
			oos.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	class TraitListener implements ActionListener {

		private int i;
		private int group;

		public final static byte EYE_GROUP = 0;
		public final static byte HAIR_GROUP = 1;
		public final static byte SKIN_GROUP = 2;

		TraitListener(int i , byte group) {
			this.i = i;
			this.group = group;
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			if(group == EYE_GROUP)
				selectedEye = i;
			else if(group == HAIR_GROUP)
				selectedHair = i;
			else if(group == SKIN_GROUP)
				selectedSkin = i;
		}

	}
}


Class de l'objet de sauvegarde :


package fr.bukkit.jeremgamer.cours;

import java.io.Serializable;

public class RPCharacter implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 592864057290681719L;
	
	public String name;
	public String sex;
	public int age;
	public int size;
	public int eyes;
	public int hair;
	public int skin;
	
	public RPCharacter() {
		name = "";
		sex = "Homme";
		age = 25;
		size = 175;
		eyes = 0;
		hair = 0;
		skin = 0;
	}
	
}


Le résultat donne le rendu suivant :


LZxzrc9.png

 

Percevez-vous maintenant la puissance du GridBagLayout? Et ce n'est pas grand chose, il peut faire bien mieux !
Alors qu'avez-vous pensé de ce TP? N'hésitez pas à m'envoyer vos avis ou à les poster en commentaire ! ;)

 

Vous pouvez télécharger le JAR exécutable ici si vous voulez tester. Le fichier de sauvegarde sera localisé dans le dossier courant. (Le même dossier où se situe le JAR)

 

 

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0507549001433339939.png

Dans ce chapitre, je vous présenterai de nombreux nouveaux composants qui vous permettront d'étoffer vos GUI. ;)

Il n'y aura que très peu d'explications car ce chapitre n'est absolument pas difficile, et le peu de notions que nous utiliserons dans ce dernier sont normalement déjà connues. Je vous invite fortement à essayer tous les bouts de code que je vous fournirai pendant ce chapitre.

 

Sans plus tarder, commençons avec quelque chose de très proche de ce que vous connaissez déjà : il s'agit d'un bouton restant enfoncé quand on clique dessus, j'ai nommé le JToggleButton.

 
JToggleButton button = new JToggleButton("Désactivé");
		
button.addItemListener(new ItemListener() {

	@Override
	public void itemStateChanged(ItemEvent e) {
		JToggleButton b = (JToggleButton) e.getSource();
		if(e.getStateChange() == ItemEvent.SELECTED)
			b.setText("Activé");
		else if(e.getStateChange() == ItemEvent.DESELECTED)
			b.setText("Désactivé");
	}
			
});

 

Rien de bien compliqué ! ^_^ Seule nouveauté, j'utilise ici un ItemListener et non pas un ActionListener comme vous savez déjà le faire. J'aurais très bien pu le faire également de la manière suivante, mais le but est ici de vous montrer les différents moyens d'arriver au même résultat. Choisissez celui que vous préférez. ;)

button.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		JToggleButton b = (JToggleButton) e.getSource();
		if(b.isSelected())
			b.setText("Activé");
		else
			b.setText("Désactivé");
	}

});

 

Le fonctionnement de ce bouton est finalement le même que celui des cases à cocher, que vous avez surement rencontré plus souvent dans l'utilisation courante. En Java, elles s'appellent les JCheckBox.

JCheckBox box = new JCheckBox("Cochez cette case");
		
box.addItemListener(new ItemListener() {

	@Override
	public void itemStateChanged(ItemEvent e) {
		if(e.getStateChange() == ItemEvent.SELECTED)
			System.out.println("Coché");
		else if(e.getStateChange() == ItemEvent.DESELECTED)
			System.out.println("Décoché");
	}
	
});

 

w6WaSOx.png

 

 

 

Dans un tout autre style, le JLabel ne permet pas d’interaction native avec l'utilisateur. Il sera possible d'en ajouter manuellement cependant. Il s'agit de texte, tout simplement, mais contenu dans un composant Swing rigide, ce qui le rend bien plus pratique que d'écrire le texte grâce à l'objet Graphics.

JLabel label = new JLabel("Un JLabel.");

 

ACygFmq.png

 

Voici tout ce qu'il y a de plus simple n'est-ce pas? ^_^ Les JLabel proposent de nombreuses options de personnalisation, ce qui va nous permettre de faire quelque chose d'un peu plus joli et adapté à nos envie. Tout d'abord, l'alignement peut être changé. Ici il est centré, mais on peut le caler à gauche ou à droite, etc... Ceci fonctionnera uniquement avec le BoxLayout.

JLabel label = new JLabel("Un JLabel." , JLabel.LEFT); //A gauche
JLabel label2 = new JLabel("Un JLabel." , JLabel.RIGHT); //A droite
		
//Ou à l'aide de cette méthode
label.setHorizontalAlignment(JLabel.LEFT); 

 

On peut ensuite choisir la couleur et la police du texte :

label.setForeground(Color.RED); //Couleur du texte
label.setFont(new Font("Impact" , Font.BOLD , 42)); //Police de caractère

 

VteuC8i.png

 

Les JLabel sont également utilisés pour afficher des images. Notre image aura donc les mêmes caractéristiques qu'un composant Swing! :) C'est une fois de plus un avantage dans certaines situations par rapport à l'utilisation de Graphics.

JLabel label = new JLabel();
try {
	label.setIcon(new ImageIcon(ImageIO.read(new File("image.png"))));
} catch (IOException e) {
	e.printStackTrace();
}

 

rAC4FK4.png

 

Il est bien-sûr possible d'afficher du texte à côté de l'image. Vous savez déjà comment vous occuper du texte avec les JLabel. ;)

Si vous voulez changer le texte après avoir instancié votre label, vous pouvez utiliser la méthode setText().

 

 

 

Passons à un autre composant : le JComboBox. Vous le connaissez peut-être plutôt sous le nom de menu déroulant.

Nous allons pouvoir ajouter autant d'item que l'on souhaite, et mieux encore : programmer un comportement différent pour chaque item !

JComboBox<String> box = new JComboBox<String>(); //Attention à la généricité !
//On ajoute les items
box.addItem("Item n°0"); 
box.addItem("Item n°1");
box.addItem("Item n°2");
box.addItem("Item n°3");

//Ou instancié à l'aide d'un tableau pour rentrer directement tous les items :
String[] items = { "Item n°0" , "Item n°1" , "Item n°2" , "Item n°3"};
JComboBox<String> box2 = new JComboBox<String>(items);

 

CCXzocb.png

 

Maintenant, ajoutons un Listener pour rendre tout ça un peu plus dynamique ! Pour l'exemple, nous allons ajouter un bouton et un label. Lors du clic sur le bouton, on actualisera le label pour lui faire afficher le texte contenu dans l'item sélectionné.

JComboBox<String> box = new JComboBox<String>(); //Attention à la généricité !
//On ajoute les items
box.addItem("Item n°0"); 
box.addItem("Item n°1");
box.addItem("Item n°2");
box.addItem("Item n°3");
		
JLabel label = new JLabel((String) box.getSelectedItem());
		
JButton button = new JButton("Actualiser");
button.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		label.setText((String)box.getSelectedItem());
	}
			
});

 

C'est bien, mais finalement on n'a pas écouté la JComboBox mais le JButton...

Retirons le bouton et actualisons le label automatiquement lorsque l'item sélectionné change :

JComboBox<String> box = new JComboBox<String>();

box.addItem("Item n°0"); 
box.addItem("Item n°1");
box.addItem("Item n°2");
box.addItem("Item n°3");
		
JLabel label = new JLabel((String) box.getSelectedItem());

box.addItemListener(new ItemListener() {

	@Override
	public void itemStateChanged(ItemEvent e) {
		label.setText((String)e.getItem());	
	}
			
});

 

Avec les JComboBox, il est préférable d'utiliser les ItemListener plutôt que les ActionListener, car je pense que vous avez compris qu'ils sont littéralement faits pour ça. ^_^ Il est possible d'utiliser un ActionListener mais il s'avèrera moins pratique. A vous de choisir une fois de plus. ;)

 

 

 

Dans la même catégorie du choix unique parmi plusieurs options, le JRadioButton est aussi très régulièrement utilisé.

Avant de vous montrer le code, je pense que vous présenter sa représentation pourra vous faciliter la compréhension du code qui suit.

 

qbbSBPr.png

ButtonGroup grp = new ButtonGroup(); //On créé un groupe dans lequel on ajoutera nos JRadioButton
//Si on ne le fait pas, on pourra sélectionner les deux boutons en même temps
//Or on souhaite qu'une seule option puisse être choisie
		
JRadioButton rb1 = new JRadioButton("Option n°1");
JRadioButton rb2 = new JRadioButton("Option n°2");
	
grp.add(rb1);
grp.add(rb2);

panel.add(rb1);
panel.add(rb2);

 

Encore une fois ce n'est pas bien compliqué. Pour ce qui est des Listener, on utilisera des ActionListener en général, et on les appliquera aux boutons directement, et non au groupe.

 

Et maintenant, j'ai l'honneur de vous présenter les JSpinner ! :D

XxAp0KZ.png

Vous connaissez désormais leur nom ! ^_^

La particularité de ceux-ci est que nous devons leur donner un modèle pour qu'ils sachent quoi afficher. Pour l'exemple, nous utiliserons un SpinnerNumberModel, qui permettra donc l'affichage de nombres. Sachez qu'il existe également les AbstractSpinnerModel, SpinnerDateModel et les SpinnerListModel. Je vous invite à jeter un œil à la documentation sur ces modèles afin de savoir tous les maitriser.

 

Pour réaliser le spinner présenté au dessus, le code est le suivant :

SpinnerModel model = new SpinnerNumberModel(0 , 0 , 100 , 1); //Valeur de base, minimum, maximum, palier
JSpinner spin = new JSpinner(model); //On instancie le spinner avec notre model
((DefaultEditor) spin.getEditor()).getTextField().setEditable(false); //Optionnel, détails ci-dessous

 

La dernière ligne permet de désactiver l'édition du champ de texte où est affichée la valeur par l'utilisateur. C'est bien-sûr optionnel.

 

Pour soulever les doutes possibles sur les paliers dans le modèle, voici une petite explication. Les paliers définiront quelle valeur sera ajoutée ou retirée lors du clic sur les flèches à droite du spinner. Si par exemple on a 0 en base et 2 en palier, quand on cliquera sur la flèche en haut (pour ajouter donc), le spinner affichera 2, puis 4, puis 6, etc...

 

 

Les JSlider sont des barres dont l'utilisateur peut déplacer le curseur. Elles sont notamment utilisées pour régler le volume sur les lecteurs multimédias.

 

BriFSd8.png

 

Voici le code correspondant :

JLabel label = new JLabel("Valeur : " + 50);
		
JSlider slider = new JSlider();
slider.setMinimum(0); //Valeur minimum
slider.setMaximum(100); //Valeur maximum
slider.setValue(50); //Valeur actuelle
slider.setPaintTicks(true); //Afficher des graduations
slider.setPaintLabels(true); //Afficher les valeurs sous les graduations supérieures
		
//En unité du slider, c'est à dire par rapport aux valeurs minimum et maximum.		
slider.setMinorTickSpacing(10); //Espace entre les graduations inférieures
slider.setMajorTickSpacing(20); //Espace entre les graduations supérieures
		
slider.addChangeListener(new ChangeListener() {

	@Override
	public void stateChanged(ChangeEvent e) {
		label.setText("Valeur : " + slider.getValue());
	}
			
});
		
panel.add(slider);
panel.add(label);

 

Il existe de nombreuses méthodes chez le JSlider, je vous suggère donc une fois de plus d'aller voir la documentation. ;)

 

 

 

Pour finir ce chapitre, je vais vous présenter une jolie famille qui vous sera surement bien utile : les champs de saisie ! Nous en verrons quatre.

 

Le plus simple pour commencer: JTextField. Comme d'habitude c'est toujours très simple de les utiliser, cependant il faudra leur donner une taille préférée sinon ils seront tout petits dans les layouts n'imposant pas de contrainte de taille comme le FlowLayout.

JTextField field = new JTextField();
field.setPreferredSize(new Dimension(110 , 25));
		
JTextField field2 = new JTextField("Texte par défaut"); //Le texte sera présent directement dans le champ
//Le champ de saisie prendra la taille adaptée par rapport au texte par défaut
		
panel.add(field);
panel.add(field2);

 

JNWll7p.png

 

Voila l'utilisation la plus simple que l'on peut en faire, mais il y a mieux ! Comme pour les JLabel, il est possible de changer la police et la couleur ! ;)

JTextField field = new JTextField();
field.setPreferredSize(new Dimension(110 , 25));
		
field.setForeground(Color.RED); //Couleur du texte
field.setFont(new Font("Impact" , Font.PLAIN , 16)); //Police

 

2G8ondN.png

 

Grâce aux méthodes getText() et setText(), vous pouvez respectivement récupérer le contenu du champ de saisie ou alors le modifier. Il est possible de personnaliser le caret également (le trait clignotant). Une fois de plus, la documentation sera votre meilleur ami ! ;)

 

Il y a une autre spécificité aux champs de saisie: on écoutera ce qui s'y passe avec un CaretListener et non un ChangeListener.

Voici un exemple :

JLabel label = new JLabel();
		
JTextField field = new JTextField();
field.setPreferredSize(new Dimension(110 , 25));
		
field.addCaretListener(new CaretListener() {

	@Override
	public void caretUpdate(CaretEvent e) {
		label.setText(field.getText());
	}
	
});

		
panel.add(field);
panel.add(label);

 

xmUgs5t.png

 

Bien, vous savez à peu près tout sur les JTextField. Mais ses frères et sœur ont d'autres options à offrir ! Ce que je viens de vous apprendre reste bien évidemment valable pour eux. ;)

 

Si vous avez essayé d'écrire un texte un petit peu plus long, vous avez remarqué qu'il n'y a pas de retour à la ligne, et que la fin n'est pas visible. Si l'on veut pouvoir faire des retours à la ligne, alors on devra utiliser des zones de texte bien plus grandes. On utilisera sa sœur, la JTextArea. Pour ce qui est du texte non visible, nous verrons cela dans le prochain chapitre. ;)

JTextArea field = new JTextArea();
field.setPreferredSize(new Dimension(200 , 100));

2Pg7zIo.png

 

Vous remarquerez qu'il n'y a plus de bordures, ce qui peut être un peu moche je vous l'accorde. Pas de souci, nous y remédierons dans un autre chapitre également.

Vous pouvez définir un retour à la ligne automatique :

field.setLineWrap(true); //Retour à la ligne activé

field.setWrapStyleWord(true); //Empêche la coupure des mots

 

Voyons désormais une autre variante: le JPasswordField. Il est identique au JTextField à la différence que seul le programme sera capable de lire se qu'il contient. Peu importe ce que vous écrirez, le même caractère s'affichera à chaque fois ('*' par défaut) :

 

deRvFyN.png

 

JPasswordField pass = new JPasswordField();
pass.setPreferredSize(new Dimension(110 , 25));
pass.setEchoChar('•'); //Pour choisir le caractère à afficher

 

Autre petite différence, il faut utiliser la méthode getPassword() et non getText() pour récupérer le contenu de la zone de saisie. Évidemment, il vous sera retourné ce que l'utilisateur aura vraiment tapé et non une multitude de points comme l'utilisateur peut le voir. Ça n'aurait aucun intérêt sinon...

 

Et pour clôturer ce long chapitre riche en contenu, le plus compliqué de champs de saisie: le JFormattedTextField. Ce dernier permet de filtrer ce que l'utilisateur entre, ou de limiter le nombre de caractères. Par exemple, on pourra choisir qu'il soit uniquement possible de rentrer des nombres. Ce qui le rendra bien plus pratique qu'un simple JTextField dans certains cas, et qui évitera de nombreux tests superflus.

 

Dans l'exemple je vais en empiler plusieurs à l'aide d'un BoxLayout, chacun aura un filtre différent :

JFormattedTextField ft1 = new JFormattedTextField(NumberFormat.getIntegerInstance()); //Uniquement des nombres entiers
ft1.setPreferredSize(new Dimension(110 , 25));
		
JFormattedTextField ft2 = new JFormattedTextField(NumberFormat.getNumberInstance()); //Uniquement des nombres
ft2.setPreferredSize(new Dimension(110 , 25));
		
JFormattedTextField ft3 = new JFormattedTextField(NumberFormat.getPercentInstance()); //Uniquement des pourcentages
ft3.setPreferredSize(new Dimension(110 , 25));
		
JFormattedTextField ft4 = new JFormattedTextField(DateFormat.getDateInstance(DateFormat.SHORT)); //Uniquement des dates sous la forme jj/mm/aa
ft4.setPreferredSize(new Dimension(110 , 25));
		
JFormattedTextField ft5 = new JFormattedTextField(DateFormat.getTimeInstance()); //Uniquement des heures sous la forme hh:mm:ss
ft5.setPreferredSize(new Dimension(110 , 25));

		
panel.setLayout(new BoxLayout(this , BoxLayout.PAGE_AXIS));
panel.add(ft1);
panel.add(ft2);
panel.add(ft3);
panel.add(ft4);
panel.add(ft5);

 

TqcQZGv.png

 

Il faut avouer que les possibilités des filtrage restent assez limitées. Alors voila ce que je vous propose: nous allons créer notre propre modèle ! :D Nous allons utiliser des MaskFormatter, adaptés aux numéros de série, de téléphone, etc...

MaskFormatter mask = new MaskFormatter();
try {
	mask.setMask("## ## ## ## ##"); //Numéro de téléphone français
} catch (ParseException e) {
	e.printStackTrace();
}
		
JFormattedTextField ft1 = new JFormattedTextField(mask);
ft1.setPreferredSize(new Dimension(110 , 25));

 

ZJR8Ppz.png

 

Je pense que cette histoire n'est pas très claire pour vous. Pourquoi avoir mis plein de dièses en argument du constructeur du MaskFormatter? Il s'agit de symboles représentant un ensemble de caractères. La dièse représente donc un nombre encore inconnu. Voici une liste détaillée:

  • # : Un chiffre
  • ' : Un symbole comme '!' ou '$', appelé caractère d'échappement. Cela comprend aussi '\n' '\t'.
  • U : Une lettre (les minuscules sont changées en majuscules)
  • L : Une lettre (les majuscules sont changées en minuscules)
  • A : Un chiffre ou une lettre
  • ? : Une lettre
  • * : N'importe quel caractère
  • H : N'importe quel caractère hexadécimal (0-9, a-f ou A-F)

 

 

Voici un exemple: Nous souhaitons que l'utilisateur entre un chiffre puis une lettre, puis n'importe quel caractère, on fera alors :

mask.setMask("# ? *"); //Les espaces sont facultatifs, mais s'ajouteront automatiquement lors de la saisie

 

Il y a une autre manière de procéder : autoriser des caractères ou en interdire :

MaskFormatter mask = new MaskFormatter();
try {
	mask.setMask("***************"); //15 caractères max
} catch (ParseException e) {
	e.printStackTrace();
}
mask.setValidCharacters("abcdefghijklmnopqrstuvwxyz"); //Caractères autorisés
mask.setInvalidCharacters("0123456789"); //Caractères interdits (dans le cas où on n'utilise pas setValidCharacters())
		
JFormattedTextField ft1 = new JFormattedTextField(mask);
ft1.setPreferredSize(new Dimension(110 , 25));

 

Problème : impossible de créer un masque dont le nombre de caractères est illimité. -_-

Le code cadeau va donc porter là dessus ! ;) Nous allons créer un KeyListener qui s'occupera de tester si le caractère est autorisé ou non :

package fr.bukkit.jeremgamer.cours;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JPanel;
import javax.swing.JTextField;

public class ContentPanel extends JPanel {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7311158212818984619L;

	public ContentPanel() {	
		
		JTextField field = new JTextField();
		field.setPreferredSize(new Dimension(110 , 25));

		TextFieldFormatter form = new TextFieldFormatter();
		form.setValidCharacter("abcdefghijklmnopqrstuvwxyz/*-+&é\"\'(-è_çà)=~#{[|`\\^@]}²");
		field.addKeyListener(form);
		
		this.add(field);

	}
	
	class TextFieldFormatter extends KeyAdapter {

		private String validCharacters = "";
		private String invalidCharacters = "";

		public void setValidCharacter(String s) {
			this.validCharacters = s;
		}
		
		public void setInvalidCharacters(String s) {
			this.invalidCharacters = s;
		}
		
		@Override
		public void keyTyped(KeyEvent e) {
			char c = e.getKeyChar();
			if((c!=KeyEvent.VK_BACK_SPACE) && (c!=KeyEvent.VK_DELETE) && (c!=KeyEvent.VK_ENTER) && (c!=KeyEvent.VK_SPACE))
				if(invalidCharacters.contains(Character.toString(c)) || !validCharacters.contains(Character.toString(c))) {
					Toolkit.getDefaultToolkit().beep(); //Notifier l'erreur par un son du système d'exploitation
					e.consume(); //On stoppe l'event, ainsi le caractère n'est pas ajouté
				}
		}
		
	}
}

 

Je pense que vous allez revenir ici régulièrement avant de tout maitriser, car il est difficile de tout retenir ! De plus je pense qu'un résumé ne sera pas nécessaire sachant que tout ce que je vous ai présenté ici était assez synthétique. Normalement vous êtes désormais en mesure de créer de belles interfaces graphiques interactives et dynamiques. ;) Vous savez donc ce qui vous attend, un TP ! ^_^ Cela faisait bien trop longtemps que je ne vous en avais pas fait faire.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0937398001432731892.png

Comme promis, nous allons revenir en détails sur les Listener, comment fonctionnent-t-ils? Comment font ils pour savoir qu'ils peuvent écouter tel objet? Par quel procédé remarquent-ils que quelque chose c'est produit dans cet objet? Toutes ces questions vont trouver une réponse dans ce chapitre ;)

 

Reprenons l'exemple du début du chapitre précédent :

Citation

Imaginez une situation peu probable: des enfants jouent dans leur chambre (ce sont nos objets), quand soudain leurs parents les appellent à table. Les enfants écoutent leurs parents et viennent déjeuner. Ici le principe est le même, nos objets écouteront d'autres objets, et quand ils se manifesteront, on définira un comportement.

 

Nous allons entrer un peu plus dans les détails, en faisant le lien avec du code. Imaginons qu'il y a deux enfants. Sous forme de code on aura quelque chose qui ressemble à ça :

//Dans la classe Parents
private void appelerATable() {
     enfant1.venirATable();
     enfant2.venirATable();
}

 

Problème: imaginez que le couple décide d'avoir un nouvel enfant. Quand ils appelleront à table, seuls les deux premiers viendront. -_-

Vous allez me dire qu'il suffit d'une petite ArrayList et un boucle for, en ajoutant le nouvel enfant à la liste. J'ai mieux à proposer ! Nous allons élever ces enfants pour qu'ils écoutent et non qu'ils soient forcés à exécuter ce que l'on leur demande. Car dans le code ci-dessus, finalement les enfants ne viennent pas tout seul, ce sont les parents qui viennent les chercher ! :o Cependant vous remarquerez bien vite que cette proposition n'est pas sans intérêt, au contraire ! ;) il faudra juste y ajouter quelques subtilités.

 

Première étape: apprendre aux parents à se faire écouter par leurs enfants, peu importe le nombre qu'ils sont. Ce qui est bien, c'est que cela ne s'appliquera pas qu'aux enfants ! Je m'explique: nos parents seront donc écoutables, ce qui signifie que n'importe quel objet pourra agir en conséquences des actes de ces derniers. Si jamais vous avez à rajouter des éléments dans votre programme, comme par exemple les grands-parents ou les amis, et bien il faudra adapter votre classe Parents afin qu'elle supporte cette nouveauté. Nous allons optimiser tout ça, plus besoin de toucher au code des parents !

 

On appelle cela le pattern observer. Les patterns sont des logiques de programmation pour optimiser le code en vue de futurs changements possibles, afin de rendre chaque objet complètement indépendant et rendre votre code 100% modulable !

Ici on utilisera le patter observer car il se prête totalement à notre exemple: il s'agit "d'écouter".

 

Maintenant que le concept est clair dans votre tête, passons à la pratique ! :D Lisez attentivement le code qui suit.

package fr.bukkit.jeremgamer.cours;

import java.util.Observable;

public class Parent extends Observable {
	
	public static final String APPEL_A_TABLE = "AAAAAAAAAAAA TAAAAAAAABBLEEEEEEEUUUUUUX!!!!";
	
	protected void appelerATable() {
		crier(APPEL_A_TABLE);
	}
	
	private void crier(String message) {
		setChanged(); //Héritée de Observable, indique qu'un changement est effectué
		//Utile lorsqu'une variable est changée et que les Observers doivent en être informés
		notifyObservers(message); //On notifie le message aux objets écoutant
	}
	
	protected Enfant faireEnfant() {
		Enfant enfant = new Enfant(); //L'enfant est né!

		//On ajoute l'enfant aux Observer du Parent
		addObserver(enfant); //Méthode héritée de Observable
		return enfant;
	}

}
package fr.bukkit.jeremgamer.cours;

import java.util.Observable;
import java.util.Observer;

public class Enfant implements Observer {

	@Override
	public void update(Observable o, Object arg) {
		if(o instanceof Parent)
			if(arg.equals(Parent.APPEL_A_TABLE))
				venirATable();
		
	}
	
	private void venirATable() {
		System.out.println("Un enfant est venu à table.");
	}

}

 

Et pour finir, dans la méthode principale :

public static void main(String[] args) {
		
	Parent parent = new Parent();
		
	parent.faireEnfant(); //On fait deux enfants
	parent.faireEnfant();
	
	parent.appelerATable();
}

 

Essayons :

Un enfant est venu à table.
Un enfant est venu à table.

 

C'est bien ce que nous voulions ! Cela fonctionne très bien ! ^_^ Mis a part le fait que le parent peut faire des enfants tout seul et qu'il les appelle directement à table à peine leur naissance terminée, on peut voir que ces derniers écoutent bien ! :D Pour le réalisme, nous allons faire abstraction pour cette fois. :ph34r:

 

Revenons en détails sur ce code :

Vous pouvez constater que l'on a effectué aucun référencement! Ce qui est déjà un grand pas. Regardez à nouveau le code ci-dessus et constatez que... Nos objets Enfant et Parent sont pas encore complètement indépendants car dans la méthode faireEnfant() de la classe Parent, on instancie un Enfant. Le problème est simple à régler, je suis certain que vous avez déjà deviné la solution ! ;)

 

Rien de bien compliqué, retirons cette méthode. Nous procéderons donc ainsi, ce qui revient strictement au même :

Parent parent = new Parent();
		
parent.addObserver(new Enfant());
parent.addObserver(new Enfant());
		
parent.appelerATable();

 

Nos deux classes sont désormais bien indépendantes! Tellement que l'on pourra comme je vous l'ai dis tout à l'heure ajouter un nouveau type de personnes sans aucun problème, et sans avoir à toucher au code d'une autre classe que celle du nouveau personnage ! ;)

 

C'est bien beau tout ça mais moi il y a quand même quelque chose qui me chiffonne... Vous ne savez pas de quoi il s'agit?

Si je vous rappelle que Observable est une classe, toujours aucune réaction? :huh:

 

Je pense que cette dernière information suffira donc à vous mettre en évidence le problème : rappelez-vous que Java ne gère pas l'héritage multiple !

Ah voila! Je sais que vous avez saisi désormais ! Comment faire si notre objet à écouter hérite déjà d'une autre classe? :unsure: Ce qui sera d'ailleurs très souvent le cas dans la programmation de GUI... Et oui, quasiment toutes nos classes hériteront de classes issues du package swing ou AWT, comme nous l'avons déjà fait avec les JPanel ou les JButton.

 

Mais comme toujours, il existe une solution ! B) Car il y a toujours une solution! Nous allons réarranger tout ça à l'aide de nos propres interfaces. De plus cela vous permettra de savoir exactement comment fonctionne les Observable; c'est tout simple :

package fr.bukkit.jeremgamer.cours;

public interface Observable_ { //Notez bien l'underscore, c'est une convention lorsqu'on utilise des noms déjà utilisés dans l'API Java

	  public void addObserver(Observer_ obs);
	  public void updateObservers(String message);
	  public void deleteObserver(Observer_ obs);
	
}
package fr.bukkit.jeremgamer.cours;

public interface Observer_ {	
	public void update(String message);
}
package fr.bukkit.jeremgamer.cours;

import java.util.ArrayList;


public class Parent implements Observable_ {
	
	public static final String APPEL_A_TABLE = "AAAAAAAAAAAA TAAAAAAAABBLEEEEEEEUUUUUUX!!!!";
	
	private ArrayList<Observer_> listObservers = new ArrayList<Observer_>();
	
	protected void appelerATable() {
		crier(APPEL_A_TABLE);
	}
	
	private void crier(String message) {
		updateObservers(message);
	}
	
	protected Enfant faireEnfant() {
		Enfant enfant = new Enfant();
		addObserver(enfant);
		return enfant;
	}

	@Override
	public void addObserver(Observer_ obs) {
		listObservers.add(obs);
	}

	@Override
	public void updateObservers(String message) {
		for(Observer_ obs : listObservers)
			obs.update(message);
	}

	@Override
	public void deleteObserver(Observer_ obs) {
		listObservers.remove(obs);
	}

}
package fr.bukkit.jeremgamer.cours;


public class Enfant implements Observer_ {

	@Override
	public void update(String message) {
			if(message.equals(Parent.APPEL_A_TABLE))
				venirATable();
		
	}
	
	private void venirATable() {
		System.out.println("Un enfant est venu à table.");
	}

}

 

Si vous testez ce code, vous remarquerez qu'il fonctionne aussi bien qu'en utilisant la méthode proposée par l'API Java, mais l'héritage de l'Observable est désormais libre ! :)

 

Il faudra bien-sûr adapter ce code à vos besoins. Ici on as juste un message donc on se contentera de cette version simplifiée, mais rien ne vous empêche d'essayer de reproduire quelque chose d'aussi pratique et adapté à toutes les situations que celui proposé par l'API Java.

 

 

Résumé :

 

  • Les patterns sont des logiques de programmation visant à préparer le code à de futurs ajouts et modifications, le tout avec une facilité déconcertante.
  • Observable est une classe permettant à celles qui en héritent de pouvoir être écoutées par les classes implémentant l'interface Observer.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0623585001432129495.png

Nous entamons une toute nouvelle notion: la programmation évènementielle. Dans ce chapitre, vous ferez vos premiers pas dans ce domaine.

 

Il s'agira "d'écouter" ses composants, mais pas forcément, il peut également s'agir d'un objet comme les autres ! Cette fois-ci nous verrons simplement comment définir une action à réaliser lors de différents évènements possibles sur un bouton.

 

Le concept n'est pas bien compliqué :

Imaginez une situation peu probable : des enfants jouent dans leur chambre (ce sont nos objets), quand soudain leurs parents les appellent à table. Les enfants écoutent leurs parents et viennent déjeuner. Ici le principe est le même, nos objets écouteront d'autres objets, et quand ils se manifesteront, on définira un comportement.

 

Pour cela nous utiliserons des Listener. Il en existe plusieurs, qui sont tous des interfaces, et qui nous permettrons de prendre en compte plusieurs genres d'évènements. Par exemple on aura tout d'abord le plus fréquent : ActionListener, que l'on détaillera dans un instant, puis MouseListener, qui permet d'écouter la souris de l'utilisateur, ou KeyListener, qui permet quand à lui d'écouter le clavier.

 

Nous travaillerons sur de nombreuses classes anonymes, alors si vous ne vous sentez pas très à l'aise avec cette notion, je vous invite à relire le chapitre sur l'anonymat.

 

Reprenons une fenêtre toute simple, contenant simplement un bouton avec lequel nous allons interagir. Nous allons ensuite y ajouter un ActionListener.

 

Une seconde! Il s'agit bien d'une interface? Comment en faire une classe?

Souvenez-vous ! Il est possible de créer une classe anonyme à l'aide d'une interface. ;)

 

Voici alors la forme que cela prendra :

JButton button = new JButton("Un bouton");
		
button.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		//Notre action à effectuer lors du clic
		System.out.println("Vous avez cliqué sur le bouton");
	}
	
});

Je vous invite à tester, on voit que la phrase s'affichera bien dans la console à chaque clic sur le bouton ! :)

 

Vous voyez, rien de bien sorcier pour l'instant. C'est dans la seconde partie de ce chapitre que cela se compliquera, quand je vous expliquerai en détails comment cela fonctionne, et comment le réaliser sur vos propres objets. ^_^

 

Fermons la parenthèse, rien ne vous empêche d'ajouter plusieurs Listener à vos boutons, même s'il est préférable de tout gérer en un seul, ce qui est bien plus optimisé. Cependant s'il vous faut des Listener différents, pour écouter le clavier et la souris en même temps par exemple, alors il sera nécessaire d'en ajouter plusieurs, ce qui parait logique. Voyons un peu ce qui est réalisable à l'aide ce cet outil :

button.addActionListener(new ActionListener() {

	@Override
	public void actionPerformed(ActionEvent e) {
		JButton source = (JButton) e.getSource(); //Pour récupérer le composant source, ici on est sûr qu'il s'agit d'un bouton donc le cast est sans risque
			
		source.setText("Bouton cliqué"); //Changer le texte du bouton lors du clic
		source.setEnabled(false); //Griser le bouton après le clic
		//Toutes les autres méthodes de JButton
			
		//N'oubliez pas que nous sommes dans une autre classe, faites bien attention à l'accessibilité des variables utilisées!
	}
			
});

 

Voyons désormais comment écouter le clavier de l'utilisateur. Nous allons implémenter KeyListener à notre panneau.

package fr.bukkit.jeremgamer.cours;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.JPanel;

public class ContentPanel extends JPanel implements KeyListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7311158212818984619L;

	public ContentPanel() {
		
		this.addKeyListener(this); //Il faut penser à ajouter le Listener!
		this.setFocusable(true);
		this.requestFocus(); //Pour s'assurer que le panneau a bien le focus
		//Car les évènements seront déclenchés uniquement si le panneau a le focus

	}

	@Override
	public void keyPressed(KeyEvent e) { //Quand une touche sera enfoncée
		System.out.println("Key pressed: " + e.getKeyChar());
	}

	@Override
	public void keyReleased(KeyEvent e) { //Quand une touche sera relâchée
		System.out.println("Key released: " + e.getKeyChar());
	}

	@Override 
	public void keyTyped(KeyEvent e) { //Quand une touche sera tapée (enfoncée puis relâchée)
		System.out.println("Key typed: " + e.getKeyChar());
	}
}
Key pressed: b
Key typed: b
Key released: b
Key pressed: u
Key typed: u
Key released: u
Key pressed: k
Key typed: k
Key released: k

 

On peut constater que KeyTyped() est appelé avant KeyReleased(), ce qui pourrait être pratique dans certain cas.

Avez-vous essayé les flèches directionnelles ou des touches comme Ctrl ou Alt? Le résultat est un peu décevant :

Key pressed: ?
Key released: ?
Key pressed: ?
Key released: ?

 

Sans parler du fait que KeyTyped() n'est pas appelé, on n'a le droit qu'à un point d'interrogation! Cela est du au fait que pour récupérer la touche pressée, on utilise la méthode getKeyChar(), qui retourne forcément un char, or ces touches pourraient difficilement retourner une telle valeur! On utilisera donc une autre méthode: getKeyCode(), qui retournera l'identifiant de la touche pressée.

Key pressed: 38
Key released: 38
//38: flèche haut
Key pressed: 65
Key typed: 0
Key released: 65
//65: touche 'a'

 

Hum tout de suite on est capables récupérer toutes les touches, mais tous ces numéros ne sont pas très pratiques... :unsure: Comment s'y retrouver et les reconnaitre?

 

Miracle, l'API Java nous sauve la vie encore une fois ! ^_^ La classe KeyEvent nous propose de nombreuses constantes statiques recensant toutes les touches de notre clavier !

 

Par exemple, si on veut faire un petit jeu 2D qui se joue avec les flèches du clavier, rien de plus simple maintenant que l'on a ces constantes !

int code = e.getKeyCode();
switch (code) {
case KeyEvent.VK_LEFT: 
	System.out.println("Aller à gauche");
	break;
case KeyEvent.VK_UP: 
	System.out.println("Aller en haut");
	break;
case KeyEvent.VK_RIGHT: 
	System.out.println("Aller à droite");
	break;
case KeyEvent.VK_DOWN: 
	System.out.println("Aller en bas");
	break;
}

 

Et voila un résultat plus convenable ! :)

 

Vous avez sans doute remarqué que maintenant que nous utilisons getKeyCode(), KeyTyped() nous affirme toujours que la touche tapée porte le code 0. :huh: C'est parce que cette méthode est utilisée pour supporter toutes les touches. getKeyChar() sera surtout utilisé pour du traitement de texte.

 

Nous n'avons pas besoin de cette méthode, dans l'idéal, on aimerait bien pouvoir s'en débarrasser pour ne pas charger le code inutilement. Magie magie! Un autre merveilleux outil fournit pas notre API préférée est là pour nous ! N'est-ce pas génial?

On appelle cela les Adapter, ce sont des classes héritant des interfaces Listener, mais qui nous laissent le choix des méthodes utilisées. Comme il s'agit de classes, on devra alors procéder différemment :

package fr.bukkit.jeremgamer.cours;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JPanel;

public class ContentPanel extends JPanel {

    /**
     *
     */
    private static final long serialVersionUID = -7311158212818984619L;

    public ContentPanel() {
        
        this.addKeyListener(new KeyboardListener()); //Cette fois on instancie notre classe interne
        this.setFocusable(true);
        this.requestFocus();
    }
    
    class KeyboardListener extends KeyAdapter { //Classe interne hériant de Keyadapter
        
        public void keyPressed(KeyEvent e) {
            int code = e.getKeyCode();
            switch (code) {
            case KeyEvent.VK_LEFT:
                System.out.println("Aller à gauche");
                break;
            case KeyEvent.VK_UP:
                System.out.println("Aller en haut");
                break;
            case KeyEvent.VK_RIGHT:
                System.out.println("Aller à droite");
                break;
            case KeyEvent.VK_DOWN:
                System.out.println("Aller en bas");
                break;
            }
        }
        
        //On s'est débarrassé de keyReleased() et keyTyped()
        
    }
}
package fr.bukkit.jeremgamer.cours;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

import javax.swing.JPanel;

public class ContentPanel extends JPanel {

    /**
     *
     */
    private static final long serialVersionUID = -7311158212818984619L;

    public ContentPanel() {
        
        this.addKeyListener(new KeyboardListener()); //Cette fois on instancie notre classe interne
        this.setFocusable(true);
        this.requestFocus();
    }
    
    class KeyboardListener extends KeyAdapter { //Classe interne hériant de Keyadapter
        
        public void keyPressed(KeyEvent e) {
            int code = e.getKeyCode();
            switch (code) {
            case KeyEvent.VK_LEFT:
                System.out.println("Aller à gauche");
                break;
            case KeyEvent.VK_UP:
                System.out.println("Aller en haut");
                break;
            case KeyEvent.VK_RIGHT:
                System.out.println("Aller à droite");
                break;
            case KeyEvent.VK_DOWN:
                System.out.println("Aller en bas");
                break;
            }
        }
        
        //On s'est débarrassé de keyReleased() et keyTyped()
        
    }
}

 

Et maintenant, la souris ! Pour en tirer une application intéressante, nous allons utiliser le MouseListener sur une classe de bouton personnalisé ! Nous nous contenterons de changer la couleur de fond du bouton à titre d'exemple.

package fr.bukkit.jeremgamer.cours;

import java.awt.Color;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import javax.swing.JButton;

public class CustomButton extends JButton implements MouseListener {

	
	/**
	 * 
	 */
	private static final long serialVersionUID = 7877964058790016354L;

	public CustomButton(String title) {
		super(title);
		this.addMouseListener(this);
		this.setBackground(Color.BLUE);
		this.setForeground(Color.white);
	}
	
	@Override
	public void mouseClicked(MouseEvent e) { //Lors du clic
		//On ne met rien ici car on s'occupe du changement de couleur dans
		//mousePressed() et mouseReleased()
   	         //On utilisera d'ailleurs un MouseAdapter en temps normal pour retirer cette méthode inutile.
	}

 	@Override
	public void mouseEntered(MouseEvent e) { //Lorsque la souris entre dans la zone du bouton
		this.setBackground(Color.CYAN);
		this.setForeground(Color.black);
	}

 	@Override
 	public void mouseExited(MouseEvent e) { //Lorsque la souris sors de la zone du bouton
		this.setBackground(Color.BLUE);
		this.setForeground(Color.white);
	}

	 @Override
 	public void mousePressed(MouseEvent e) { //Lorsque que le bouton du clic est enfoncé
		this.setForeground(Color.BLACK);
	}

 	@Override
	public void mouseReleased(MouseEvent e) { //Lorsque le bouton du clic est relâché
		this.setBackground(Color.CYAN);
		this.setForeground(Color.black);
	}

}

 

Je vous invite à essayer ce code ;)

Je pense que vous avez compris le principe.

 

Dans la seconde partie de ce chapitre nous verrons plus en détails comment fonctionnent réellement les Listener, comment faire les votres, ainsi que comment écouter vos objets maison ! :D

 

 

Résumé :

 

  • Les interfaces Listener permettent d'écouter des objets.
  • C'est à dire être "informé" lorsqu'un évènement en relation avec cet objet ce produit.
  • ActionListener permet d'écouter lorsqu'une action comme un clic sur un bouton est effectuée.
  • KeyListener permet d'écouter le clavier.
  • MouseListener permet d'écouter la souris.
  • Les Adapter sont des classes implémentant les Listener et permettant de se passer des méthodes superflues.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

Jackblue

blog-0322314001431881254.png

Chapitre 2 : les Commandes (Partie I)

Bonjour à tous !

 

On se retrouve aujourd'hui pour voir comment communiquer directement avec votre serveur via des commandes, on va voir comment les créer, j'ai eu la surprise dernièrement de voir quelques débutants utiliser des events pour faire des commandes grâce à PlayerCommandPreprocessEvent et AsyncPlayerChatEvent entre autre, qui permet d'analyser ce qu'envoie le joueur avant de faire quelques choses, seulement ils ne sont pas fait à la base pour ça :x

 

On va donc voir comment faire de "vraies" commandes, avec l'objet le plus adapté pour cela j'ai nommé..

 

I - Le CommandExecutor

 

Donc on va reprendre notre workspace avec notre projet du précèdent chapitre et on va rajouter (dans mon cas):

this.getCommand("pizza").setExecutor(new CommandPizza());

Pour que notre class principale ressemble à ça :

 

public class PizzaPlugin extends JavaPlugin{		Logger log;		@Override	public void onEnable(){		log = this.getLogger();		this.getCommand("pizza").setExecutor(new CommandPizza());		log.info("La pizza est prete !");	}		@Override	public void onDisable(){		log.info("Bob on a perdu la pizza :c");	}}

 

Donc on initialise notre commande via un String qui sera chargé de notre plugin.yml (on verra cette partie à la fin), et notre "Executor" on va devoir le créer, passer votre curseur sur l'erreur et créer votre class, comme ça Eclipse implements directement CommandExecutor à votre class :)

IE4EMjO.jpg

 

Vous obtenez alors quelques choses qui ressemble à ça :

public class CommandPizza implements CommandExecutor {	@Override	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {		return false;	}}

Je me suis permis de changer les noms des paramètres pour qu'ils soient plus clairs, afin de les commenté :

  • CommandSender sender : Correspond à "l'envoyeur" de la commande, que ça soit un joueur ou la console, on verra juste après comment le reconnaître.
  • Command cmd : L'objet de la commande exécuté, utile si vous utilisez une même class Executor pour plusieurs commandes, mais on évite pour un soucis d'organisation.
  • String label : C'est l'alias de la commande utilisé.
  • String[] args : Un tableau de String contenant tout les arguments envoyés par le sender (et seulement les arguments pas la commande).

On va maintenant rajouter ceci à notre plugin.yml :

commands:   pizza:     description: Permet de gérer une pizza.     permission: pizza.manage     usage: Un problème est survenue, tapez /pizza help pour plus d'informations.

  • description : Description du plugin
  • permission : Permission nécessaire pour utiliser cette commande
  • usage : Ce message apparaît si votre method onCommand return false.

Ensuite on va voir comment déterminé le sender, via l'opérateur instanceof :

        @Override	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {		if(sender instanceof Player){			sender.sendMessage("Joueur");		}else if(sender == Bukkit.getServer().getConsoleSender()){			sender.sendMessage("Console");		}else{			sender.sendMessage("Inconnu");		}		return true;	}

Cet opérateur détermine si notre objet est l'instance d'un autre, par exemple vous avez un objet Animal, votre map est remplis d'objet Animal mais vous, vous voulez seulement que votre event ou commande marche sur un animal spécifique hérité de votre SuperClass Animal, dans notre cas on cherche a savoir si c'est un Jaguar, on fera alors ainsi :

        if(animal instanceof Jaguar){		//Do awesome stuff	}

C'est le même principe avec notre commande, on a plusieurs objets qui implements l'interface CommandSender, et on test si c'est celui qui nous convient.

 

A noté que je return true à la fin de ma method onCommand, tout simplement parce notre sender est obligatoirement dans ces trois cas de figure, soit la console, soit un joueur, soit autre.

Cependant dans la plupart des cas on return false car cela signifie que aucune condition n'a étais remplis avant et donc que la commande a échoué, voici un exemple :

        @Override	public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {		if(sender instanceof Player){			if(args.length == 1){				Player player = (Player) sender; //On créer un objet Player afin de cast notre sender				player.sendMessage("Vous avez créer une pizza :" + args[0]); //On affiche notre pizza dans le chat				return true; // on return true comme la commande s'est correctement executée			}		}else{			sender.sendMessage("Impossible de créer une pizza depuis la console");			return true; //afin de ne pas avoir le message usage de notre plugin.yml		}		return false;	}

Ici, return false sera appelé seulement si la commande est faite par un joueur et qu'elle a 0 ou plus d'un argument.

A noté que j'ai volontairement créer un objet Player, afin de cast l'objet sender, sachant que l'on a au préalable vérifié que notre objet était bien un objet Player, bien que ça soit ici totalement inutile j'aurais pu envoyé mon message sans le cast en utilisant l'objet sender, et c'est ce que je fais pour envoyé un message à la console :)

 

Voilà on a fait le tour des bases à connaître sur les commandes, on verra la deuxième partie la semaine prochaine pour faire des commandes plus avancées et gérer des permissions, en attendant je vous invite a faire la commande help dont on a parlé dans le plugin.yml.

 

Astuce : Utiliser la fonction equals pour comparer deux objets String. ex : args[0].equals("help").

 

 

 

Jackblue

SystemGlitch

blog-0373382001431880416.png

Dans ce chapitre nous verrons deux nouveaux layouts, le premier est assez simple à utiliser et permet de superposer plusieurs panneaux et de passer de l'un à l'autre facilement et le second est quand à lui très complexe mais permet de créer une disposition en grille avec des cases de différentes tailles.

 

Commençons sans plus tarder avec le CardLayout ! Comme expliqué ci-dessus, il permet de vous faciliter la vie lorsque vous avec besoin de passer d'une scène à l'autre de votre logiciel. Par exemple, lorsque vous avez un menu permettant d'accéder aux différentes fonctionnalités du logiciel sur la gauche, et les options de ces fonctionnalités à droite en grand. Pour passer de l'une à l'autre, vous n'allez pas retirer un panneau puis en ajouter un autre à chaque fois. Premièrement cela risque fort de perturber votre disposition générale. De plus, ce n'est absolument pas optimisé et cela consommera beaucoup de ressources de recalculer les panneaux et le layout à chaque changement. On utilisera alors le CardLayout sur un conteneur qui contiendra tous ces panneaux ! Nos panneaux seront tout simplement tous calculés à l'instanciation du conteneur, la disposition ne changera pas et on pourra changer de panneau à volonté sans aucun problème !

 

Trêve de parlotte, voici le code permettant de le faire. Je l'expliquerais ensuite. Je vous invite fortement à le recopier et le tester pour voir le résultat. Vous ne comprendrez pas tout c'est normal, car il y a des notions que vous ne connaissez pas encore à l'intérieur, notamment les actions sur les boutons.

 
package fr.bukkit.jeremgamer.cours;

import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JPanel;

public class ContentPanel extends JPanel {

    /**
     *
     */
    private static final long serialVersionUID = -7311158212818984619L;
    
    private CardLayout cl = new CardLayout(); //On instancie le layout
    private final String CONTENT[] = {"red" , "green" , "blue"}; //On crée un tableau contenant les identifiants
    JPanel container = new JPanel(cl); //On instancie le conteneur
    
    public ContentPanel() {        
        
        //On instancie les panneaux que l'on ajoutera au conteneur
        JPanel red = new JPanel();
        red.setBackground(Color.RED); //On change leur couleur de fond pour les différencier
        red.setName(CONTENT[0]); //Facultatif, on l'utilisera juste pour retrouver quel panneau est acutellement affiché
        
        JPanel green = new JPanel();
        green.setBackground(Color.GREEN);
        green.setName(CONTENT[1]);
        
        JPanel blue = new JPanel();
        blue.setBackground(Color.BLUE);
        blue.setName(CONTENT[2]);
        
        container.add(red , CONTENT[0]); //On ajoute le panneau rouge avec l'identifiant "red"
        container.add(green , CONTENT[1]); //On ajoute le panneau vert avec l'identifiant "green"
        container.add(blue , CONTENT[2]); //On ajoute le panneau bleu avec l'identifiant "blue"
        
        
        //----------------------------------------
        //On passe à la création du panneau de contrôle qui nous permattra de passer d'un panneau à l'autre
        //Ne vous attardez pas trop sur cette partie, ce qu'elle contient n'est pas encore de votre niveau
        
        JPanel control = new JPanel();
        
        
        JComboBox<String> index = new JComboBox<String>();
        for(String s : CONTENT)
            index.addItem(s);
        
        index.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                @SuppressWarnings("unchecked")
                JComboBox<String> box = (JComboBox<String>) e.getSource();
                cl.show(container, (String)box.getSelectedItem()); //On affiche le panneau portant l'identifiant sélectionné dans le menu déroulant
            }
            
        });
        
        JButton previous = new JButton("Précédent");
        previous.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                cl.previous(container); //On affiche le panneau précédent (Ajouté avant celui acutellement affiché)
                //Si le panneau affiché et le premier, alors le dernier panneau sera affiché à l'appel de cette méthode
                index.setSelectedItem(getCurrentCard().getName()); //On actualise le menu déroulant pour qu'il affiche le bon identifiant
            }
            
        });
        
        JButton next = new JButton("Suivant");
        next.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                cl.next(container); //On affiche le panneau suivant (Ajouté après celui acutellement affiché)
                //Si le panneau affiché et le dernier, alors le premier panneau sera affiché à l'appel de cette méthode
                index.setSelectedItem(getCurrentCard().getName());
            }
            
        });
        
        //On ajoute tous les contrôles au panneau de contrôle
        control.add(previous);
        control.add(next);
        control.add(index);
        
        //On ajoute le conteneur et le panneau de contrôle au panneau principal
        this.setLayout(new BorderLayout());
        this.add(container , BorderLayout.CENTER);
        this.add(control , BorderLayout.SOUTH);
        
    }
    
    private JPanel getCurrentCard() { //Pour récupérer le panneau actuellement affiché
        JPanel card = null;

        for (Component comp : container.getComponents())
            if (comp.isVisible() == true)
                card = (JPanel)comp;
        return card;
    }

}

 

Oui c'est un peu long, mais le plus important est en haut du constructeur, avant le trait de séparation fait avec un commentaire. ;) Je vous avais prévenu que la longueur de vos programmes allait beaucoup augmenter ! ^_^

Voici le résultat en images :

 

iUrVxDo.png

 

Vous avez vu que j'utilisais un tableau pour stocker les identifiants, mais vous pouvez très bien faire sans et spécifier directement les identifiants lors de l'ajout au conteneur :

container.add(panel , "id");

 

Utiliser un tableau assure simplement de s'y retrouver dans les identifiants et de ne pas en oublier. C'est d'ailleurs aussi efficace pour tous les utiliser rapidement comme je l'ai fait ici :

for(String s : CONTENT)
	index.addItem(s);

 

Vous n'avez très certainement pas compris le reste et c'est normal car cela relève de la programmation événementielle, que nous aborderons très bientôt.

 

 

Passons maintenant aux choses sérieuses ! Nous allons nous attaquer à un gros morceau en parlant du GridBagLayout !

 

Le conteneur contiendra de nombreuses cellules faisant office de cases dans un grille, chaque composant sera ajouté à l'aide de la coordonnée de la cellule voulue, et pourra occuper une ou plusieurs d'entre elles ! C'est ainsi que comme je l'ai dit au début de ce chapitre, on pourra obtenir une grille avec des cases de tailles différentes.

 

Les coordonnées se décomposent de la même manière que dans un tableau bi-dimensionnel, c'est à dire que la première case sera aux coordonnées (0 , 0) celle en dessous se trouvera à (0 , 1), la case à droite de cette dernière sera à (1 , 1).

Gardez à l'esprit que la coordonnée x est le numéro de la colonne, et y le numéro de la ligne.

 

Nous procèderons ainsi :

  • Définir les coordonnées
  • Indiquer si le composant utilisera plusieurs cellules en vertical ou en horizontal.
  • Indiquer quand une ligne est terminée

 

Voici quelle forme cela va prendre :

package fr.bukkit.jeremgamer.cours;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;

import javax.swing.JPanel;

public class ContentPanel extends JPanel {

    /**
     *
     */
    private static final long serialVersionUID = -7311158212818984619L;

    public ContentPanel() {

        //Instanciation des différentes cellules
        JPanel cell0 = new JPanel();
        cell0.setBackground(Color.BLACK);

        JPanel cell1 = new JPanel();
        cell1.setBackground(Color.BLUE);

        JPanel cell2 = new JPanel();
        cell2.setBackground(Color.CYAN);

        JPanel cell3 = new JPanel();
        cell3.setBackground(Color.GREEN);

        JPanel cell4 = new JPanel();
        cell4.setBackground(Color.MAGENTA);

        JPanel cell5 = new JPanel();
        cell5.setBackground(Color.ORANGE);

        JPanel cell6 = new JPanel();
        cell6.setBackground(Color.RED);


        //Conteneur
        JPanel container = new JPanel(new GridBagLayout());

        GridBagConstraints gbc = new GridBagConstraints(); //Instanciation de l'objet gérant les contraintes

        gbc.weightx = 1; //On demande de l'espace supplémentaire pour le redimensionnement
        gbc.weighty = 1; //Chaque cellule serait très petite sinon
        gbc.fill = GridBagConstraints.BOTH; //On remplit autant de cases que l'on peut en verticalité et en horizontalité

        //On positionne dans la première case.
        gbc.gridx = 0;
        gbc.gridy = 0;

        //On définit la taille que le composant empruntera (en cases)
        gbc.gridwidth = 1;
        gbc.gridheight = 1;
        container.add(cell0 , gbc); //On ajoute la cellule 0 en spécifiant la contrainte

        //-------------------------

        gbc.gridx = 1;
        gbc.gridheight = 2;
        container.add(cell1 , gbc);

        //-------------------------

        gbc.gridx = 2;
        gbc.gridheight = 1;
        container.add(cell2 , gbc);

        //-------------------------

        gbc.gridx = 3;
        container.add(cell3 , gbc);

        //-------------------------

        gbc.gridwidth = GridBagConstraints.REMAINDER; //Fin de ligne
        gbc.gridx = 0;
        gbc.gridy = 1;
        gbc.gridwidth = 1;
        container.add(cell4 , gbc);

        //-------------------------

        gbc.gridx = 2;
        gbc.gridwidth = 3;
        container.add(cell5 , gbc);

        //-------------------------

        gbc.gridwidth = GridBagConstraints.REMAINDER;
        gbc.gridy = 2;
        gbc.gridx = 0;
        gbc.gridwidth = 5;        
        container.add(cell6 , gbc);


        this.setLayout(new BorderLayout());
        this.add(container , BorderLayout.CENTER);

    }
}

 

d53QkC2.png

 

Voila qui peut être pratique non? ^_^ C'est complexe à comprendre, complexe à utiliser, mais cela permet de faire les GUI les plus élaborés et les plus complets ! ;)

Comme ce layout propose de très nombreuses possibilités, je vous conseille fortement d'aller lire la page de documentation qui en parle. Avec ça, vous saurez tout !

 

Voila c'est tout pour aujourd'hui! Vous êtes maintenant capable de disposer vos composants exactement comme vous le souhaitez! Dans le prochain chapitre, nous nous attaquerons à la programmation événementielle, et donc notamment comment interagir avec ses boutons !

 

 

Résumé :

 

  • Le CardLayout permet d'empiler, de superposer plusieurs panneaux de manière optimisée, et de pouvoir passer facilement d'un panneau à l'autre dans l'affichage.
  • Chaque panneau ajouté avec un CardLayout doit être ajouté avec son identifiant sous forme de String.
  • Le GridBagLayout est un layout permettant la création de grille dont chaque cellule peut avoir une taille différente, de la même manière qu'un tableau Excel.
  • Chaque composant doit être ajouté selon une contrainte du type GridBagConstraints.
  • On parcourt la grille à l'aide de coordonnées.
  • On choisi la taille de chaque composant en unités de cellules avec gridwidth et gridheight.

 

 

JeremGamer

 

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0077178001431880403.png

Nous entrons désormais dans une autre dimension de la création de GUI: nous allons voir comment positionner des composants comme bon nous semble sur notre fenêtre, et surtout, les faire s'adapter automatiquement à la taille de la fenêtre ! Quand je parle de composants, je parle de boutons, de zones de textes, de barres de chargement, etc... Enfin nous y sommes ! ;)

 

La disposition de vos composants sera gérée par des Layout Managers, lesquels réagiront à plusieurs paramètres comme la taille du conteneur, le nombre de composants, etc... Ces layouts se trouvent tous dans le package java.awt.

 

Commençons par reprendre un panneau vierge. Nous allons créer une classe qui hérite de JPanel et nous l'ajouterons ensuite en tant que ContentPane à notre JFrame. Nous y ajouterons un bouton, tout ce qu'il y a de plus basique :

 
public class ContentPanel extends JPanel {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7311158212818984619L;
	
	private JButton button = new JButton("Un bouton"); //Intanciation de notre bouton
	
	public ContentPanel() { //A partir de là, tout fonctionne comme vous savez déjà le faire
							//On utilisera this car cette fois il fera directement référence à notre panneau
		this.add(button); //On ajoute le bouton, rien de bien compliqué!		
	}

}

 

IKJHYIH.png

 

Et voila ! Un bouton, il ne fait rien, mais c'est un bon début !

Petite remarque, chaque composant est unique, c'est à dire que si vous tentez d'ajouter plusieurs fois notre objet "button", il ne s'affichera qu'une seule fois !

Vous remarquerez que notre bouton est centré, avec un petit écart entre lui et le bord supérieur du panneau, le layout par défaut à été utilisé car nous n'en avons défini aucun ! Il s'agit du FlowLayout. Ce layout est le plus basique: les composants se suivent en ligne, et dès qu'un composant atteint ou dépasse le bord du panneau, il y aura un "retour à la ligne". Exemple :

for(int i = 1 ; i <= 5 ; i++)
	this.add(new JButton("Bouton n°" + i));

 

gL3OWY8.png

 

Si vous redimensionnez la fenêtre, vous verrez que les boutons se réorganisent par eux-même !

Des options sont disponibles, mais pour cela il faudra instancier manuellement le layout. Par exemple, dans l'exemple ci-dessous, j'augmente la taille des écarts entre les composants : (Cela est valable pour tous les autres layouts présentés plus tard dans ce chapitre)

FlowLayout flow = new FlowLayout();
flow.setHgap(15);//Écarts horizontaux
flow.setVgap(15);//Écarts verticaux
		
this.setLayout(flow); //On applique le layout à notre panneau
	
for(int i = 1 ; i <= 5 ; i++)
	this.add(new JButton("Bouton n°" + i));

 

BZHSdWu.png

 

 

Avec le FlowLayout, on peut également changer l'alignement. C'est à dire qu'au lieu de centrer, on peut faire partir de la gauche, de la droite, etc...

flow.setAlignment(FlowLayout.CENTER); //Centré, par défaut
flow.setAlignment(FlowLayout.LEFT); //Commence à gauche
flow.setAlignment(FlowLayout.RIGHT); //Commence à droite

 

Avant de voir d'autres layouts, j'aimerais faire une petite parenthèse sur le positionnement absolu. Il s'agit simplement d'ajouter les composants sans layout. Je ne vous le recommande absolument pas, évitez-le le plus possible d'ailleurs, pour éviter de nombreux possibles problèmes de rendu, de chevauchements et autre. De plus utiliser cette technique revient à sacrifier le redimensionnement ainsi que l'adaptation automatique de la disposition. Bref, voici comment faire :

this.setLayout(null); //On dis à notre panneau d'utiliser le positionnement absolu
		
button.setBounds(60, 25, 110, 27); //On positionne le bouton
//x , y , largeur , hauteur
		
this.add(button);

 

oj33oph.png

 

 

 

BorderLayout :

 

Commençons par le plus simple à prendre en main: le BorderLayout. Ce layout permet de positionner facilement 5 composants ou conteneurs. (car oui l'enjeu sera de mélanger plusieurs panneaux par la suite, chacun ayant un layout différent, afin d'obtenir la disposition parfaite !)

Pourquoi seulement 5 composants? Une image vaut bien plus que des mots :

 

IPWUzxJ.png

 

Le code permettant de réaliser cette disposition est le suivant :

this.setLayout(new BorderLayout()); //On indique qu'on utilisera le BorderLayout
		
this.add(new JButton("NORTH") , BorderLayout.NORTH); //On ajoute un bouton dans la région supérieure
this.add(new JButton("WEST") , BorderLayout.WEST); //De même dans la région gauche
this.add(new JButton("CENTER") , BorderLayout.CENTER); //Centre
this.add(new JButton("RIGHT") , BorderLayout.EAST); //Droite
this.add(new JButton("SOUTH") , BorderLayout.SOUTH); //Région inférieure

 

Petite remarque : Si vous n'ajoutez qu'un seul composant au centre, ce dernier prendra tout l'espace disponible. Cela peut-être assez utile. ;)

 

Vous remarquerez que les composants sont redimensionnés. La majorité des layouts appliquent des contraintes à vos composants. Ici, NORTH et SOUTH prendront toute la largeur, WEST et EAST, le maximum de hauteur, et CENTER utilisera tout l'espace comme dit à l'instant.

 

Il est donc possible de prévoir certaines éventualités en donnant une taille préférée à vos composants. Il s'agira de la taille par défaut de votre composant. Je vais modifier cela sur le bouton WEST :

this.setLayout(new BorderLayout()); //On indique qu'on utilisera le BorderLayout
		
JButton west = new JButton("WEST"); //On a besoin de l'instance du bouton pour définir les tailles
west.setPreferredSize(new Dimension(200 , 50)); //largeur, hauteur comme d'habitude
		
this.add(new JButton("NORTH") , BorderLayout.NORTH);
this.add(west , BorderLayout.WEST);
this.add(new JButton("CENTER") , BorderLayout.CENTER);
this.add(new JButton("RIGHT") , BorderLayout.EAST);
this.add(new JButton("SOUTH") , BorderLayout.SOUTH);

 

k0EehFc.png

 

Le bouton a bien sa taille préférée. Cependant, sa hauteur étant soumise à une contrainte du layout, celle-ci ne sera pas affectée par la taille préférée. Cela fonctionne de même pour NORTH et SOUTH bien-sûr mais avec la largeur soumise à une contrainte. Pour CENTER, les deux valeurs sont soumises à des contraintes, il est donc inutile de lui donner de taille préférée.

 

 

 

GridLayout :

 

Ce layout est très pratique pour créer des claviers virtuels de calculatrice par exemple. Il s'agit tout simplement de transformer votre conteneur en une grille dont toutes les cases auront la même taille. Ces cases se redimensionneront bien-sûr avec le panneau. Vous pouvez définir le nombre de colonnes et de lignes. Voici comment faire :

this.setLayout(new GridLayout(3 , 2)); //Lignes , colonnes 
		
for(int i = 0 ; i < 6 ; i++)
	this.add(new JButton(String.valueOf(i)));

 

cCvmYmg.png

 

Vous pouvez changer le nombre de lignes et de colonnes via les méthodes setColumns(int n) pour les colonnes et setRows(int n) pour les lignes. ;) (appelées via l'instance d'un objet GridLayout)

Vous avez vu que j'ai utilisé un boucle pour ajouter tous les boutons, et que ces derniers se sont positionnés comme il faut.

En effet, avec ce layout les composants "remplissent" petit à petit le conteneur, case par case. L'ordre est donc respecté.

 

 

 

BoxLayout :

 

Ce layout vous permettra de disposer vos composants en ligne ou en colonne. On aura donc deux attributs: LINE_AXIS et PAGE_AXIS. Exemple :

JPanel p1 = new JPanel();
p1.setLayout(new BoxLayout(p1, BoxLayout.LINE_AXIS)); //En ligne, le premier argument doit faire référence au conteneur auquel on applique le layout
		
for(int i = 0 ; i < 6 ; i++)
	p1.add(new JButton(String.valueOf(i)));

JPanel p2 = new JPanel();
p2.setLayout(new BoxLayout(p2, BoxLayout.PAGE_AXIS)); //En colonne
		
for(int i = 0 ; i < 6 ; i++)
	p2.add(new JButton(String.valueOf(i)));

		
this.add(p1);
this.add(p2);

 

mH8Mu1h.png

 

Celui-ci est aussi très simple à utiliser vous en conviendrez. ^_^ Là où j'ai un peu plus poussé la "difficulté", c'est que j'ai ajouté deux panneaux à mon conteneur (this). Vous devriez commencer à entrevoir ce que l'on va pouvoir faire en fusionnant plusieurs panneaux n'est-ce pas?

 

Il existe un conteneur proposé par le JDK qui vous permet de l'utiliser encore plus facilement ! Il s'agit de l'objet Box. Ce n'est rien d'autre qu'un panneau paramétré avec un BoxLayout. Voici comment l'utiliser pour avoir exactement le même rendu que ci-dessus :

Box p1 = Box.createHorizontalBox();		
for(int i = 0 ; i < 6 ; i++)
	p1.add(new JButton(String.valueOf(i)));

Box p2 = Box.createVerticalBox();
for(int i = 0 ; i < 6 ; i++)
	p2.add(new JButton(String.valueOf(i)));

 

Dans le prochain chapitre, nous verrons d'autres layouts plus complexes, mais avec ce que vous venez de voir, vous pouvez déjà couvrir une infinité de possibilités !

 

 

Résumé :

 

  • La disposition des composants est gérée par des layout managers.
  • Ces derniers imposent des contraintes à vos composants.
  • FlowLayout est le layout par défaut. Il permet la disposition la plus basique avec un retour à la ligne automatique.
  • BorderLayout permet une disposition simple et rapide par rapport aux bords du conteneur.
  • GridLayout permet de disposer vos composants en grille, toutes les cases ayant la même taille.
  • BoxLayout permet de disposer vos composants en ligne ou en colonne.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

Jackblue

blog-0415195001431880911.png

Chapitre 1 : Introduction & Workspace

 

Bonjour à tous !

 

Je commence dés aujourd'hui les cours sur "Comment développer avec une API ?" plus précisément sur une API Minecraft côté serveur tel que Bukkit, Spigot, Glowstone, BungeeCord, et bientôt Sponge. Ainsi maintenant le vendredi vous aurez soit un cours sur le Java proposé par JeremGamer, soit un cours sur le développement sous une API.

 

I - Introduction

 

Pour ce cours j'utiliserai l'API Spigot, qui est un fork de Bukkit, cela signifie qu'il est initialement conçu à partir de Bukkit, projet lancé par md_5, il permet de réduire significativement les lags avec un Bukkit optimisé et avec de nouveaux outils réseaux, une autre API existe pour complété cet aspect réseau, je veux bien sûr parler de BungeeCord, on aura peut être l’occasion de l'utiliser durant quelques hors séries. Voilà pour Spigot, si vous voulez d'avantages de renseignements, vous pouvez vous rendre sur cette page : http://www.spigotmc.org/wiki/about-spigot/

 

Sachez que l'API Forge reprend aussi pas mal des concepts présent sur ces API, bien que Forge soit plus complexe permettant une modification bien plus large, tout ça pour conclure sur le fait que ce cours bien que destiné pour les API Serveur, vous donnera tout de même une certaine base pour toutes les API de développement Minecraft présentes actuellement.

 

Voilà, j’espère via ce cours donner une partie de mon expérience sur le développement sous ces API :)

 

Qu'allons nous apprendre dans ce cours ?

 

Nous allons donc utiliser pour ce cours l'API Spigot, et voir pour utiliser la plupart des outils qu'ils comportent, au terme de ce cours vous saurez gérer des events, créer des commandes, utiliser une base de donnée, manipuler les objets principaux de minecraft, etc.. donc à faire ce plugin tant convoité !

 

Qu'es ce que j'ai besoin pour suivre ce cours ?

return
;

 

II - Création de sa Workspace

 

Depuis la 1.8, afin de pouvoir avoir Spigot il vous faudra télécharger le BuildTools et générer votre propre.jar, plus d'informations ici :

http://www.spigotmc.org/wiki/buildtools/

 

Je vous ai bien sûr déjà générer les fichiers en version 1.8.3 de minecraft :

Télécharger Spigot 1.8.3

 

Et pour ceux qui voudrait le générer, il vous suffit de télécharger le dernier build ici : https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar

De le mettre dans un nouveau dossier, puis installer si vous n'avez pas déjà git for windows (ou SourceTree ou encore Github for windows si vous pensez publier vos sources) afin d'avoir une console Git.

Lancer la Git Bash et grâce à cd afin de parcourir les dossiers et vous rendre dans votre nouveau dossier, une fois cela fait lancer :

java -jar BuildTools.jar

Ajouter --rev 1.8 et vous aurez le build Spigot en version 1.8.0, si vous ne mettez rien cela télécharge le dernier build à savoir 1.8.3 (actuellement).

Cela prend plusieurs minutes et une fois fini vous devriez voir dans votre dossier deux fichiers : spigot-1.8.jar et craftbukkit-1.8.jar

 

Lançons maintenant Eclipse, créer un nouveau projet Java dans votre Workspace, je vais personnellement l'appeler PizzaPlugin.

Clique droit sur le projet, Properties -> Java Build Path -> (Onglet) Librairies -> Add External JARs.. et vous allez ajouter le Spigot-1.8.3.jar téléchargé ou générer précédemment puis OK.

 

 

 

gKkuzkp.jpg

 

On retourne au Package Explorer (où il y a le dossier de votre projet) et vous allez créer un nouveau package (fr.<pseudo>.<nom_du_plugin> ou fr.<nom_equipe/entreprise>.<nom_du_plugin> par convention)

 

Ensuite créer une class dans ce package que l'on nomme Main ou <NomDuPlugin>Plugin, une fois créer vous aller l'extends JavaPlugin puis importer (avec la souris en passant par dessus ou en faisant CTRL+SHIFT+O, Eclipse s'occupe d'import tout ce qu'il faut ou/et enlève tout les imports non utilisé)

 

 

72Gfu1k.jpg

 

On va créer notre fichier plugin.yml, clique droit sur le projet Java, New -> File, puis mettez dans le champs File name, "plugin.yml"

A ce stade votre projet devrait ressemble à ça :

 

iARJKMv.jpg

On va remplir notre fichier plugin.yml avec ceci :

#Ceci est un commentairename: Pizzaversion: ersion indev #Je n'ai volontairement pas mis de v, attendez le résultat dans la console :3 description: Un plugin de livraison de pizza à domicile. #Pas obligatoire pour le bon fonctionnement de votre plugin, mais toujours bon de savoir ce qu'il fait :)author: Jackbluemain: fr.jackblue.pizza.PizzaPlugin #Cette ligne est très importante elle permet le lancement de votre plugin

Changez les champs pour que ça coïncide avec votre plugin, on verra d'autres attributs plus tard, mais sachez qu'il existe une page wiki en français sur ce fichier :

http://wiki.bukkit.org/Plugin_YAML/fr

 

Maintenant que vous avez fini de remplir votre plugin.yml, on a fini le déploiement de votre Workspace, il ne nous reste plus que le développement du plugin :D

On retourne donc vers notre class principale précédemment créer et on va y ajouter quelques petites choses :

package fr.jackblue.pizza;import java.util.logging.Logger;import org.bukkit.plugin.java.JavaPlugin;public class PizzaPlugin extends JavaPlugin{		Logger log;		@Override	public void onEnable(){		log = this.getLogger();		log.info("La pizza est prete !");	}		@Override	public void onDisable(){		log.info("Bob on a perdu la pizza :c");	}}

Donc je créer un objet Logger, on va l'utiliser pour informer le status du plugin dans la console et l'avantage qu'il a sur un simple System.out.println() , est que l'on pourra connaître d'où vient l'information très simplement, puisque dans la console l'instance sera précisé comme ceci :

[13:04:22 INFO]: [Pizza] Enabling Pizza version indev[13:04:22 INFO]: [Pizza] La pizza est prete !

Donc pensez bien à préciser le status de votre plugin via un Logger c'est très utile pour le debug que ça soit pendant le développement ou le support des utilisateurs qui utilisent votre plugin.

 

Ensuite on a deux methods fondamentales pour notre plugin, onEnable() et onDisable(), elles vont charger notre code respectivement au lancement et à l'arrêt du serveur.

On utilise la method info() de l'objet Logger puisque l'on cherche juste à informer, sachez qu'il en existe différentes comme warning() et severe() pour vous informer du pire...

[13:07:03 WARN]: [Pizza] La pizza commence a etre trop cuite :c[13:07:03 ERROR]: [Pizza] Le four risque d\'exploser dans..[13:07:03 ERROR]: [Pizza] 3[13:07:04 ERROR]: [Pizza] 2[13:07:05 ERROR]: [Pizza] 1[13:07:06 ERROR]: [Pizza] Done!

Pour le moment votre plugin ne fait qu'afficher du texte dans la console, on va passer à l'exportation voir si votre workspace est totalement opérationnelle !

 

Donc clique droit sur notre projet, Export... puis une fenêtre s'ouvre et va on sélectionner JAR File dans le dossier Java

 

 

vCvoN26.jpg

 

Puis on clique sur Next> , on décoche les cases pour .classpath et .project, qui contiennent des informations sur votre session et n'est pas nécessaire au bon fonctionnement de votre plugin. Enfin vous allez renseigner la location du Jar une fois compilé dans le champs texte Jar File, idéalement mettez le lien directement dans le dossier plugins de votre serveur spigot.

 

WOdunrf.jpg

 

On passe maintenant à l'étape le plus technique et complexe du cours, cliquer sur Finish .

Il ne vous reste plus qu'a lancer votre serveur avec votre plugin, et voir si tout fonctionne, dans le cas contraire regarder attentivement les logs, cela provient surement de votre plugin.yml .

Voilà c'est tout pour ce vendredi, vous avez maintenant une workspace totalement fonctionnelle, outil indispensable pour la suite !

 

Jackblue

SystemGlitch

blog-0508275001431879381.png

Dans cette seconde partie, nous allons découvrir un nouvel objet: Graphics2D. Il s'agit d'une amélioration de l'objet Graphics. Il nous permettra d'aller encore plus loin dans la création visuelle. Il est capable de nombreuses choses donc je ne vous parlerais que quelques une d'entre elles.

 

Mais avant ça, voyons comment le récupérer :

 
Graphics2D g2d = (Graphics2D) g.create();
//ou
Graphics2D g2d = (Graphics2D) g;

 

Nous allons commencer par peindre des dégradés. Pour cela nous aurons besoin d'un GradientPaint que nous appliquerons ensuite à notre Graphics2D.

Le principe est simple :

public void paintComponent(Graphics gr) {        
	Graphics2D g2d = (Graphics2D)gr;
	GradientPaint gp = new GradientPaint(0, 0, Color.GREEN, 30, 30, Color.BLUE, true);                
	g2d.setPaint(gp);
	g2d.fillRect(0, 0, this.getWidth(), this.getHeight());    
}

 

Voici le résultat :

 

4wMsAD1.png

 

Voici les arguments du constructeur de GradientPaint :

  • Position x où commence la première couleur
  • Position y où comment la première couleur
  • La première couleur
  • Position x où commence la seconde couleur
  • Position y où commence la seconde couleur
  • La seconde couleur
  • boolean définissant si le dégradé se répète ou non.

 

Ce que nous faisons ensuite, c'est que nous l'appliquons à notre Graphics2D, dans une forme, ici il s'agit d'un rectangle, mais on aurait pu dessiner ce dégradé dans un disque par exemple !

g2d.fillOval(getWidth() / 2 - 35, getHeight() / 2 - 35, 70, 70);

 

1phnbu0.png

 

Ne perdez pas d'esprit que les coordonnées du dégradés sont relatives au panneau sur lequel il est peint et non à sa forme !

 

Revenons à notre rectangle de base et voyons ce que ça donne en choisissant de ne pas répéter le dégradé :

 

arAZDaa.png

 

La seconde couleur sera peinte jusqu'au bout de la forme, pensez donc à adapter votre forme ! ;)

 

J'imagine que vous vous êtes rendus compte que le dégradé était oblique, cela est du à nos coordonnées. Regardez, si on fait partir les deux couleurs depuis y=0, on aura un dégradé vertical :

GradientPaint gp = new GradientPaint(0, 0, Color.GREEN, 30, 0, Color.BLUE, true);

 

b541iw9.png

 

Passons à autre aspect maintenant, en revenant à notre disque. Vous remarquerez que ce n'est pas joli joli et on constate que les courbes sont formées de telle sorte que des "escaliers" soient visibles. Nous n'avons pas appliqué d'anti-crénelage (antialiasing en anglais). Vous avez très certainement déjà rencontré ce phénomène qui est présent surtout dans les jeux vidéos, ces "escaliers" sont visibles sur les discontinuités entre deux objets, pour contrer cela, on utilise l'antialiasing.

En 3D, la technique la plus efficace, appelée "MSAA" (MultiSample AntiAlisasing), consiste à calculer l'image dans une résolution bien supérieure à celle de l'écran sur laquelle elle sera affichée puis la réduire ensuite afin de l'adapter au moniteur. Cette technique demande beaucoup de ressources cependant donc sur les gros jeux, il faudra une très bonne carte graphique pour pouvoir jouer fluidement avec ce paramètre.

 

Pour revenir à notre petite fenêtre sans prétention, la technique d'antialiasing que nous allons utiliser ici consiste en ajouter des pixels de la même couleur que la forme à dessiner, mais avec un certain niveau d'alpha, c'est à dire de transparence. Petite parenthèse d'ailleurs pour vous montrer comment créer votre propre couleur :

Color color = new Color(R , V , B , A); //Rouge, Vert, Bleu, Alpha (Alpha est optionnel)
//N'oubliez pas que les valeurs seront des entiers allant de 0 à 255!

 

Revenons à notre antialisasing. Voici donc comment il va fonctionner:

 

xQKj9i8.png

 

Vous voyez les pixels à moitié transparents autour de ce cercle? C'est bien de ceux-là que je vous parlais. ^_^ Si on réduit l'image un grand nombre de fois, vous verrez un cercle parfait, or il ne l'est pas vraiment comme vous pouvez le voir ici! Cependant sans ces pixels transparents, le cercle apparaitrait comme dans l'exemple un peu plus haut.

 

Trêve de bavardages, voici comment faire !

g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

 

Résultat :

 

dFnceqz.png

 

Faisons encore plus fou! ^_^ Nous allons faire ce que l'on appelle un "Clipping". C'est à dire que nous allons récupérer qu'une petite partie de l'image pour ensuite effectuer des traitements dessus. Ce qui est génial c'est que l'on est pas obligé de récupérer une zone rectangulaire, n'importe quelle forme fonctionne! Ici nous allons remplir un cercle dans la région dans laquelle il est en contact avec un rectangle :

Graphics2D g2d = (Graphics2D)gr;

g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

Shape oldClip = g2d.getClip(); //On récupère la région du rendu précédent

Rectangle rect = new Rectangle(getWidth() / 2 - 80, 10, 80, getHeight() - 20); //On créé un rectangle grâce aux classes héritant de Shape

Ellipse2D circle = new Ellipse2D.Double(getWidth() / 2 - 20, getHeight() / 2 - 20, 40, 40); //De même ici

AffineTransform tx = new AffineTransform();
GeneralPath path = new GeneralPath();
path.append(tx.createTransformedShape(rect), false); //On applique la trransformation au rectangle via le chemin général de la région

g2d.clip(circle);
g2d.clip(path);
		        
g2d.fill(circle);

g2d.setClip(oldClip);

g2d.draw(circle);
g2d.draw(path); //On dessine le chemin de la région

 

4kqrlLT.png

 

Vous avez constaté ici qu'on a réalisé une transformation affine. Bien d'autres choses sont possibles grâce à elles !

Par exemple il est possible d'effectuer une rotation ! Exemple :

Graphics2D g2d = (Graphics2D)gr;

g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//Toujours l'antialisasing pour faire plaisir à vos mirettes

Rectangle rect = new Rectangle(getWidth() / 2 - 60, 20, 60, getHeight() - 40);
AffineTransform tx = new AffineTransform();
tx.rotate(0.5f, rect.getCenterX(), rect.getCenterY()); //On effectue une rotation en prenant le centre du rectangle comme axe

g2d.draw(tx.createTransformedShape(rect)); //On dessine la forme transformée

 

I9T5to7.png

 

Et voila ! :)

 

Sachez également qu'il est possible de "changer de crayon" avec Graphics2D. Par exemple, vous pouvez changer la couleur, l'épaisseur du trait, faire en sorte que le trait ne soit pas plein, etc... Pour cela nous utiliserons BasicStroke.

BasicStroke stroke = new BasicStroke(2.0f, //Largeur du trait
				     BasicStroke.CAP_BUTT, //Contours des traits
				     BasicStroke.JOIN_MITER, //Coins reliés
				     10.0f, //Limite
				     new float[]{10.0f}, //Longueur du trait et des écarts, vous pouvez en mettre plusieurs, ils seront appliqués en alternance
				     0.0f); //Quand commence le premier trait

g2d.setStroke(stroke);
g2d.setColor(Color.RED);
g2d.drawRoundRect(10, 10, 80, 60, 15, 15);

 

usrpucg.png

 

Voyons un peu les variantes: il nous faudra faire varier le CAP et le JOIN.

 

 

 

CAP.ROUND : Les traits auront des contours arrondis : (j'ai grossis l'épaisseur des traits pour mieux s'en rendre compte)

 

1HX0wL8.png

 

 

 

CAP.SQUARE : Comme CAP.BUTT sauf que l'intégralité du traits aura des contours: (On le voit d'ailleurs au niveau des coins)

 

9eWEOrH.png

 

 

 

JOIN_BEVEL : Les coins seront aplatis : (C'est léger)

BasicStroke stroke = new BasicStroke(5.0f,
                 BasicStroke.CAP_BUTT,
                 BasicStroke.JOIN_BEVEL);

g2d.setStroke(stroke);
g2d.setColor(Color.RED);
g2d.drawRect(10, 10, 80, 60);

 

Aws39A9.png

 

 

 

JOIN_MITER : Les coins sont reliés proprement :

 

xExAdmG.png

 

 

 

JOIN_ROUND : Les coins seront arrondis (Léger également) :

 

gZyXrUS.png

 

 

 

Et pour finir ce chapitre, je vais vous apprendre à remplir une zone avec une texture !

 

Il n'y a rien de bien compliqué, pour commencer, récupérez votre image comme dans le chapitre précédent puis faites ceci :

TexturePaint texture = new TexturePaint(img, new Rectangle(0, 0, 60, 60)); //les autres formes fonctionnent aussi

g2d.setPaint(texture);
g2d.fillRect(0, 0, getWidth(), getHeight()); //Le rectangle dans lequel peindre

 

Faites bien attention à une chose, les valeurs données dans le Rectangle utilisé pour instancier notre TexturePaint seront celles utilisées pour la texture en elle-même et non pour sa forme, comme pour le dégradé ! Ici on dessinera la même texture mesurant 60*60 pixels sur toute la surface du panneau, elle sera donc dessinée plusieurs fois si la fenêtre est plus grande que 60*60 pixels.

 

9o8adXu.png

 

Génial n'est-ce pas? :D Vous allez pouvoir commencer à faire des choses intéressantes avec tout ça ! Cependant ne vous relâchez pas trop vite, de nouvelles perspectives vont s'ouvrir à vous dès le prochain chapitre ! ;) Je pense qu'un résumé n'est pas nécessaire pour ce chapitre sachant qu'il n'y a pas de réelles notions dedans. Amusez-vous bien avec vos nouvelles compétences en graphisme ! ^_^

 

 

JeremGamer

 

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0475605001431879370.png

Avant de commencer à créer et positionner des composants comme des boutons, nous allons voir comment faire un peu de graphisme 2D ! Cette notion peut-être importante pour la suite car elle met au jour le principe par lequel l'intégralité du contenu de vos panneaux est affichée.

 

Pour commencer, il va falloir commencer à vous habituer à travailler avec de nombreuses classes, et donc bien vous organiser avec les packages. Ce que je vous conseille, c'est une classe par grand panneau et par fenêtre. Vous ne vous y retrouverez jamais sinon. Quand je dis par grand panneau, je veux dire qu'il n'est pas nécessaire de créer une classe pour un panneau ne contenant que 2 ou 3 composants et n'ayant pas d'application vraiment particulière dans le programme. Nous travaillerons donc avec des objets de votre création héritant des objets que nous avons vu jusque là. Des exemples seront présents un peu partout dans ce chapitre et ceux qui suivront.

 

Sachez donc que vos panneaux sont tous repeints un nombre incalculable de fois, et de manière événementielle. Nous verrons bientôt en quoi cela est "événementiel". ;) Pour bien vous en faire prendre conscience, créons un petit programme avec une fenêtre et un panneau.

 
public class MainFrame extends JFrame {

	/**
	 * 
	 */
	private static final long serialVersionUID = -5897453038271365972L; //Les composants Swing sont sérialisables
	
	public MainFrame() {
		this.setSize(300 , 120);		
		this.setTitle("Ma première fenêtre");		
		this.setLocationRelativeTo(null);		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);	
		
		//Ici on a très peu de choses à changer donc on utilise l'annonymat
		JPanel content = new JPanel() {

			/**
			 * 
			 */
			private static final long serialVersionUID = -3587004117783902958L;
			private int n = 0;
			
			public void paintComponent(Graphics g) { //La méthode qui nous intéresse
				super.paintComponent(g);
				n++;
				System.out.println("Panneau actualisé " + n + " fois.");
			}
		};
		content.setBackground(Color.GREEN);
		
		this.setContentPane(content);
		this.setVisible(true);
	}

}

 

Si vous instanciez cette fenêtre, vous verrez que le panneau est très peu actualisé. Maintenant, redimensionnez la fenêtre.

Là vous pouvez voir que c'est assez impressionnant ! On atteint très rapidement les 1000 actualisations. Et encore, dites-vous qu'il n'y a aucun composant dans notre panneau ! Par exemple, vous voyez la barre clignotante dans les zones de saisie? Et bien le panneau est actualisé à chaque fois qu'elle apparait ou disparait, ainsi qu'à chaque fois qu'une lettre est ajoutée ou retirée.

Voila où je voulais en venir, votre panneau se repeint automatiquement. Vous aurez remarqué que je n'appelle jamais la méthode "paintComponent()" dans mon code, or cette dernière s'exécute quand même, et à chaque fois qu'une actualisation est nécessaire.

Enfin, la ligne la plus importante est celle où je fais appel à la super-méthode. Nous avons ici redéfini la méthode "paintComponent()", il est donc nécessaire de faire appel à la méthode de la super-classe pour que l'affichage se fasse correctement. Pourquoi? Simplement parce que c'est dans cette dernière que sont dessinés tous les composants et les éléments 2D. Sans ça, et bien vos composants n'apparaitraient pas! Si cela n'est pas clair, je vous invite à relire le cours sur l'héritage.

 

Pour peindre notre panneau, nous utiliserons non pas un pinceau mais l'objet Graphics, que l'on ne peut utiliser uniquement grâce à l'instance retournée par la méthode "getGraphics()" ou via le paramètre de la méthode "paintComponent()". (Cette méthode retournera null tant que la fenêtre n'aura pas été affichée au moins une fois avec "setVisible(true)")

 

Si vous voulez forcer l'actualisation de votre panneau, n’appelez pas "paintComponent()" mais "repaint()". ;)

 

Passons au moment que vous attendiez : nous allons enfin commencer à dessiner ! Par contre, ne vous attendez pas à un rendu digne d'un professionnel de Photoshop, nous n'allons dessiner que des formes basiques, puis nous verrons que l'on peut écrire du texte, ainsi qu'afficher des images !

 

C'est parti ! Tout va se passer dans la méthode "paintComponent()" de notre panneau. Nous allons aussi retirer la ligne pour mettre le fond en vert, histoire de préserver l'intégrité de nos petits yeux. ^_^

 

Commençons par dessiner une forme ovale pleine.

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.fillOval(getWidth() / 2 - 20, getHeight() / 2 - 20, 40, 40); //x , y , largeur , hauteur
	//On centre le cercle au milieu du panneau
	//On aura un disque vu que la largeur est égale à la hauteur
}

 

YAqZYSl.png

 

Pas mal non? Si vous redimensionnez la fenêtre, vous verrez que le disque reste bien centré. :) Gardez bien à l'esprit que les coordonnées sont croissante depuis le coin supérieur gauche du panneau, et que la coordonnée du disque est également celle du pixel supérieur gauche. C'est pour cela que quand je l'ai centré, j'ai bien récupéré la moitié de la taille du panneau, mais j'y ai également soustrait le rayon du disque. N'oubliez pas non plus que le pixel supérieur gauche du disque est celui du carré qui l'englobe !

 

hH2GRkK.png

 

Il est possible de dessiner des formes creuses, c'est à dire juste les contours. Pour cela, remplacez simplement "fill" par "draw" dans le nom de la méthode! Cela est valable pour les formes ovales mais aussi pour toutes les autres formes.

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.drawOval(getWidth() / 2 - 20, getHeight() / 2 - 20, 40, 40);
}

 

u3xJgMU.png

 

Je pense que tout est expliqué, maintenant je vais vous faire une petite liste de ce qui est possible de faire avec ce merveilleux objet !

 

 

Dessiner des rectangles :

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.fillRect(15, 15, 100, 50);
	g.drawRect(getWidth() - 115, 15, 100, 50);
}

 

M3hdEg2.png

 

 

 

Dessiner des rectangles avec des bords arrondis :

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.fillRoundRect(15, 15, 100, 50, 10, 10); //Les deux derniers arguments définissent la taille de l'arc arrondi
	g.drawRoundRect(getWidth() - 115, 15, 100, 50, 10, 10);
}

 

grMrjkh.png

 

 

 

Dessiner des lignes :

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.drawLine(0, 0, getWidth(), getHeight()); //x , y départ puis x , y arrivée
	g.drawLine(0, getHeight(), getWidth(), 0);
	g.drawLine(0, getHeight() / 2, getWidth() , getHeight() / 2);
	g.drawLine(getWidth() / 2 , 0 , getWidth() / 2 , getHeight());
}

 

HaCQ9cX.png

 

 

 

Dessiner à partir d'un tableau de bytes :

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.drawBytes("Bukkit.fr".getBytes(), 0, "Bukkit.fr".getBytes().length, 20, 20);
}

 

NdZ1YOM.png

 

 

 

Dessiner un polygone :

private int x[] = {20, 30, 50, 60, 60, 50, 30, 20};
private int y[] = {30, 20, 20, 30, 50, 60, 60, 50};

public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.fillPolygon(x, y, 8); //Le dernier argument est le nombre de points
}

 

5akcRz8.png

 

 

Et pour le plaisir, générons un polygone aléatoire ! Il se modifiera à chaque actualisation :

private Random rand = new Random();

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	int x[] = {rand.nextInt((getWidth() - 0) + 1) , rand.nextInt((getWidth() - 0) + 1) , rand.nextInt((getWidth() - 0) + 1)};
        int y[] = {rand.nextInt((getHeight() - 0) + 1) , rand.nextInt((getHeight() - 0) + 1) , rand.nextInt((getHeight() - 0) + 1)};
	g.fillPolygon(x, y, 3); //Un triangle aléatoire sera dessiné
}

 

NCEPcnC.png

 

 

 

Dessiner un arc de cercle :

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.drawArc(20, 20, 100, 100, 0, 90); //x , y , largeur , hauteur , angle de début , angle de fin
}

 

bUgZ48m.png

 

 

 

Écrire du texte :

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.drawString("Vive Bukkit.fr", 20, getHeight() / 2); //Texte , x , y
}

 

FDeIW2r.png

 

 

 

C'est bien joli tout ça, mais si on est limité à dessine en gris, on ne va pas aller très loin... Voila donc comment faire pour personnaliser un peu tout ça !

public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.setFont(new Font("English157 BT" , Font.BOLD, 40)); //Nom de la police, style, taille
	g.setColor(Color.BLUE);
	g.drawString("Vive Bukkit.fr", 20, getHeight() / 2);
}

 

rirIgId.png

 

Nous sommes limité à cela avec l'objet Graphics, mais sachez que dans le prochain chapitre, nous verrons qu'il est possible d'aller bien plus loin, notamment choisir l'épaisseur des traits ou même faire des dégradés de couleur!

 

Il reste une dernière chose à vous montrer: comment afficher une image. B)

private BufferedImage img = ImageIO.read(new File("Java-duke-guitar.png"));
                
public void paintComponent(Graphics g) {
        super.paintComponent(g);
        g.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}

 

N'oubliez pas d'englober tout ça avec un bloc try/catch IOException ;)

 

1wQZTds.png

 

Petit problème: Notre image est déformée... C'est logique vu qu'on lui a demandé de s'adapter à la taille de la fenêtre !

Si on avait une fenêtre de la même taille que l'image, ça donnerait ça :

 

B24Ipuy.png

 

Faites bien attention à ça! Alors voici quelques astuces :

 

Pour que l'image reste toujours à la bonne taille, faites comme suit :

private BufferedImage img = ImageIO.read(new File("Java-duke-guitar.png"));
				
public void paintComponent(Graphics g) {
	super.paintComponent(g);
	g.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null);
}

 

C'est tout pour aujourd'hui ! Ce chapitre est déjà bien plus long que d'habitude ! ^_^

En guise de code cadeau, je vais vous donner une petite astuce pour centrer votre image. Ainsi elle ne sera jamais déformée et sera toujours bien centrée, même en cas de redimensionnement ! B)

 

(J'ai mis le fond du panneau en noir pour mieux voir les bords de l'image)

private BufferedImage img = ImageIO.read(new File("Java-duke-guitar.png"));
				
public void paintComponent(Graphics g) {
	super.paintComponent(g);
	int imgHeight = img.getHeight() * getWidth() / img.getWidth();
	int bHeight = (this.getHeight() - imgHeight) / 2;
	g.drawImage(img , 0 , bHeight, getWidth() , imgHeight , null);
}

 

tVcpr1q.png

 

Et comme je suis d'une générosité légendaire aujourd'hui, voici un autre code cadeau. Il sert à optimiser une image afin de l'afficher plus rapidement sur votre GUI, et donc optimiser votre programme. Car pour une raison inconnue, les images mettent assez longtemps à être traitées.

private BufferedImage optimizeImage(BufferedImage img) {
		GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();                    
		GraphicsConfiguration gc = gd.getDefaultConfiguration();

		boolean istransparent = img.getColorModel().hasAlpha();

		BufferedImage img2 = gc.createCompatibleImage(img.getWidth(), img.getHeight(), istransparent ? Transparency.BITMASK : Transparency.OPAQUE);
		Graphics2D g = img2.createGraphics();
		g.drawImage(img, 0, 0, null);
		g.dispose();

		return img2;
}

 

Utilisez-le ainsi :

BufferedImage img = optimizeImage(ImageIO.read(file)); //En variable d'instance.

 

Vous savez quoi faire par la suite ! ;)

 

Résumé :

 

  • Les panneaux sont repeints automatiquement et à chaque fois qu'ils ont besoin de l'être.
  • Ils sont donc repeints de manière événementielle.
  • L'objet Graphics nous permet de dessiner sur notre panneau.
  • Cet objet n'est utilisable que via l'instance retournée avec la méthode "getGraphics()".
  • Les modifications se font dans la méthode redéfinie "paintComponent(Graphics g)".
  • N'oubliez pas de faire appel à la super-méthode sinon les autres composants ne s'afficheront pas!
  • Vous pouvez dessiner toutes sortes de formes grâce à Graphics.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0148374001431878857.png

Nous entamons une toute nouvelle grande partie de notre cours: vous allez désormais apprendre, tout au long de nombreux chapitres, à créer des GUI ("Graphical User Interfaces")! On appelle cela IHM en français, pour "Interface Humain-Machine".

 

Vos logiciels vont enfin ressembler à quelque chose d'autre qu'un rectangle noir tout moche, rempli de texte et dont la navigation s'effectue uniquement via des commandes. ^_^

 

Le JDK nous propose deux packages, javax.swing et java.awt, que vous apprendrez à utiliser pendant ces chapitres. Cependant, certaines parties du cours seront dédiées à quelques conseils et techniques en lien avec les interfaces graphiques.

 

Je vous conseille de bien vous accrocher, car à partir de maintenant, vous serez confrontés à un nombre de lignes de code bien plus élevé que jusqu’à présent, la programmation de GUI demandant beaucoup d'instructions. Vous verrez rapidement pourquoi. ;)

 

Passons aux choses sérieuses! Nous allons créer notre toute première fenêtre! Je vous conseille de créer un nouveau projet. Créez votre classe principale contenant la méthode principale et insérez-y ceci :

 
public static void main(String[] args) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //On termine le processus quand l'utilisateur ferme la fenêtre
		frame.setVisible(true);
}

 

Lancez ce code et constatez :

 

jxCuPeT.png

 

Votre première fenêtre ! La voila ! Cependant, vous conviendrez qu'elle ne ressemble pas à grand chose... En effet, cette dernière est toute petite, tout simplement parce qu'on ne lui a pas demandé de faire autrement! Il va falloir lui dire absolument tout ce que l'on veut qu'elle fasse! Et cela sera valable pour absolument tous nos composants! Vous comprenez maintenant pourquoi le nombre de ligne de vos programmes va rapidement augmenter. :P

 

Vous remarquerez aussi que votre fenêtre est apparue dans le coin supérieur gauche de votre écran. Et oui, vous ne lui avez pas non plus dit qu'elle devait apparaitre ou milieu de l'écran! J'aimerais d'ailleurs ouvrir une petite parenthèse: vous savez qu'un écran est composé de pixels, créant ainsi une gigantesque grille dans laquelle il est possible de "naviguer" via des coordonnées. On aura alors deux valeurs de positions x et y. Jusque là tout va bien. Ce dont vous ne vous doutez surement pas, c'est que contrairement aux repères en mathématiques, les coordonnées croissent ainsi :

 

l5uxmFh.png

 

Ici notre fenêtre mesure 500 pixels par 500 pixels. La coordonnée du pixel tout en bas à droite de le fenêtre sera alors x = 500 et y = 500 et celui tout en haut à gauche x = 0 et y = 0. Ce système est valable pour toutes les interfaces graphiques, que ce soit en Java ou pas !

 

Fermons la parenthèse et revenons à notre fenêtre. Vous remarquerez que j'ai instancié un objet du type JFrame. Nous allons travailler dessus tout au long de ce chapitre. Pour commencer, vous constaterez également que j'ai fait appel à la méthode setVisible(true) afin de faire apparaitre la fenêtre. Sans ça, votre fenêtre ne s'ouvrira jamais ! Vous pouvez à tout moment cacher ou afficher votre fenêtre grâce à cette méthode, ce n'est pas parce que l'on ne la voit pas qu'elle n'existe pas !

 

Embellissons un peu tout ça !

JFrame frame = new JFrame();
	
frame.setSize(500 , 500); //Taille de la fenêtre en pixels (largeur , hauteur)
		
frame.setTitle("Ma première fenêtre"); //Définition du titre
	
frame.setLocationRelativeTo(null); //On positione la fenêtre au centre de l'écran
//Cette méthode peut aussi permettre de positionner la fenêtre par rapport à une autre fenêtre
		
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //On termine le processus quand l'utilisateur ferme la fenêtre
//Sans ça, le processus du programme continue à tourner dans le vide et à consommer des ressources!

frame.setVisible(true);

 

Voila qui est mieux! Sachez qu'il existe beaucoup d'autres options. Les plus utiles sont détaillées ci-dessous :

  • setAlwaysOnTop(boolean) défini si la fenêtre restera quoi qu'il arrive au premier plan, devant toutes les autres fenêtres (y compris celles des autres logiciels)
  • setLocation(int x , int y) positionne la fenêtre sur l'écran aux coordonnées choisies. Sachez que les valeurs négatives fonctionnent et que les coordonnées choisies seront celles du pixel supérieur gauche de la fenêtre.
  • setMaximumSize(new Dimension(int largeur , int hauteur) défini la taille maximale de la fenêtre quand l'utilisateur essaiera de la redimensionner. N'appelez pas cette méthode si vous ne souhaitez pas définir de limite.
  • setMinimumSize(new Dimension(int largeur , int hauteur) fait de même mais pour la taille minimum. Je vous conseille de toujours le spécifier.
  • setResizable(boolean) permet d'autoriser ou de bloquer le redimensionnement de la fenêtre par l'utilisateur. Vous pourrez toujours la modifier avec setSize(int largeur , int hauteur).
  • setUndecorated(boolean) sur true, les bordures de la fenêtre, c'est à dire celles où se trouvent la croix rouge pour fermer la fenêtre, où se trouve son titre, son icône, etc... seront retirées.

 

C'est bien gentil d'avoir une fenêtre, mais si on ne peut rien mettre dedans, il n'y a aucun intérêt. Nous allons maintenant nous intéresser au contenu de notre fenêtre !

 

Avant de passer à la suite, il est important de savoir qu'une fenêtre n'est rien d'autre qu'un empilement de composants ayant chacun leur rôle. Notre fenêtre "vide" est en fait déjà constituée de multiples couches superposées :

 

ui-rootPane.gif

 

  • Root Pane: Objet JRootPane, c'est le conteneur principal qui contiendra tous les autres composants. Comme son nom l'indique: il s'agit de la "racine".
  • Layered Pane: Objet JLayeredPane, il est une troisième dimension: la profondeur, quand vos composants se chevauchent. Il s'agit également d'un panneau qui contient le Content Pane et la barre de menu (JMenuBar), quand il y en a une.
  • Content Pane: Il s'agit d'un panneau qui contient l'essentiel de votre fenêtre. C'est ici que nous placerons nos composants
  • Glass Pane: Objet GlassPane. Couche supérieure transparente utilisée pour afficher certaines choses au dessus des autres composants, comme des boites de dialogue internes. Elle est aussi utilisée pour intercepter les actions de l'utilisateur avant que ces dernières parviennent jusqu'aux composants.

 

Sachez qu'il existe deux autres types de fenêtres: les JWindow, non redimensionnables, non déplaçables et sans bordures, pratiques pour faire des splash screens (vous savez, comme la fenêtre sans bordure que vous pouvez voir en lançant Eclipse et dans laquelle vous pouvez voir l'avancement du chargement de l'IDE), et les JDialog, destinées à être des boites de dialogue, demandant quelque chose à l'utilisateur. Elles figent le reste du programme tant qu'elles sont ouvertes.

 

Je vous présente une classe qui deviendra rapidement votre plus grand ami : j'ai nommé JPanel ! Il s'agit d'un conteneur, pouvant contenir d'autres conteneurs ainsi que des composants tels que des boutons, des champs de saisie, des cases à cocher, etc...

 

Commençons par instancier notre panneau. Ensuite nous allons modifier sa couleur de fond. D'ailleurs, sachez que vous pouvez changer la couleur de fond de n'importe quel composant de la même manière, avec la même méthode. ;)

Et pour finir, nous allons le définir en tant que ContentPane de notre fenêtre.

JFrame frame = new JFrame();
frame.setSize(300 , 120);		
frame.setTitle("Ma première fenêtre");		
frame.setLocationRelativeTo(null);		
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);	
	
JPanel content = new JPanel();
content.setBackground(Color.GREEN);
		
frame.setContentPane(content);
frame.setVisible(true);

 

DZQA4WW.png

 

Voila ! Certes il y a peu de changements, mais à partir de là nous allons pouvoir commencer à faire des choses intéressantes ! Mais ça, ce sera la prochaine fois ! ;)

 

Il y avait longtemps que je ne vous avais pas fait part d'un code cadeau. ^_^ Le code suivant vous permet de rendre une JFrame sans bordures draggable, c'est à dire que l'on peut la déplacer sur l'écran. Vous avez peut-être essayé de faire une fenêtre sans bordures tout à l'heure, sachez qu'il est impossible pour l'utilisateur de la déplacer, car la bande supérieure où il clique d'habitude pour le faire... n'est pas là ! -_- Voici donc une classe qui permet de créer une telle fenêtre :

package fr.bukkit.jeremgamer.cours;

import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class DemoFrame extends JFrame {
	
	private int pX , pY;
	
	public DemoFrame() {
		
		this.setSize(300 , 120);				
		this.setLocationRelativeTo(null);		
		this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		this.setUndecorated(true);
		
		JPanel content = new JPanel();
		content.setBackground(Color.BLUE);
		
		this.setContentPane(content);
		
		this.addMouseListener(new MouseAdapter() {

			public void mousePressed(MouseEvent me) {
				pX = me.getX();
				pY = me.getY();
			}

			public void mouseDragged(MouseEvent me) {
				setLocation(getLocation().x + me.getX() - pX, getLocation().y + me.getY() - pY);
			}
		});

		this.addMouseMotionListener(new MouseMotionAdapter() {
			public void mouseDragged(MouseEvent me) {
				setLocation(getLocation().x + me.getX() - pX, getLocation().y + me.getY() - pY);
			}
		});
	}
}

 

Pensez à appeler setVisible(true) après avoir instancié DemoFrame. ;)

 

Résumé :

 

  • Les GUI (Graphical User Interface) sont des interfaces graphiques pour l'utilisateur.
  • On utilise les packages javax.swing et java.awt.
  • Les coordonnées démarrent du coin supérieur gauche de l'affichage.
  • Souvenez-vous du nom des JFrame.
  • Une JFrame est composée de multiples couches.
  • Les JPanel sont des conteneurs.
  • Ils contiendront les composants que nous ajouterons à notre fenêtre.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0205500001431879071.png

Ce hors-série sera dédié à l'utilisation de votre IDE, mais aussi d'un autre logiciel. Nous allons voir comment exporter votre projet et le rendre utilisable par n'importe qui, qui aurait Java installé sur sa machine.

 

Vous allez me dire que ce chapitre sera extrêmement court. Certes, mais je ne vais pas simplement vous expliquer qu'en trois clics votre programme sera compilé et utilisable! Nous verrons également comment générer des fichiers exécutable (".exe") pour Windows.

 

Commençons par l'export "classique". Nous allons compiler notre code, ainsi il ne sera plus lisible par l'humain mais par la machine. Avec Eclipse, rien de plus simple! Cliquez-droit sur votre projet et choisissez "Export". Dans la boite de dialogue qui s'ouvrira, ouvrez "Java", sélectionnez "Runnable Jar File" et cliquez sur "Next".

 

1JbA6nO.png

 

Vérifiez bien que "Launch Configuration" est bien réglé sur la configuration de votre programme, puis choisissez l'emplacement pour l'export. Dans "Library handling", je vous conseille de cocher "Extract required librairies into generated JAR". Cliquez sur "Finish". Voila! Votre programme est compilé!

Si votre programme a une interface graphique, rien d'autre à faire, cependant nous n'avons pas encore vu comment faire, mais n'ayez crainte, ça ne saurait tarder ;)

Cependant, si votre programme tourne sur console, comme nous le faisons jusque là, il y a une étape en plus.

Rendez-vous dans le dossier où se trouve le jar que vous venez d'exporter.

Si vous tentez de lancer votre programme, vous constaterez que rien ne se passe. Nous allons devoir l'ouvrir grâce à un petit script variant en fonction de votre système d'exploitation.

 

Windows :

 

Créez un fichier texte dans le même dossier que votre jar et modifiez l'extension de telle sorte à ce que votre fichier soit un fichier "bat" tel que "Run.bat".

Éditez ensuite ce fichier et écrivez-y les lignes suivantes :

@echo off
java -jar NomDuJar.jar
PAUSE

 

Linux :

 

Procédez de la même manière, mais avec un fichier en ".sh". Écrivez-y ceci :

#!/bin/bash
cd -P $(dirname $0)
java -jar ./NomDuJar.jar

 

Puis dans un terminal, tapez la commande suivante pour lancer votre programme :

cd 'chemin du dossier'

//puis

chmod +x NomDuJar.jar run.sh

 

MacOS X :

 

Créez également un fichier tel que son extension soit ".command" et autorisez son script à se lancer.

Insérez-y ceci :

cd "$( dirname "$0" )"
java -jar ./NomDuJar.jar

 

Ouvrez L'application Terminal et tapez "chmod +x " (n'oubliez pas l'espace à la fin) puis glissez le fichier que vous venez de créer dans le Terminal. Appuyez sur "Entrée" et fermez le Terminal. Vous n'avez plus qu'a double-cliquer sur votre fichier en ".command" pour lancer votre programme!

 

Voyons désormais comment créer des fichier exécutables pour Windows! Cela vous plaira surement! Sachez tout de même que cela ne fonctionnera qu'avec des programmes dotés d'une interface graphique. Commencez par compiler votre programme comme expliqué ci-dessus.

Nous aurons ensuite besoin du logiciel Launch4J que vous pouvez télécharger ici. Une fois le logiciel installé, lancez-le.

 

V8gDnru.png

 

De nombreuses options s'offrent à vous. Nous allons voir pas-à-pas les plus importantes.

 

Tout d'abord, à la première ligne, sélectionnez le chemin où sera créé votre fichier exécutable. A la seconde ligne, spécifiez l'emplacement du jar de votre programme.

La prochaine ligne qui nous intéressera sera la ligne "Icon" qui nous permettra de définir une icône pour notre fichier. Votre icône devra obligatoirement être un fichier ".ico" d'une taille de 64 par 64 pixels.

 

Rendez-vous maintenant dans l'onglet "Version Info". Je pense que vous n'avez pas besoin de mon aide pour remplir les champs proposés. Cette partie n'est pas obligatoire mais il est préférable de la remplir afin de renseigner au mieux les futurs utilisateurs.

 

Je vous laisse explorer par vous-même les autres options. ;)

 

Cliquez sur la disquette dans la barre en haut de la fenêtre pour sauvegarder votre configuration puis cliquez sur l'engrenage situé juste à côté afin de générer votre exécutable. Vérifiez que tout c'est bien passé dans la console en bas de la fenêtre.

Si tout c'est bien passé, alors vous aurez créé votre premier ".exe" !

 

 

JeremGamer

 

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0880720001431878567.png

Dans ce chapitre vous allez apprendre à récupérer dynamiquement de nombreuses informations à propos d'une classe. Ainsi vous pourrez exploiter ces données avec votre programme, sans avoir besoin de les entrer manuellement de telle sorte qu'a ce que si elles changent, vous récupérerez toujours l'intégralité des données qui vous intéressent. On appelle ce concept la réflexivité, ou l'introspection.

 

Lors du chargement d'une classe par la JVM, cette dernière instancie un objet Class qui récupérera automatiquement toutes les informations à propos de votre classe.

 

Il existe deux manières de récupérer un objet Class :

 
Class cls = MaClasse.class;
Class cls2 = obj.getClass(); //obj étant un objet instancié!

 

Si vous avez bien écouté jusque là, vous devinerez que la méthode getClass() provient évidemment de la classe Object ! ;)

 

Autre petite remarque : il est recommandé d'utiliser la généricité avec l'objet Class :

Class<MaClasse> cls = MaClasse.class;

 

A titre d'exemple, reprenons une fois de plus les classes de mon exemple global (jeu vidéo médiéval). Prenons le Chevalier :

package fr.bukkit.jeremgamer.cours;

public class Chevalier extends Personne {

	int i = 42;
	
	public Chevalier() {
		super();
		System.out.println("Cette personne est un chevalier!");
	}
	
	public void combattre() {
		System.out.println("Les têtes vont rouler!");
	}
	
	@Override
	public void methodeCommune() {
		super.methodeCommune();
		System.out.println("i chevalier = " + this.i);
		System.out.println("i personne = " + super.i);
	}
	
}

 

Voyons désormais ce que nous pouvons faire de tout cela! Il y a de très nombreuses possibilités avec ce concept. Vous constaterez qu'il existe aussi les objets Package, Annotation, Constructor, Field, Method, etc... qui résument tout ce que nous avons vu jusque là! Je ne vais pas vous montrer toutes les possibilités car elles sont vraiment trop nombreuses. De plus je pense que vous savez désormais fouiller par vous-même afin de trouver toutes les méthodes ;)

Class<Chevalier> cls = Chevalier.class;
System.out.println("Nom de la classe: " + cls.getSimpleName()); //getName() renverra le chemin cannonique vers la classe (nom des packages) 
System.out.println("Nom de sa super-classe: " + cls.getSuperclass().getSimpleName());
System.out.println("Package: " + cls.getPackage().getName());
System.out.println("Cette classe est une annotation: " + cls.isAnnotation());		
System.out.println("Modificateur de portée: " + cls.getModifiers()); //Je vous invite à jeter un œil à la documentation de cette méthode
		
System.out.println("------------------------------");

System.out.println("Interfaces implémentées: ");
for(Class<?> i : cls.getInterfaces())
	System.out.println(i.toString());

System.out.println("------------------------------");
		
System.out.println("Champs déclarés: ");
for(Field f : cls.getDeclaredFields())
	System.out.println(f.toString());

System.out.println("------------------------------");

System.out.println("Méthodes déclarées: ");
for(Method m : cls.getDeclaredMethods())
	System.out.println(m.toString());
		
System.out.println("------------------------------");
		
System.out.println("Constructeurs déclarés: ");
for(Constructor<?> c : cls.getDeclaredConstructors())
	System.out.println(c.toString());
		
System.out.println("------------------------------");
		
System.out.println("Annotations déclarées: ");
for(Annotation a : cls.getDeclaredAnnotations())
	System.out.println(a.toString());

 

On obtiendra le résultat suivant :

Nom de la classe: Chevalier
Nom de sa super-classe: Personne
Package: fr.bukkit.jeremgamer.cours
Cette classe est une annotation: false
Modificateur de portée: 1
------------------------------
Interfaces implémentées: 
------------------------------
Champs déclarés: 
int fr.bukkit.jeremgamer.cours.Chevalier.i
------------------------------
Méthodes déclarées: 
public void fr.bukkit.jeremgamer.cours.Chevalier.combattre()
public void fr.bukkit.jeremgamer.cours.Chevalier.methodeCommune()
------------------------------
Constructeurs déclarés: 
public fr.bukkit.jeremgamer.cours.Chevalier()
------------------------------
Annotations déclarées: 

 

Vous voyez que l'on peut récupérer un nombre impressionnant d'informations à propos de n'importe quelle classe! Et encore, comme je l'ai dit plus haut, la liste n'est ici pas exhaustive!

 

Voyons maintenant si votre curiosité vous aura poussé vers la méthode newInstance() ! Grâce à elle, il est possible d'instancier une classe sans l'opérateur new !

try {
			
	Class cls = Class.forName(Chevalier.class.getName()); //Il est important d'utiliser getName() ici
	Chevalier chevalier = (Chevalier) cls.newInstance(); //Quand il n'y a pas d'arguments à donner
			
	chevalier.combattre();
} catch (ClassNotFoundException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
}

 

Avec cette méthode, le constructeur est appelé comme si on utilisait new. Ajoutons maintenant un constructeur avec paramètres à notre classe Chevalier afin de voir comment procéder avec des paramètres :

public Chevalier(String nom , Integer age) {
		this.nom = nom;
		this.age = age;
}
try {

	Class<?> cls = Class.forName(Chevalier.class.getName()); //Il est important d'utiliser getName() ici

	//On récupère le constructeur ayant les paramètres de type suivants
	Constructor<?> ct = cls.getConstructor(new Class[]{String.class , Integer.class});   
	Chevalier chevalier = (Chevalier) ct.newInstance(new Object[]{"Thomas", 25});
	chevalier.combattre();

	//Ou, pour éviter un cast, on peut appeler les méthodes ainsi:
	Method m = cls.getMethod("combattre");
	m.invoke(chevalier);
	//Method m2 = cls.getMethod(name, parameterTypes) Pour les méthodes avec argument, de la même manière que pour la récupération du constructeur
	//m2.invoke(chevalier, args);
} catch (ClassNotFoundException e) {
	e.printStackTrace();
} catch (InstantiationException e) {
	e.printStackTrace();
} catch (IllegalAccessException e) {
	e.printStackTrace();
} catch (IllegalArgumentException e) {
	e.printStackTrace();
} catch (InvocationTargetException e) {
	e.printStackTrace();
} catch (NoSuchMethodException e) {
	e.printStackTrace();
} catch (SecurityException e) {
	e.printStackTrace();
} //Cela fait beaucoup d'exceptions à gérer n'est-ce pas !

 

Je pense qu'un résumé n'est pas nécessaire pour ce chapitre, souvenez-vous juste du nom de ce concept : la réflexivité! Elle vous permet donc de récupérer absolument toutes les informations concernant n'importe quelle classe et d'instancier un objet sans l'opérateur new, ainsi que d'appeler des méthodes indirectement.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0351902001431877420.png

Ce chapitre s'inscrit dans la continuité du précédent et sera essentiellement constitué de morceaux de code. Vous connaissez déjà la majorité de ce qu'il y a à savoir pour les notions qui viennent. ;)

 

Commençons par l'écriture d'objets dans un fichier. On appelle l'action de sauvegarder des objets la "serialization" (en anglais).

Il est très intéressant de pouvoir sauvegarder un objet afin de pouvoir le réutiliser plus tard, c'est possible ! Cependant il y a quelques pré-requis.

 

Pour commencer, la classe de l'objet doit implémenter une interface marqueur nommée Serializable. Son utilité est d'indiquer à la JVM que l'objet est sérialisable. Notez bien que toutes les classes ayant une super classe implémentant cette interface seront également sérialisables. Ainsi vous pouvez plus facilement créer une architecture pratique pour vos logiciels!

 

Passons à la pratique ! Votre objet doit avoir un constructeur dont les paramètres définiront tous les champs de votre objet.

 
package fr.bukkit.jeremgamer.cours;

import java.io.Serializable;

public class SavedObject implements Serializable {

    /**
     *
     */
    private static final long serialVersionUID = -7319756719270519466L; //Explications ci-dessous
    
    private String id;
    private int i;
    
    public SavedObject(String id , int i) {
        this.id = id;
        this.i = i;
    }
    
    public String toString() {
        return "ID: " + id + " || i = " + i;
    }

}

 

Voici un objet serialisable ! :)

Petite parenthèse sur le serialVersionUID: cet identifiant unique peut-être généré par Eclipse. La JVM associe un nombre de version à chaque objet serialisable. Ce dernier est utilisé lors de la déserialisation pour vérifier la compatibilité entre la source et le récepteur. Il n'est pas obligatoire, mais fortement recommandé.

 

Revenons à nos flux. Instancions deux objets SavedObject et écrivons-les dans un fichier :

File file = new File("test.txt"); //Vous pouvez choisir l'extension de votre choix

ObjectOutputStream oos;
BufferedOutputStream bos;
FileOutputStream fos;

try {

	if(!file.exists())
		file.createNewFile();
	fos = new FileOutputStream(file);
	bos = new BufferedOutputStream(fos);
	oos = new ObjectOutputStream(bos);

	oos.writeObject(new SavedObject("Objet n°1" , 42));
	oos.writeObject(new SavedObject("Objet n°2" , 7));
	oos.close();

	System.out.println("Sauvegarde effectuée!");

} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} 

 

Et maintenant lisons le fichier sauvegardé :

File file = new File("test.txt");

ObjectInputStream ois;
BufferedInputStream bis;
FileInputStream fis;

try {

	if(!file.exists())
    	file.createNewFile();
	fis = new FileInputStream(file);
	bis = new BufferedInputStream(fis);
	ois = new ObjectInputStream(bis);
            
	SavedObject object1 = (SavedObject) ois.readObject();
	SavedObject object2 = (SavedObject) ois.readObject();
            
	ois.close();
            
	System.out.println(object1.toString());
	System.out.println(object2.toString());

} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}  

 

On obtiendra le résultat suivant :

ID: Objet n°1 || i = 42
ID: Objet n°2 || i = 7

 

Que ce passerait-t-il si votre objet à désérialiser contenait un autre objet de votre création?

package fr.bukkit.jeremgamer.cours;

import java.io.Serializable;

public class SavedObject implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = -7319756719270519466L;
	
	private String id;
	private int i;
	private Personne personne;
	
	public SavedObject(String id , int i, Personne personne) {
		this.id = id;
		this.i = i;
		this.personne = personne;
	}
	
	public String toString() {
		return "ID: " + id + " || i = " + i + " || Personne: " + personne.toString();
	}

}

 

Si nous re-sauvegardons nos deux objets et que nous les relisons, une erreur sera levée ! Il s'agira d'une NotSerializableException car la classe Personne n'implémente pas Serializable.

Deux solutions :

  • Faire implémenter Serializable à votre classe Personne
  • Déclarer votre variable de telle sorte à ce qu'elle indique à la JVM qu'elle n'a pas besoin d'être sérialisée. Auquel cas, ses champs ne seront pas sauvegardés et la variable sera tout simplement ignorée. Elle sera alors égale à null si on la laisse comme dans l'exemple ci-dessus. Cela se fait grâce au mot-clef transient.
    private transient Personne personne;

     

Passons aux flux vous permettant de manipuler des archives, zip notamment.

Conservons le fichier généré précédemment. Nous allons le compresser dans une archive zip ! Pour cela nous utiliserons pour la première fois dans ce cours des méthodes et objets externes au JDK ! Téléchargez cette API (choisissez les binaries) et suivez les instructions ci-dessous:

  • Décompressez le contenu de l'archive téléchargée à l'emplacement de votre choix.
  • Retournez sur Eclipse et faites un clic droit sur votre projet.
  • Cliquez sur "Properties" et choisissez la section "Java Build Path".
  • Cliquez sur "Add external JARs" et allez chercher le fichier "commons-compress-x.x.x.jar" se trouvant dans le dossier que vous venez de décompresser.
  • Répétez ces instructions pour cette API.

 

Nous voila fins prêts! La seconde API n'était pas essentielle mais elle nous facilitera beaucoup la tâche alors pourquoi s'en priver? ^_^

 

Chaque fichier destiné à être compressé est une entrée. Il faut bien faire attention à la gestion des dossiers également. La deuxième API intervient là, elle nous permettra de tout copier très simplement, que ce soit un fichier ou un dossier et des sous-dossiers, à l'aide d'un simple appel de méthode ! :P

File zip = new File("test.zip");
File file = new File("test.txt");

ArchiveOutputStream zipOutput = null;
OutputStream out = null;

try {
			
	out = new FileOutputStream(zip);
	zipOutput = new ZipArchiveOutputStream(out);

	//((ZipArchiveOutputStream) zipOutput).setLevel(ZipArchiveOutputStream.STORED); Sera stocké sans être compressé
	((ZipArchiveOutputStream) zipOutput).setLevel(9); //Compression maximum pour cette API

	ZipArchiveEntry entry = new ZipArchiveEntry(file.getName());
	entry.setSize(file.length());
	zipOutput.putArchiveEntry(entry);
	IOUtils.copy(new FileInputStream(file.getAbsolutePath()), zipOutput); //Méthode de la seconde API
	zipOutput.closeArchiveEntry();  
	zipOutput.finish();  
			
	System.out.println("Compression terminée!");
			
} catch (IOException e) {
	e.printStackTrace();
} finally {
	try {
		zipOutput.close();  
		out.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

 

Une fois compressé, mon fichier pèse 117 octets contre 129 de base. C'est négligeable, mais sur de plus gros volumes, cela devient assez intéressant ! Je vous invite à essayer ! Désormais, essayons dans l'autre sens.

File zip = new File("test.zip");

ArchiveInputStream zipInput = null;
InputStream in = null;

ObjectInputStream ois = null;
BufferedInputStream bis = null;

try {

	in = new FileInputStream(zip);
	zipInput = new ZipArchiveInputStream(in);

	ZipFile zipFile = new ZipFile("test.zip");

	//On sait que l'archive ne contient qu'un fichier, sinon on utiliserais une boucle pour tout récupérer
	InputStream is = zipFile.getInputStream(zipFile.getEntry("test.txt")); //On connait aussi le nom du fichier
	zipFile.close();
	bis = new BufferedInputStream(is); 
	ois = new ObjectInputStream(bis);

	SavedObject object1 = (SavedObject) ois.readObject(); //De même pour ici
	SavedObject object2 = (SavedObject) ois.readObject(); //On connait le contenu du fichier donc on peut se permettre de ne pas penser aux variantes

	System.out.println(object1.toString());
	System.out.println(object2.toString());

} catch (IOException | ClassNotFoundException e) {
	e.printStackTrace();
} finally {
	try {
		zipInput.close();  
		ois.close();
		bis.close();
		in.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

 

Le résultat est le même que précédemment: (J'ai retiré la variable "personne")

ID: Objet n°1 || i = 42
ID: Objet n°2 || i = 7

 

 

Résumé :

 

  • L'action de sauvegarder un fichier s'appelle serialization.
  • On les effectuera grâce à des ObjectOutputStream.
  • Les objets à sérialiser doivent obligatoirement implémenter Serializable.
  • Souvenez-vous de comment importer une API (librairie) à votre projet.
  • Vous pouvez écrire et lire des archives Zip grâce aux ZipOutputStream/ZipFile de l'API Compress de Apache Commons

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0184467001431877365.png

Ce chapitre s'avèrera complexe, mais il vous plaira surement ! ;) En effet, vous apprendrez aujourd'hui à utiliser des flux d'entrées et de sortie afin de pouvoir permettre à votre programme d'échanger des données avec une autre source. Cela comprend également la lecture et l'écriture de fichiers ! On utilisera donc un certain nombre de streams (flux en anglais) qui englobent le processus d'envoi et de réception de données. L'API Java nous propose de nombreux types de flux différents selon les utilisations et le type de données à manipuler. Ces flux fonctionnent toujours de manière séquentielle, c'est à dire que l'on devra commencer par les ouvrir, pour ensuite effectuer les traitements d'informations, puis finir par les fermer. Souvenez-vous, nous en avons déjà très brièvement parlé dans le chapitre sur la communication avec l'utilisateur. ;) Il est très important de fermer le flux après son utilisation pour éviter des corruptions de données ou une concurrence entre plusieurs sources sur l'utilisation des données en question.

 

Tous ces flux sont divisés en deux grandes catégories :

 

  • Les flux d'entrée (input stream)
  • Les flux de sortie (output stream)

 

Toutes la classes concernées sont localisées dans le package java.io. Rien de plus logique car IO est tout simplement l'acronyme de In/Out (entrée/sortie) !

Dans le chapitre sur la communication avec l'utilisateur, nous avons vu un flux d'entrée uniquement. La source était l'utilisateur, et ce dernier envoyait des informations au programme par l'intermédiaire de son clavier et de ses petits doigts, mais aussi par le Scanner sans le savoir !

 

Les flux fonctionnent à l'aide de bytes, il ne s'agit pas d'un échange d'objets direct! Ainsi nous aurons un buffer (tampon) qui définira le nombre de bytes par paquet. Il prendra la forme d'un tableau de byte dans notre code. Nous procéderons alors à l'aide de boucles. Le buffer étant quelque chose de très complexe lié à de nombreux paramètres tels que le type de processeur, le type de système de stockage et bien d'autres choses, nous n'allons pas nous attarder là dessus. Je vous invite cependant à faire quelques recherches sur le sujet afin d'en savoir plus ! :) Ici nous allons travailler avec un petit fichier texte donc une taille de buffer de 64 suffira amplement. (Pour la manipulation de plus gros fichiers, ou de fichiers de taille inconnue, je vous conseille un buffer d'une taille de 4096 ou 8192 bytes)

 

Il existe aussi des classes chargées d’interpréter et de faire des traitements sur les données du flux. Elles doivent être liées à un flux de lecture "classique" et fonctionnent un peu comme des filtres. Par exemple, dans le monde des Strings, qui est bien plus compliqué qu'on pourrait le croire, ces flux seront très pratiques, surtout afin d'interpréter les caractères spéciaux, qui seront mal restitués à l'aide de flux "classiques". Ces données seront également mises en tampon, elles s'appellent donc de la manière suivante: Buffered + Type de lecture. Je recommande fortement l'utilisation de ces flux car ils sont bien plus rapides et optimisés et surtout, beaucoup plus simples à utiliser! :P Attention à ne pas vous embrouiller avec tous ces buffers!

 

Avant de vous donner un exemple, intéressons-nous à une classe... intéressante ! ^_^

Il s'agit de la classe File. Elle nous permettra en quelque sorte de "restituer" un fichier dans notre programme sous forme d'un objet. Je vous invite à créer un fichier texte en suivant les instructions suivantes:

  • Cliquez-droit sur votre projet
  • Sélectionnez "New -> File"
  • Nommez-le "test.txt" (N'oubliez pas l'extension!)
  • Eclipse vous ouvrira un nouvel onglet afin de l'éditer. Insérez-y les lignes suivantes :
    Je suis un Bukkitien!
    Vive Bukkit.fr!
    Java c'est la vie!

     

 

Votre fichier est donc à la racine de votre projet, dans le dossier courant de l'instance de votre programme quand vous le lancer pour faire des tests grâce à Eclipse. Nous allons maintenant voir ce que nous pouvons tirer d'un fichier sans même utiliser de flux.

File file = new File("test.txt");

//Pour récupérer des informations :
System.out.println("Le fichier existe : " + file.exists());
System.out.println("Nom du fichier    : " + file.getName());
System.out.println("Chemin du fichier : " + file.getPath());
System.out.println("Chemin absolu     : " + file.getAbsolutePath());
System.out.println("Droit de lecture  : " + file.canRead());
System.out.println("Droit d'ecriture  : " + file.canWrite());
System.out.println("Poids du fichier  : " + file.length());
		
//Le fichier est dans le dossier courant donc on doit faire une petite manip afin de trouver le chemin du dossier
//Sinon on utilisera tout simplement file.getParentFile();
File parent = new File(file.getAbsolutePath().substring(0 , file.getAbsolutePath().length() - "\test.txt".length()));
System.out.println("Liste des fichiers présents dans le même dossier : ");
for(File f : parent.listFiles()) {
	String type;
	if(f.isFile()) //On vérifie s'il s'agit d'un fichier ou d'un dossier
		type = "fichier";
	else
		type = "dossier";
	System.out.println(f.getName() + " : " + type);
}
		
//Pour effectuer certaines actions sur notre fichier :
try {
	file.createNewFile(); //Créera le fichier (vierge) ou l'écrasera s'il existe déjà. Pensez à bien faire la vérification avant!
} catch (IOException e) {
	e.printStackTrace();
} 
file.delete(); //Supprime le fichier
file.deleteOnExit(); //Supprime le fichier lors de la fermeture du programme
file.mkdirs(); //Créera les dossiers ou se trouve le fichier s'ils n'existent pas. Pensez également à le faire pour éviter les erreurs

 

Afin de dissiper tout doute possible, je tiens à préciser que les objets File peuvent également être des dossiers. Qu'ils soient des fichiers ou des dossiers, il peuvent aussi ne pas exister physiquement sur le disque, il est donc important de faire les vérifications qui s'imposent.

 

Voici enfin un exemple: nous allons lire fichier que nous venons de créer! Prenez bien votre temps pour le lire, il n'est pas évident à comprendre mais résume ce que je vous ai dis jusque là !

File file = new File("test.txt");
FileInputStream fis = null; //On déclare le flux de lecture de fichier
byte[] buffer = new byte[64]; //On créé notre tampon
int n = 0; //Servira à l'affectation du résultat de la lecture. Egal à -1 quand la lecture est finie
		
try {
	fis = new FileInputStream(file); //On instancie le flux dans un bloc try/catch
	while((n = fis.read(buffer)) >= 0) //Tant que le résultat de la méthode read ne renvoie pas -1, on continue la lecture
               System.out.print(new String(buffer));
} catch (FileNotFoundException e) { //Levée si le fichier n'existe pas ou n'a pas été trouvé
	e.printStackTrace();
} catch (IOException e) { //Levée si une erreur de lecture ou d'écriture survient
	e.printStackTrace();
} finally { //On s'assure de bien fermer le flux après utilisation, même en cas d'erreur!
	if(fis != null) {
		try {
			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

On obtient bien le résultat suivant :

Je suis un Bukkitien!
Vive Bukkit.fr!
Java c'est la vie!

 

Essayons maintenant de copier notre fichier par le même procédé :

File file = new File("test.txt");
File file2 = new File("test2.txt");
FileInputStream fis = null; //On déclare le flux de lecture de fichier
FileOutputStream fos = null; //On déclare le flux d'écriture de fichier
byte[] buffer = new byte[64]; //On créé notre tampon
int n = 0; //Servira à l'affectation du résultat de la lecture. Egal à -1 quand la lecture est finie
		
try {
	if(!file2.exists())
		file2.createNewFile(); //On créé le fichier
			
	fis = new FileInputStream(file); //On instancie les flux dans un bloc try/catch
	fos = new FileOutputStream(file2);
			
	while((n = fis.read(buffer)) >= 0) //Tant que le résultat de la méthode read ne renvoie pas -1, on continue la lecture
		fos.write(buffer); //On écrit le contenu du buffer dans le nouveau fichier via le flux d'écriture
			
	System.out.println("Copie terminée!");
			
} catch (FileNotFoundException e) { //Levée si le fichier n'existe pas ou n'a pas été trouvé
	e.printStackTrace();
} catch (IOException e) { //Levée si une erreur de lecture ou d'écriture survient
	e.printStackTrace();
} finally { //On s'assure de bien fermer le flux après utilisation, même en cas d'erreur!
	if(fis != null) {
		try {
			fis.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

 

Maintenant que nous avons vu comment faire avec les flux classiques, voyons comment vous simplifier la vie avec les flux "Buffered". Voici un code faisant exactement la même chose que le précédent, mais avec cette technique :

File file = new File("test.txt");
File file2 = new File("test2.txt");

FileReader fr = null;
BufferedReader br = null;
FileWriter fw = null;
BufferedWriter bw = null;
try {
	fr = new FileReader(file.getAbsoluteFile());
	br = new BufferedReader(fr);
	fw = new FileWriter(file2.getAbsoluteFile());
	bw = new BufferedWriter(fw);
		
	if (!file2.exists()) 
		file2.createNewFile();
		
	String line = br.readLine(); //On lit la première ligne
	while (line != null) { //On vérifie que la ligne n'est pas vide
		System.out.println(line);
		bw.write(line);
		bw.newLine();
		line = br.readLine();
	}
	bw.flush();
	System.out.println("Copie terminée!");
} catch (FileNotFoundException fnfe) {
	fnfe.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} finally { //On ferme les flux quoi qu'il arrive
	try {
		br.close();
		fr.close();
		bw.close();
		fw.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

 

Ici pas besoin de s'embêter avec la taille de votre buffer, tout est bien plus simple !

Sachez que si le fichier existe déjà, son contenu sera intégralement supprimé avant la réécriture.

 

Il existe un autre avantage : ces classes possèdent de nombreuses méthodes permettant de récupérer ou d'écrire facilement tout type primitifs, ainsi que des Strings. Il faut cependant s'y prendre un petit peu différemment en passant par des DataStreams.

FileInputStream fis = null;
BufferedInputStream bis = null;
DataInputStream dis = null;

FileOutputStream fos = null;
BufferedOutputStream bos = null;
DataOutputStream dos = null;


try {
	if (!file.exists()) 
		file.createNewFile();
			
	fos = new FileOutputStream(file.getAbsoluteFile());
	bos = new BufferedOutputStream(fos);
	dos = new DataOutputStream(bos);

	dos.writeBoolean(true);
	dos.writeByte(42);
	dos.writeChar('J');
	dos.writeDouble(32.64);
	dos.writeFloat(111.11f);
	dos.writeInt(5094);
	dos.writeLong(6549894161568L);
	dos.writeShort(3);
	dos.close();

	fis = new FileInputStream(file.getAbsoluteFile());
	bis = new BufferedInputStream(fis);
	dis = new DataInputStream(bis);

	System.out.println(dis.readBoolean());
	System.out.println(dis.readByte());
	System.out.println(dis.readChar());
	System.out.println(dis.readDouble());
	System.out.println(dis.readFloat());
	System.out.println(dis.readInt());
	System.out.println(dis.readLong());
	System.out.println(dis.readShort());
			
} catch (FileNotFoundException e) {
	e.printStackTrace();
} catch (IOException e) {
	e.printStackTrace();
} finally {
	try {
		dis.close();
		dos.close();
	} catch (IOException e) {
		e.printStackTrace();
	}
}

 

C'est tout pour aujourd'hui à propos des flux! Vous savez désormais écrire et lire dans un fichier! Dans la seconde partie de ce chapitre, nous verrons des flux permettant d'écrire ou de lire des objets, de compresser des fichiers dans une archive zip, etc... :)

 

Résumé:

  • Les flux d'entrée et de sortie permettent à votre logiciel d'échanger des données avec une source externe.
  • Ils fonctionnent avec des bytes uniquement, qui seront ensuite utilisés et interprétés pour obtenir le type de donnée voulu.
  • Un buffer (tampon) est un "paquet" d'informations sous forme d'un tableau de byte.
  • La classe File permet de "modéliser" un fichier ou un dossier, existant ou non, sous forme d'objet dans votre programme.
  • Les flux doivent être ouverts avant de traiter les informations, puis fermés une fois l'utilisation terminée.
  • La fermeture du flux se fera dans le bloc finally afin de s'assurer que cela se fasse correctement.
  • Les flux "Buffered" font office de filtre et traitent les informations automatiquement.
  • Ils sont beaucoup plus rapides et optimisés.
  • Les DataStreams vous permettent de faire appel à plusieurs méthodes destinées à tous les types primitifs ainsi qu'aux Strings.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png

SystemGlitch

blog-0376934001431876807.png

Dans ce chapitre, vous allez apprendre à vous passer des classes abstraites quand vous serez confrontés à certains cas. Par exemple, quand vous voulez utiliser différentes variables statiques afin de savoir ce que doit faire votre code.

 

Prenons l'exemple suivant :

 
public static final int ACTION_1 = 0;
public static final int ACTION_2 = 1;

public static void main(String[] args) {
	execution(ACTION_1);
	execution(ACTION_2);
}
	
public static void execution(int action) {
	if(action == ACTION_1)
		System.out.println("Action n°1");
	else if(action == ACTION_2)
		System.out.println("Action n°2");
}

 

Ainsi des paramètres prédéfinis sont disponibles pour effectuer les actions de différentes manières avec la même méthode. Cependant, il y a une grosse faiblesse à ce système: si vous entrez un paramètre non prédéfini, rien ne se passera car aucun comportement n'a été prévu, ce qui fausse le fonctionnement de la méthode. Pour remédier à cela, il existe les Énumérations !

Ce principe introduit depuis Java 5 vous permet d'énumérer (oui sans blague ! ^_^ ) une série de données ayant un type personnalisé et sûr, ce qui prévient l'utilisation de Cast et la génération de ClassCastException.

En réalité, les types et les constantes de ces données ne sont pas déclarés, c'est pour cela qu'elles sont sûres! Vous aurez donc compris que l'on ne pourra pas instancier les énumérations, et que l'on accèdera à ses différentes valeurs de la même manière que lorsqu'on souhaite récupérer une constante statique.

 

Avant d'éclaircir tous ces points, voyons un peu comment se déclare une énumération :

enum Sucrerie {
	GATEAU ,
	CREPE ,
	GLACE ,
	BONBON ,
	PATISSERIE;
}

 

On aura ici 5 "objets" héritant des méthodes de Object, comme n'importe quel autre objet en Java, mais aussi à quelques méthode spécifiques supplémentaires qui seront très bientôt détaillées. Pas besoin de déclarer de modificateur de portée ou de déclaration de type, ils s'utiliseront comme des variables statiques publiques.

 

Au niveau des méthodes concernant les énumérations, voici deux petites choses à savoir:

  • La méthode values() retournera toutes les valeurs de l'énumération, sous forme d'un tableau. On pourra donc l'utiliser ainsi :
    for(Sucrerie s : Sucrerie.values()) {
    	//...
    }
  • L'utilisation de toString() sur un objet d'énumération renverra le nom avec lequel on l'a déclaré dans l'énumération. Par exemple, si on faisait Sucrerie.GLACE.toString(), cela renverrait "GLACE".

 

Les énumérations peuvent, comme des classes abstraites, déclarer des méthodes et constructeurs. Ces champs prendront effet pour les objets de l'énumération et non pour l'énumération elle-même !

enum Sucrerie {
	GATEAU ("Gâteau"), //Entre parenthèses, on donne l'argument pour le constructeur ci-dessous
	CREPE ("Crêpe"),
	GLACE ("Glace"),
	BONBON ("Bonbon"),
	PATISSERIE ("Pâtisserie");
		
	private String name = ""; //Valable pour TOUS les objets ci-dessus!
		
	Sucrerie(String name) { //Constructeur des objets ci-dessus
		this.name = name; //S'appliquera donc pour tous les objets déclarés
	}
		
	public String toString() { //On redéfini la méthode pour renvoyer un nom plus convenable
		return name;
	}
}

 

Il est interdit d'ajouter de modificateur au constructeur, tout simplement car celui-ci sera toujours considéré comme privé, afin d'empêcher la modification des valeurs données lors de la déclaration des objets.

Ainsi, si nous revoyons le premier exemple et que nous remplaçons le type du paramètre par "Sucrerie", l'utilisation de valeur non déclarée dans l'énumération sera alors impossible !

 

Avant de passer à un exemple plus complet vous montrant ce dont sont capables les énumérations, voyons un peu quelles sont les fameuses méthodes spécifiques dont je vous ai parlé plus haut: (Je rappelle que ces méthodes peuvent être appelées depuis les objets de l'énumération et non depuis l'énumération elle-même!)

  • ordinal() renvoie un int spécifiant l'index de l'objet en question, par ordre de déclaration. Ici si on appelle cette méthode depuis CREPE, on aura 1 car il est déclaré en deuxième.
  • name() renverra la même chose que toString(), à savoir le nom donné à l'objet lors de sa déclaration.

 

Et pour finir, voici deux méthodes à appeler directement depuis l'énumération :

  • valueOf(String) renvoie l'objet dont le nom est égal à celui spécifié en argument. Si l'objet n'existe pas, cela génèrera une IllegalArgumentException.
  • La méthode values() renvoie un tableau, c'est à dire que vous pouvez l'utiliser comme tel :
    Sucrerie.values()[1]; //Renverra CREPE

     

 

Comme promis, voici un exemple un peu plus étoffé :

package fr.bukkit.jeremgamer.cours;

import java.util.Random;

public class Cours {


    enum Sucrerie {
        GATEAU ("Gâteau" , 5 , "Un délicieux gâteau au chocolat fondant."),
        CREPE ("Crêpe" , 2 , "Un bonne crêpe comme on les aime!"),
        GLACE ("Glace" , 1 , "Très rafraichissant!"),
        BONBON ("Bonbon" , 1 , "Pour répondre à vos petits caprices sucrés."),
        PATISSERIE ("Pâtisserie" , 2 , "Idéal pour un petit déjeuner savoureux!");
        
        private String name = "";
        private int price = 0;
        private String description = "";
        
        Sucrerie(String name , int price , String description) {
            this.name = name;
            this.price = price;
            this.description  = description;
        }
        
        public String toString() {
            return name;
        }
        
        public int getPrice() {
            return price;
        }
        
        public String getDescription() {
            return description;
        }
        
        public void setDescription(String description) {
            this.description = description;
        }
        
        public void setPrice(int price) {
            this.price = price;
        }
    }

    public static void main(String[] args) {
        Random rand = new Random();
        int nombre = rand.nextInt((Sucrerie.values().length-1 - 0) + 1) + 0;
        execute(Sucrerie.values()[nombre]);
    }

    private static void execute(Sucrerie sucrerie) {
        switch(sucrerie) { //Il est possible d'utiliser les switch pour les énumérations
        case GATEAU:
            System.out.println("Je mange un délicieux gâteau!");
            break;
        case CREPE:
            System.out.println("Miam! Une crêpe!");
            break;
        case GLACE:
            System.out.println("Une bonne glace bien rafraichissante!");
            break;
        case BONBON:
            System.out.println("Un petit caprice sucré.");
            break;
        case PATISSERIE:
            System.out.println("Rien de tel que de démarrer la journée avec une bonne patisserie!");
            break;
        }
    }
}

 

 

Résumé :

 

  • Les énumérations permettent la créations de données sûres et personnalisées.
  • Il est impossible d'instancier une énumération ou un objet énuméré.
  • Il est possible d'ajouter des constructeurs, ils s'appliqueront aux objets énumérés et non à l'énumération!
  • Dans ce cas, il faudra spécifier les arguments nécessaires lors de la déclaration de l'objet.
  • Il est également possible d'ajouter des méthodes, qui s'appliqueront également aux objets énumérés, ou de redéfinir les méthodes dont ils héritent.

 

 

JeremGamer

 

2cp1ED5.pngtNIpRtq.png