Version en ligne

Tutoriel : Créez des applications pour BlackBerry

Table des matières

Créez des applications pour BlackBerry
Un petit mot sur BlackBerry
Introduction
Les applications
Le développement
Installation des outils de développement
Installer le Java SE JDK
Eclipse et son plugin pour BlackBerry
L'interface d'Eclipse
Votre première application
Création du projet
Analyse du contenu
Lancement de l'application
La constitution des vues
Introduction
Les classes fondamentales
Les différentes techniques
Les composants de base
Les champs de texte
Les boutons
Les images
Un peu d'ordre avec les conteneurs
Le principe des conteneurs
Les différents conteneurs
Un album photo
La gestion des évènements
Les évènements de boutons
Les évènements « tactiles »
Un peu plus loin...
Les vues personnalisées
La structure de base
Insérer des contours
Insérer des remplissages
TP : un taquin
Le cahier des charges
La correction
Les explications
Travailler avec plusieurs vues
Préparation des vues
Mise à jour d'un écran
Travailler par héritage
L'internationalisation
Créer des ressources
Utiliser les ressources
Une application multilingue
Le stockage de données
Gérer des données
Accéder « physiquement » aux données
Lire et écrire dans un fichier
TP : un éditeur de texte
Le cahier des charges
La correction
Les explications
Le multimédia
Transférer des fichiers
Jouer de l'audio
Lire une vidéo
L'accéléromètre
L'orientation « grossière »
Gérer le temps
L'orientation « précise »
Les pages HTML
Afficher des pages HTML
Installer le BlackBerry MDS Simulator
Charger des pages web
Tester son application en profondeur
Préparer son application
Utiliser de nouveaux simulateurs
Utiliser un appareil physique
Déployer et distribuer
Installer des clés de signature
Finaliser son application
La distribution

Créez des applications pour BlackBerry

Vous avez envie d'apprendre à développer des applications pour BlackBerry ?
Mais vous ne trouvez aucun cours qui vous explique comment procéder ?
Bienvenue dans un cours qui va justement vous apprendre à développer pas à pas des applications pour BlackBerry OS !
Vous remarquerez qu'il est relativement facile de se procurer des ouvrages et tutoriels qui traitent du développement d'applications pour iPhone ou smartphones Android. En revanche sous BlackBerry OS, il est beaucoup plus difficile de trouver des cours à la fois complets et progressifs. Dans ce cours, nous essaierons donc de progresser petit à petit pour vous permettre de réaliser les applications de vos rêves.

À qui ce cours s'adresse-t-il ?

Tout au long du tutoriel, nous essaierons de mettre en pratique les diverses notions qui auront été mises en évidence. Nous essaierons au cours de ces travaux pratiques de réaliser des applications intéressantes et variées. Je vous laisse découvrir le genre d'applications que nous développerons dans ce cours :
Image utilisateurImage utilisateurImage utilisateur
Je vous invite donc sans plus attendre, à vous lancer dans cet apprentissage !

Un petit mot sur BlackBerry

Introduction

Dans ce chapitre, nous ferons une brève introduction à l'univers BlackBerry. Nous parlerons des smartphones du même nom ainsi que de la compagnie qui en est à l'origine. Nous verrons également les différents moyens de développement d'applications pour leur système d'exploitation BlackBerry OS. Dès le prochain chapitre, nous mettrons en place les outils nécessaires au développement de ces applications.
Je rappelle également que la lecture de ce cours suppose que vous avez déjà quelques connaissances en programmation. C'est pourquoi nous ne reviendrons pas sur des notions de base de l'informatique telles que la définition d'un système d'exploitation ou encore sur la signification d'un langage de programmation.

Introduction

Un petit mot sur BlackBerry Les applications

Introduction

Image utilisateur
Image utilisateur
Image utilisateur

BlackBerry est à la base une ligne de smartphones développée par la société canadienne Research In Motion (RIM). Cette compagnie a été fondée en 1984 par Mike Lazaridis. Son siège social est à Waterloo en Ontario mais la firme possède des bureaux en Europe, en Asie et en Amérique du Nord. Elle est à l'heure actuelle composée de plus de 13 000 collaborateurs pour un chiffre d'affaires d'environ 16 milliards de $ en 2011. C'est en 1999 qu'elle a commercialisé le premier BlackBerry. Pour plus d'informations, consultez leur site web à l'adresse www.rim.com.

Les smartphones BlackBerry sont réputés pour leur service de messages et courriels en temps réel. Ces téléphones utilisent le système d'exploitation propriétaire BlackBerry OS. Il existe de nos jours, près d'une vingtaine de modèles différents de smartphones BlackBerry, et environ 50 millions d'utilisateurs à travers le monde.
Depuis peu, la compagnie canadienne a développé sa première tablette nommée BlackBerry PlayBook. Néanmoins, cette tablette ne fonctionne pas sur le même système d'exploitation que les smartphones. Celui-ci est nommé BlackBerry Tablet OS.


Un petit mot sur BlackBerry Les applications

Les applications

Introduction Le développement

Les applications

Dans ce cours nous apprendrons pas à pas, à réaliser des applications fonctionnant sous BlackBerry OS. Mais avant de se lancer dans plus de détails sur la manière de les développer, nous verrons un peu ce qu'il est possible de réaliser.

Une application c'est quoi ?

Pour appréhender la notion d'applications, nous allons faire une analogie avec les ordinateurs. Windows 7, par exemple, est un système d'exploitation. Celui-ci possède beaucoup de fonctionnalités intégrées, mais il n'est pas rare qu'on ait besoin d'encore plus. Pour cela, nous installons des programmes supplémentaires pour augmenter les fonctionnalités de notre ordinateur.
Voici une liste de programmes ou logiciels :

Le système d'exploitation BlackBerry fonctionne sur le même principe. Cependant ici on va parler d'applications. On retrouvera des applications diverses qui servent à un tas de trucs. Voici une liste d'applications :

Quelques applications sont présentées dans le tableau ci-dessous :

Navigateur Web

Monopoly

Fruit Ninja

Image utilisateurImage utilisateurImage utilisateurImage utilisateurImage utilisateurImage utilisateur

Les outils mis à disposition par RIM nous permettent de créer des applications de toutes sortes. Nous pourrons y intégrer de belles interfaces en utilisant des composants proposés (boutons, menus, etc.), mais également des interfaces graphiques entièrement personnalisées avec des images ou encore utiliser la 3D. Nous aurons aussi tous les outils nécessaires pour par exemple écrire des fichiers dans le terminal ou encore avoir accès aux services de localisation.


Introduction Le développement

Le développement

Les applications Installation des outils de développement

Le développement

La compagnie Research In Motion propose tout un tas d'outils pour réaliser vos applications sur BlackBerry OS et BlackBerry Tablet OS. Le développement d'applications peut être réalisé dans différents langages de programmation. Voici tout ce que la société nous propose :

Image utilisateur

Voici les possibilités de développement :

En ce qui concerne BlackBerry OS, le développement peut être réalisé uniquement en Java ou en HTML5. Dans notre cas, nous programmerons nos applications en Java tout au long du cours. Dès le prochain chapitre, nous commencerons à nous équiper des outils nécessaires au développement BlackBerry en Java.


Les applications Installation des outils de développement

Installation des outils de développement

Le développement Installer le Java SE JDK

Pour réaliser des applications pour BlackBerry, nous aurons besoin de différents outils. L'Environnement de Développement Intégré (ou IDE) conseillé pour le développement d'application BlackBerry en Java est Eclipse. Cet environnement de développement est libre, gratuit et multi-plateforme. Nous installerons donc le Plugin Java BlackBerry pour Eclipse.
Dans ce chapitre, nous détaillerons toutes les étapes d'installation pas à pas pour ne perdre personne. Une fois terminé, nous aurons alors tout ce qu'il faut pour écrire nos applications, les compiler et les tester à travers un simulateur. Pour ceux qui ne connaîtraient pas l'environnement de développement, nous présenterons rapidement son interface.
Les outils de développement Java BlackBerry fournis par RIM sont malheureusement disponibles uniquement sous Windows et Mac.

Installer le Java SE JDK

Installation des outils de développement Eclipse et son plugin pour BlackBerry

Installer le Java SE JDK

Téléchargement du JDK

Les programmes écrits en Java ont la particularité d'être portables, c'est-à-dire qu'ils peuvent fonctionner sur différents systèmes d'exploitation. Ceci vient du fait que les programmes Java sont compilés en Byte Code et vont s'exécuter sur une machine virtuelle. Cette machine virtuelle est appelée JRE (ou Java Runtime Environment). Nous en aurons besoin car Eclipse est lui-même principalement écrit en Java. Étant donné que les applications BlackBerry sont écrites en Java, nous aurons aussi besoin du JDK (ou Java Development Kit) qui intègre les outils pour développer et compiler du Java. Notez que le JRE est inclus dans le JDK.

Pour télécharger la dernière version du JDK, rendez-vous sur le site d'Oracle à la page suivante. Cliquez alors sur le bouton Download du JDK pour passer à l'étape suivante.

Image utilisateur

Avant de lancer le téléchargement vous devrez accepter la licence en cliquant sur la case à cocher Accept License Agreement, et spécifier votre système d'exploitation dans la liste proposée. Enfin, sélectionnez l'emplacement et enregistrez le fichier d'installation sur votre disque dur.

Installation du JDK

Pour lancer l'installation, double-cliquez sur le fichier d'installation téléchargé précédemment, puis suivez les étapes ci-dessous. Pour ma part, j'ai créé un dossier BlackBerry, qui contiendra toutes les installations que nous allons réaliser. Je vous conseille vivement de faire la même chose pour éviter d'en semer de partout.

Étape 1 sur 4 : Cliquez sur Next.
Étape 1 sur 4 : Cliquez sur Next.
Étape 2 sur 4 : Spécifiez le dossier d'installation et cliquez sur Next.
Étape 2 sur 4 : Spécifiez le dossier d'installation et cliquez sur Next.
Étape 3 sur 4 : Patientez pendant la durée d'installation.
Étape 3 sur 4 : Patientez pendant la durée d'installation.
Étape 4 sur 4 : Terminez l'installation en cliquant sur Continue.
Étape 4 sur 4 : Terminez l'installation en cliquant sur Continue.

Le JDK est maintenant installé. Nous allons pouvoir installer notre environnement de développement, ainsi que le plugin Java BlackBerry.


Installation des outils de développement Eclipse et son plugin pour BlackBerry

Eclipse et son plugin pour BlackBerry

Installer le Java SE JDK L'interface d'Eclipse

Eclipse et son plugin pour BlackBerry

Eclipse est un IDE qui a été développé pour faciliter l'ajout de plugins. Il est à la base configuré pour écrire des programmes en Java, mais il est possible de lui ajouter des modules d'extension pour apporter de nouvelles fonctionnalités au logiciel. Il faut alors télécharger individuellement les plugins désirés et les installer.
Le développement d'applications BlackBerry sous Eclipse IDE nécessite l'ajout d'un plugin. Cependant, Research In Motion propose un kit d'installation qui intègre déjà l'IDE, le plugin ainsi que toutes les bibliothèques de classes. Grâce à lui, une seule installation est suffisante pour avoir un IDE configuré et prêt à l'emploi.

Téléchargement du plugin

Pour télécharger Eclipse et son plugin Java BlackBerry intégré, nous devons nous rendre sur leur site à l'adresse www.blackberry.com/developers/java, puis suivre les étapes suivantes :

Étape 1 sur 2 : Dans la rubrique Development tools and downloads, sélectionnez votre système d'exploitation.
Étape 1 sur 2 : Dans la rubrique Development tools and downloads, sélectionnez votre système d'exploitation.
Étape 2 sur 2 : Cliquez sur Download.
Étape 2 sur 2 : Cliquez sur Download.

Une fois le téléchargement terminé, vous devriez donc avoir un fichier exécutable si vous êtes sous Windows, ou une archive Zip si vous êtes sous Mac. Dans la suite, je détaillerai les étapes d'installation de l'IDE sous Windows. Je laisse le soin aux initiés Mac de décompresser leur archive et de mettre en place leur IDE.

Installation sous Windows

Double-cliquez sur le fichier exécutable pour démarrer l'installation. Une fois celle-ci terminée, nous aurons alors notre environnement de développement Eclipse qui intègre déjà le plugin Java BlackBerry. Les étapes à suivre durant la procédure d'installation sont détaillées ci-dessous.

Étapes 1 à 4 sur 7 : Choisissez les différentes options que vous souhaitez.
Étapes 1 à 4 sur 7 : Choisissez les différentes options que vous souhaitez.
Étape 5 sur 7 : Vérifiez les informations puis cliquez sur Install.
Étape 5 sur 7 : Vérifiez les informations puis cliquez sur Install.
Étape 6 sur 7 : Patientez pendant l'installation...
Étape 6 sur 7 : Patientez pendant l'installation...
Étape 7 sur 7 : Cochez la case Start Eclipse Java Plug-in puis cliquez sur Done pour démarrer Eclipse.
Étape 7 sur 7 : Cochez la case Start Eclipse Java Plug-in puis cliquez sur Done pour démarrer Eclipse.

Installer le Java SE JDK L'interface d'Eclipse

L'interface d'Eclipse

Eclipse et son plugin pour BlackBerry Votre première application

L'interface d'Eclipse

Nous avons maintenant tous les outils nécessaires au développement d'applications BlackBerry. Dès le prochain chapitre, nous commencerons à écrire nos premières lignes de code. Mais avant cela, nous allons faire un petit tour d'horizon de l'interface d'Eclipse. En effet, il se peut que certains d'entre vous n'aient pas l'habitude de programmer avec Eclipse.

Normalement, vous devriez déjà avoir ouvert Eclipse après l'installation, mais vous pouvez également lancer l'environnement de développement en double-cliquant sur eclipse.exe dans le dossier d'installation du logiciel.

Nous ne ferons ici qu'un rapide tour de l'interface, nous ne verrons donc pas l'intégralité des fonctionnalités du logiciel. Mais je vous conseille grandement de fouiller dans les recoins de l'IDE.

Voici donc ce qui apparaît au lancement du logiciel :

Image utilisateur
Image utilisateur

Dans le Workspace Launcher, il vous est demandé de renseigner le dossier de destination de l'ensemble de vos projets. Si vous souhaitez conserver le même dossier pour tous vos projets, vous pouvez cocher la case Use this as default and do not ask again puis cliquer sur le bouton OK.

Nous voilà enfin dans notre environnement de développement. Celui-ci est divisé en plusieurs zones découpées dans la figure ci-dessous.

Image utilisateur

Voici une liste des fonctionnalités correspondant aux encadrés ci-dessus :


Eclipse et son plugin pour BlackBerry Votre première application

Votre première application

L'interface d'Eclipse Création du projet

La création du premier projet est une étape importante dans l'apprentissage de la programmation. C'est en général l'occasion de se familiariser avec son environnement de développement et avec les bases du langage utilisé. Ce chapitre n'est pas destiné à réaliser une application complexe, nous aurons tout le temps pour perfectionner nos applications plus tard.
Dans ce chapitre, nous allons :

Au cours de ce chapitre, il ne vous est pas demandé de comprendre toutes les subtilités du code. Tout deviendra plus clair au fur et à mesure, à la lecture des chapitres.

Création du projet

Votre première application Analyse du contenu

Création du projet

Maintenant que nous avons un environnement de développement configuré, nous allons pouvoir créer notre premier projet. Nous verrons quels sont les différents paramètres qui le définissent. Puis nous décortiquerons son contenu en termes de fichiers et de code source.
Lançons-nous donc dans la création du nouveau projet. Pour cela, cliquez sur File > New puis sur BlackBerry Project :

Image utilisateur

Il est également possible de créer un nouveau projet en cliquant sur l'icône New BlackBerry Project de la barre de navigation. Il s'agit du bouton de gauche présenté sur l'image ci-dessous.

Image utilisateur

Dans le champ Project name, entrez le nom du projet : « Hello World » puis cliquez sur Finish :

Image utilisateur

Grâce à cette interface, nous pouvons créer un projet de plusieurs manières. Nous allons nous attarder un peu sur les différents champs. Dans Project name nous devons renseigner le nom du projet. Il ne s'agit pas forcément du nom définitif de l'application, mais uniquement du nom du projet dans Eclipse. Dans Contents nous avons deux possibilités : créer un nouveau projet à partir de rien ou importer des sources existantes. Enfin dans JRE est spécifiée la version du JRE BlackBerry avec laquelle nous créerons le projet.

Il se peut que vous deviez redémarrer Eclipse pour que celui-ci reconfigure l'espace de travail. Vous aurez alors le message suivant :

Image utilisateur

Une fois le projet créé, vous devriez normalement le voir apparaître dans l'onglet Package Explorer avec le nom que vous avez choisi. Nous étudierons son contenu dans le paragraphe suivant, même si celui-ci est très proche d'un projet Eclipse quelconque. Au centre de l'IDE est ouvert le fichier XML de description de l'application. Nous reviendrons sur ce type de fichier plus tard, et nous étudierons les différents champs qui le composent.
L'environnement de développement devrait donc ressembler à ceci :

Image utilisateur

Votre première application Analyse du contenu

Analyse du contenu

Création du projet Lancement de l'application

Analyse du contenu

Dans ce paragraphe, nous étudierons le contenu de notre projet. En premier lieu, nous allons analyser son arborescence dans l'onglet Package Explorer. Pour cela, déroulez les différents dossiers en cliquant sur la petite flèche à gauche de ceux-ci.

Image utilisateur

Voici donc les deux dossiers principaux que nous trouvons dans le projet :

Si vous visualisez le fichier icon.png, vous devriez voir cette image :

Image utilisateur

Maintenant nous allons étudier le code source de ce projet de base. Nous verrons alors le code minimal requis pour lancer une application dans BlackBerry OS.

La classe MyApp

Je vous propose d'ouvrir le fichier MyApp.java dans l'éditeur de code en double-cliquant dessus.

package mypackage;

import net.rim.device.api.ui.UiApplication;

/**
 * This class extends the UiApplication class, providing a
 * graphical user interface.
 */
public class MyApp extends UiApplication
{
    /**
     * Entry point for application
     * @param args Command line arguments (not used)
     */ 
    public static void main(String[] args)
    {
        // Create a new instance of the application and make the currently
        // running thread the application's event dispatch thread.
        MyApp theApp = new MyApp();       
        theApp.enterEventDispatcher();
    }
    

    /**
     * Creates a new MyApp object
     */
    public MyApp()
    {        
        // Push a screen onto the UI stack for rendering.
        pushScreen(new MyScreen());
    }    
}

Pour comprendre ce code, nous l'analyserons ligne par ligne. Tout d'abord, nous déclarons que notre programme se situe dans le package mypackage.

package mypackage;

Pour créer une interface utilisateur (ou UI), nous devons créer une classe qui hérite de la classe UiApplication. Nous pourrons ainsi afficher des choses à l'écran et interagir avec l'utilisateur. Voici donc le code correspondant :

public class MyApp extends UiApplication
{
    ...  
}

Pour pouvoir utiliser la classe UiApplication, nous devons l'importer. Celle-ci se trouve dans le package net.rim.device.api.ui. Pour importer cette classe, il nous faudra écrire la ligne de code suivante :

import net.rim.device.api.ui.UiApplication;

Le point d'entrée de l'application est la méthode main(). À l'intérieur de celle-ci, nous devrons créer une instance de notre application MyApp.
Au lancement de l'application, un Thread (ou pile d'exécution) sera lancé. Ce dernier fera appel à la méthode enterEventDispatcher() et deviendra l'event-dispatching Thread. Ce qu'il faut comprendre, c'est que nous pourrons ainsi dessiner à l'écran et gérer les évènements. Tout ceci est peut-être un peu vague pour l'instant, mais nous reviendrons sur ces notions dans la partie suivante. En attendant, utilisez la méthode main() telle qu'elle vous est proposée :

public static void main(String[] args)
{
    MyApp theApp = new MyApp();       
    theApp.enterEventDispatcher();
}

Enfin pour finir, nous devons créer un constructeur à notre classe MyApp. Dans ce constructeur, nous allons tout simplement afficher un écran défini par une nouvelle classe MyScreen, écrite dans le fichier MyScreen.java. Voici le code correspondant :

public MyApp()
{        
    pushScreen(new MyScreen());
}

Cependant avant de la « pousser » sur l'écran avec la méthode pushScreen(), l'interface devra avoir été totalement définie. Il faudra donc la décrire entièrement à l'intérieur du constructeur de la classe MyScreen.

La classe MyScreen

Nous allons détailler maintenant le fichier MyScreen.java qui va nous servir à décrire notre interface affichée à l'écran. Celui-ci se situe également dans le package mypackage. Voici le code complet de cette classe :

package mypackage;

import net.rim.device.api.ui.container.MainScreen;

/**
 * A class extending the MainScreen class, which provides default standard
 * behavior for BlackBerry GUI applications.
 */
public final class MyScreen extends MainScreen
{
    /**
     * Creates a new MyScreen object
     */
    public MyScreen()
    {        
        // Set the displayed title of the screen       
        setTitle("MyTitle");
    }
}

Pour créer des interfaces, notre classe MyScreen doit hériter de la classe MainScreen. Celle-ci doit être importée du package net.rim.device.api.ui.container. Voici le code correspondant :

package mypackage;

import net.rim.device.api.ui.container.MainScreen;

public final class MyScreen extends MainScreen
{
    ...
}

Comme nous l'avons dit précédemment, l'ensemble de l'interface doit être défini dans le constructeur de la classe MyScreen. Cette dernière contiendra donc uniquement son constructeur que voici :

public MyScreen()
{        
    setTitle("MyTitle");
}

Actuellement, l'application se contente uniquement d'afficher son titre : « MyTitle ». Nous pourrions peut-être essayer de faire un peu mieux en ajoutant un petit texte de bienvenue. Voici donc le constructeur que je vous propose :

public MyScreen()
{        
    setTitle("Site du Zéro");
    add(new RichTextField("Salut les zéros!"));
}

Il ne faudra pas oublier d'importer la classe RichTextField. Je vous propose donc le code complet de la classe modifiée :

package mypackage;

import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.MainScreen;

public final class MyScreen extends MainScreen
{
    public MyScreen()
    {        
        setTitle("Site du Zéro");
        add(new RichTextField("Salut les zéros!"));
    }
}

Création du projet Lancement de l'application

Lancement de l'application

Analyse du contenu La constitution des vues

Lancement de l'application

Maintenant que notre projet est prêt, nous allons pouvoir le tester. Il est bien entendu possible de le faire sur un appareil physique, cependant il n'est pas nécessaire de posséder un smartphone BlackBerry pour lancer ses applications. Dans ce cours nous utiliserons un simulateur ou émulateur. Pour lancer votre application dans le simulateur BlackBerry, commencez par cliquer sur Run > Run As > BlackBerry Simulator :

Image utilisateur

Patientez le temps que l'OS démarre...

Image utilisateur
Image utilisateur

Une fois démarré, allez dans la catégorie Downloads présentée ci-dessous :

Image utilisateur

Enfin dans Downloads, cliquez sur l'icône de votre application.

Vous l'aurez remarqué, l'icône de votre application correspond à l'image icon.png du répertoire res présentée plus haut. Après avoir cliqué sur votre application dans le dossier Downloads, celle-ci devrait se lancer dans votre simulateur. Voici ce que vous devriez avoir sur l'écran :

Image utilisateur

Analyse du contenu La constitution des vues

La constitution des vues

Lancement de l'application Introduction

Nous voici arrivés dans le premier chapitre de la partie portant sur les UI pour User Interface ou interface utilisateur.
Nous allons tout au long de cette partie découvrir comment afficher des objets sur l'écran et ainsi pouvoir créer des applications correspondant visuellement à vos attentes. Pour démarrer en douceur dans ce chapitre, nous allons découvrir les principes de base des interfaces utilisateur.
Sans plus attendre, démarrons cette partie passionnante !

Introduction

La constitution des vues Les classes fondamentales

Introduction

Comme promis nous allons démarrer en douceur cette partie. Au cours de ce chapitre nous n'entrerons pas vraiment dans les lignes de code, mais nous verrons plutôt les grands principes de création d'interfaces graphiques.
Lorsque nous créons des applications, une grosse partie du développement peut être tournée vers le graphisme. Principalement dans les jeux, vous aurez besoin de créer une interface graphique complexe que l'utilisateur aura à l'écran. Suivant le type d'applications, ces interfaces seront différentes et pourront être réalisées de différentes façons.

Pour illustrer tout ce qui vient d'être dit, voici quelques exemples d'interfaces graphiques :

Image utilisateurImage utilisateurImage utilisateurImage utilisateur
Image utilisateurImage utilisateurImage utilisateurImage utilisateur

Notez les différences de contenu de ces différentes applications. La première est une calculatrice, vous constaterez qu'elle est principalement constituée d'un « affichage numérique » ainsi que de boutons. Cette interface graphique a été conçue à l'aide de composants prédéfinis, ce qui facilite la création de telles interfaces. Nous retrouvons parmi ces composants des boutons, des champs de texte, des listes et pleins d'autres ; nous y reviendrons !
Juste à côté le menu des réglages intègre également des composants de base comme des champs de texte, des images et différents types de boutons.
Enfin les deux derniers sont des jeux, vous remarquerez que ces interfaces sont « dessinées », principalement à l'aide d'images.

Ainsi chaque style d'interface graphique est réalisé avec une technique spécifique, que nous verrons avant la fin de ce chapitre.


La constitution des vues Les classes fondamentales

Les classes fondamentales

Introduction Les différentes techniques

Les classes fondamentales

Nous allons à présent voir les différentes classes à la base de tous les composants graphiques. Celles-ci sont contenues dans le package net.rim.device.api.ui. En particulier nous verrons les trois classes présentées ci-dessous. Tout ce que nous verrons par la suite découle de ces classes par héritage :

Commençons tout de suite par la première : la classe Field.

Les Fields

La classe Field est la classe fondamentale de tout composant graphique d'une application. En effet tous les composants graphiques tels que les boutons, les champs de texte ou autres héritent de cette classe.
Nous pourrions voir ceci comme une zone rectangulaire traçable à l'écran à l'intérieur de ce que nous appelons des conteneurs.

Comme cela vient d'être dit, nous utiliserons donc jamais en pratique cette classe telle quelle. En revanche, ce qu'il intéressant de noter c'est que tous les éléments graphiques sont donc des classes filles de cette classe Field. Celles-ci hériteront donc de toutes ses propriétés et méthodes.
Ainsi nous pourrons par exemple utiliser les propriétés FIELD_LEFT, FIELD_HCENTER ou FIELD_RIGHT pour positionner l'objet à l'écran. Bien évidemment nous ne détaillerons pas ici toutes les propriétés et méthodes de cette classe. Notez cependant les méthodes getHeight() et getWidth() qui permettent de récupérer la largeur et la hauteur de l'objet Field.

Je pense en avoir assez dit sur cette classe, nous allons donc à présent nous intéresser aux classes Manager et Screen qui elles-mêmes héritent de la classe Field.

Les Managers

Grâce à la classe Field, nous avons pu mettre en évidence les bases de tout composant graphique. Ceux-ci peuvent être des champs de texte, de saisie de texte ou encore des boutons, d'ailleurs le prochain chapitre vous introduira les composants les plus utilisés. Ces composants sont indispensables pour créer des vues mais ne suffisent pas. C'est là qu'entrent en jeu les Managers ou conteneurs.
Comme son nom l'indique, un conteneur sert à contenir d'autres éléments, c'est-à-dire des composants mais également d'autres conteneurs. Nous aurons également le temps de revenir sur le concept de conteneur dans un chapitre qui leur est dédié.

En attendant, sachez que ces conteneurs permettent de mettre en place les différents composants à l'écran tels que vous pouvez le voir sur cette application que vous connaissez très certainement :

L'application Facebook
L'application Facebook
Les Screens

Enfin pour finir sur le côté « technique » de ce chapitre, nous allons étudier la classe Screen ainsi que des classes héritant de celle-ci. Il s'agit de l’élément principal de l'affichage, c'est-à-dire que c'est lui qui englobe l'ensemble des éléments à tracer. D'ailleurs il s'agit d'une classe fille de la classe Manager vue précédemment ; en effet puisqu'elle sert de conteneur pour l'ensemble des éléments.
Pour vous prouver que cette classe Screen est à la base de tout affichage, sachez que nous l'avons déjà utilisé. Effectivement, si vous regardez bien nos codes précédents, vous verrez apparaître une classe fille de celle-ci qui se nomme MainScreen :

/**
 * A class extending the MainScreen class, which provides default standard
 * behavior for BlackBerry GUI applications.
 */
public final class MyScreen extends MainScreen
{
	public MyScreen()
	{
	}
}

Comme nous venons de le voir, il existe différentes classes pour créer un écran. Voici les trois principales : MainScreen héritant de la classe FullScreen elle-même héritant de la classe Screen. Celles-ci se distinguent principalement par leur contenu par défaut. Voici une brève description de chacune d'entre elles :

Classe

Description

Screen

Il s'agit de la classe de base : vous pouvez créer des écrans en utilisant un conteneur.

FullScreen

Elle permet également de créer une vue à partir d'un écran vide, mais avec ici un conteneur de type vertical.

MainScreen

Cette classe permet de créer des écrans possédant déjà quelques éléments standards :

  • un titre d'écran

  • un conteneur de type vertical avec défilement (nous reviendrons là-dessus dans le chapitre consacré aux conteneurs)

  • un menu par défaut

Maintenant que nous connaissons les différents éléments de base, je vais vous présenter les différentes méthodes que nous étudierons dans ce cours pour concevoir des interfaces graphiques.


Introduction Les différentes techniques

Les différentes techniques

Les classes fondamentales Les composants de base

Les différentes techniques

Comme nous avons vu, la conception d'interfaces graphiques peut être réalisée de différentes manières. Nous distinguerons tout d'abord l'utilisation de composants prédéfinis et prêts à l'emploi, mais il nous est également possible de dessiner directement à l'écran pour créer des vues personnalisées.
Nous allons présenter ici brièvement la mise en œuvre de ces techniques. Puis nous les détaillerons davantage tout au long de cette partie.

Utilisation de composants prédéfinis

La première chose que nous allons apprendre dans cette partie est l'utilisation des composants prédéfinis ainsi que leur disposition à l'écran. C'est l'objet des deux prochains chapitres de ce cours.
Grâce à cette technique, vous pourrez réaliser des interfaces graphiques telles que celle de la calculatrice présentée plus haut.
Pour rappel, voici l'interface graphique en question :

Une calculatrice intégrée.
Une calculatrice intégrée.

Pour concevoir ce type d'interfaces nous devrons utiliser des conteneurs, que nous pouvons voir comme des gestionnaires de positionnement. Pour cela nous utiliserons donc notre classe principale MainScreen, puis nous insèrerons des conteneurs qui contiendront eux-mêmes des composants.
Nous aurons tout le temps pour bien comprendre, mais saisissez qu'il s'agit uniquement d'éléments imbriqués les uns dans les autres. Il n'y aura donc aucune difficulté dans le code source.

Les vues personnalisées

Enfin pour finir nous allons brièvement présenter la conception de vues personnalisées. Ceci devrait ravir les futurs programmeurs de jeux vidéo !
Dans ce cas nous n'utiliserons pas de composants mais nous allons tracer différents éléments à l'écran. Nous utiliserons également la classe MainScreen qui est indispensable. Cependant nous allons ici nous servir de la méthode paint() de cette classe. Grâce à cette méthode nous pourrons utiliser la classe Graphics qui regorge de méthodes permettant de tracer des lignes, des cercles, des rectangles, etc, mais également afficher différentes images pour recréer tout ce qui vous passe par la tête.

À l'aide de cette technique, nous pourrons en fin de partie réaliser un jeu de taquin que voici :

Un jeu de taquin réalisé en fin de partie !
Un jeu de taquin réalisé en fin de partie !

Je pense que certains sont déjà impatients de voir tout ça. Je vous invite donc sans plus attendre à passer au chapitre suivant !


Les classes fondamentales Les composants de base

Les composants de base

Les différentes techniques Les champs de texte

Dans ce chapitre nous allons présenter les composants les plus simples, mais surtout les plus utilisés, à savoir :

En combinant ces différents éléments, vous pourrez ainsi créer des vues relativement complexes en les agençant correctement grâce aux conteneurs que nous verrons dans le prochain chapitre.
Ce chapitre est long, mais il n'y a aucune difficulté si vous prenez bien le temps de tout lire.

Les champs de texte

Les composants de base Les boutons

Les champs de texte

Nous allons dans ce chapitre présenter différents composants permettant de mettre en place une interface graphique. Chaque composant est associé à une classe avec différentes méthodes pour le manipuler. Dans ce cours nous survolerons brièvement les méthodes disponibles, c'est pourquoi je vous invite fortement à visiter la documentation officielle pour vous perfectionner. Notamment vous pouvez visiter la rubrique Documentation pour plus de renseignements ou encore la rubrique API reference pour visualiser l'ensemble des classes disponibles.

Introduction

Pour démarrer ce chapitre, nous allons reprendre notre classe MyScreen écrite pour notre premier projet Hello World. Si vous vous souvenez bien, nous avions utilisé la classe RichTextField pour pouvoir afficher un message à l'écran. Il s'agit du premier composant que nous étudierons dans ce chapitre.
Pour rappel, voici le code nous avions pour notre classe MyScreen :

package mypackage;

import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.MainScreen;

public final class MyScreen extends MainScreen
{
    public MyScreen()
    {        
        setTitle("Site du Zéro");
        add(new RichTextField("Salut les zéros!"));
    }
}

Pour l'instant nous nous contenterons encore d'écrire notre code à l'intérieur du constructeur de la classe MyScreen.
Sans plus attendre, je vous propose de faire un vaste tour des différents composants disponibles pour la création d'interfaces graphiques. Commençons tout de suite avec RichTextField !

Afficher du texte

Pour afficher du texte à l'écran, nous disposons des deux classes RichTextField et LabelField. Ces deux composants sont très similaires, et il est tout à fait possible d'utiliser l'un ou l'autre. Peut-être la classe LabelField est plutôt utilisée en tant qu'étiquette pour d'autres contrôles, alors que l'utilisation d'un RichTextField permet plus de paramétrages. Ceci dit, dans la plupart des cas les deux feront tout à fait l'affaire.

RichTextField

Dans notre projet Hello World, nous avions créé une instance de la classe RichTextField directement à l'intérieur de la méthode add(). Toutefois il est possible de créer une instance de cette classe en la stockant dans une variable. C'est en effet la solution que je vous recommande dans la majorité des cas.
Nous pourrions alors réécrire le code précédent de la manière suivante :

RichTextField monTexte = new RichTextField("Salut les zéros!");
add(monTexte);

Les champs de texte de type RichTextField sont multilignes, vous pouvez donc insérer un long texte qui s'étalera sur plusieurs lignes si besoin. Nous allons essayer ceci en utilisant la méthode setText() qui permet de modifier le texte de notre RichTextField.
Pour cela écrivez le code suivant :

monTexte.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla a metus justo. Aliquam erat volutpat. Proin molestie mauris id erat mattis consequat. Nulla ut mi ipsum. Suspendisse tempor sollicitudin magna, ac blandit sapien commodo ut. Etiam mattis dolor non dui bibendum at sollicitudin risus fringilla. Praesent placerat orci ut est egestas commodo. Aenean vehicula rhoncus leo a ornare.");

Vous obtiendriez alors un champ de texte sur plusieurs lignes comme présenté sur l'écran ci-dessous :

Image utilisateur

La classe RichTextField possède de nombreuses autres méthodes telles que getText() pour récupérer son texte ou encore la méthode setFont héritée de TextField qui permet de changer la police de celui-ci. Je vous laisserai le soin de les découvrir par vous-mêmes, en revanche nous parlerons des polices de caractères juste après la classe LabelField.

LabelField

Comme dit précédemment, la classe LabelField s'utilise principalement de la même manière que RichTextField. Nous pourrons donc déclarer une instance de cette classe comme ceci :

LabelField monTexte = new LabelField("Votre texte ici.");
add(monTexte);

Notez également qu'il n'est pas nécessaire de renseigner le texte à afficher dès la déclaration, et il en est de même pour la classe RichTextField. Si vous le désirez vous pouvez procéder de la manière suivante :

LabelField monTexte = new LabelField();
monTexte.setText("Votre texte ici.");
add(monTexte);

N'oubliez pas d'importer les différentes classes que vous utilisez. Pour éviter de les importer une par une, il est possible d'importer tout le package en insérant la ligne suivante :

import net.rim.device.api.ui.component.*;
Utilisation des polices

L'utilisation des polices de caractères est toujours un peu délicate, c'est pourquoi nous allons présenter ici la manière de faire.
Pour appliquer une police et un style à un champ de texte nous devrons utiliser les deux classes Font et FontFamily. Une fois ces classes instanciées, il est alors possible de faire passer la police de type Font en paramètre de la méthode setFont() d'un champ de texte. Voici un exemple d'utilisation de ces classes :

FontFamily typeCaracteres = FontFamily.forName("Comic Sans MS");
Font maPolice = typeCaracteres.getFont(Font.BOLD, 26);
monTexte.setFont(maPolice);

Cependant si vous utilisez ce code tel quel, vous aurez des problèmes de compilation. En effet, ce code pourrait générer une exception. Pour gérer ces exceptions, il faudra donc utiliser le bloc try{....} catch{...} du langage Java. Pour rappel, ce bloc d'instructions permet de gérer des morceaux de code pouvant générer des exceptions ; par exemple une division par zéro.
Dans notre cas, voici comment l'utiliser :

try 
{
    FontFamily typeCaracteres = FontFamily.forName("Comic Sans MS");
    Font maPolice = typeCaracteres.getFont(Font.BOLD, 26);
    monTexte.setFont(maPolice); 
}
catch (ClassNotFoundException e) 
{
    System.out.println(e.getMessage());
}

Enfin pour que personne ne soit perdu, je vous propose un code reprenant la classe MySreen intégralement. Ce code affichera donc un champ de texte de type RichTextField utilisant la police « Comic Sans MS » en gras et de taille 26.
Voici donc la classe MySreen écrite au complet :

package mypackage;

import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.Font;
import net.rim.device.api.ui.FontFamily;

public final class MyScreen extends MainScreen
{
    public MyScreen()
    {
    	RichTextField monTexte = new RichTextField();
    	try 
    	{
    		FontFamily typeCaracteres = FontFamily.forName("Comic Sans MS");
    		Font maPolice = typeCaracteres.getFont(Font.BOLD, 26);
    		monTexte.setFont(maPolice);
    	}
    	catch (ClassNotFoundException e) 
    	{
    		System.out.println(e.getMessage());
    	}
    	monTexte.setText("Ce texte utilise la police \"Comic Sans MS\", style gras, taille 26");
    	add(monTexte);
      }
}

Je rappelle également que pour insérer des guillemets dans votre texte, vous devez insérer un antislash « \ » avant le caractère. Lancez votre projet et vous devriez voir apparaître ceci à l'écran :

Image utilisateur
Les autres champs de texte

Nous allons maintenant voir différents champs de texte spécifiques qui permettent notamment à l'utilisateur de saisir du texte sous différentes formes depuis son clavier. Dans ce cours nous verrons ceux qui s'avèrent les plus utiles et performants.

EditField

Pour la saisie de texte, nous possédons diverses classes telles que TextField, BasicEditField, EditField et AutoTextEditField. Nous verrons uniquement les champs de type EditField qui sont les plus utilisés. Néanmoins l'utilisation d'un champ de saisie de texte de type BasicEditField est quasiment identique à celle que nous allons voir.
Je vous propose donc de découvrir le constructeur de ce nouveau type de champ de texte :

EditField monTexte = new EditField("Etiquette: ", "");

Vous remarquerez l'apparition d'une seconde chaîne de caractères dans le constructeur. En effet contrairement aux champs de texte LabelField ou RichTextField, les champs de saisie de texte possèdent deux champs distincts :

Vous trouverez donc des méthodes différentes pour chacun des champs. Par exemple la méthode setLabel() permettra de modifier l'étiquette alors que les méthodes setText() et getText() permettent de traiter la saisie.

Après quelques frappes au clavier, vous pourriez avoir ceci :

Image utilisateur

Les saisies de texte peuvent avoir des finalités très différentes et leur gestion peut entraîner facilement des erreurs ou des choses inattendues. C'est pourquoi la saisie de texte peut être facilitée à l'aide d'un filtre.
Par exemple, nous pourrions forcer l'utilisateur à renseigner uniquement des nombres entiers :

EditField monTexte = new EditField("Etiquette: ", "", 10, EditField.FILTER_INTEGER);

Ou encore, nous pourrions imposer un texte en majuscules :

EditField monTexte = new EditField("Etiquette: ", "", 10, EditField.FILTER_UPPERCASE);

Il existe de nombreux filtres mais voici les principaux et certainement les plus utilisés : FILTER_DEFAULT, FILTER_INTEGER, FILTER_LOWERCASE, FILTER_NUMERIC, FILTER_PHONE, FILTER_UPPERCASE et FILTER_URL.

Enfin vous pouvez utiliser les champs de type AutoTextEditField plus « intelligents ». Par exemple ils permettent d'insérer un point, un espace et une majuscule par double pression de la barre d'espace. Leur utilisation est très similaire à celle des EditField.

DateField

Vous pourriez également avoir besoin d'afficher la date, et heureusement il existe un champ spécial nommé DateField. Grâce à lui vous pouvez afficher date et heure selon le format désiré.
Voici comment créer un composant de type DateField :

DateField monTexte = new DateField("Date: ", System.currentTimeMillis(), DateField.DATE_TIME);

La méthode currentTimeMillis() permet de récupérer la date et l'heure exacte actuelle. Celle-ci renvoie le nombre de millisecondes écoulées depuis un point de départ : le 1er Janvier 1970 à 00:00:00 GMT. Ainsi cette valeur en millisecondes nous donne en même temps l'heure exacte ainsi que la date.

Pour en revenir à notre champ de texte, voici ce que donne un DateField à l'écran :

Image utilisateur

Ces champs sont également éditables, ainsi si vous cliquez sur une partie de la date vous verrez apparaître un nouveau contrôle que voici :

Image utilisateur

Bien évidemment l'heure est éditable au même titre que la date. Si vous cliquez sur l'heure, vous verrez également apparaître ceci :

Image utilisateur
PasswordEditField

Pour finir avec les champs de texte, nous allons maintenant voir la classe PasswordEditField.
Comme son nom l'indique, ce type de champs de texte est réservé aux mots de passe ou aux saisies cachées. C'est-à-dire que lors de la saisie, chaque caractère est remplacé par un astérisque « * » sur l'écran, comme ceci :

Image utilisateur

Mise à part l'affichage qui est différent, ces champs de texte sont identiques aux EditField vus précédemment. Je vous montre tout de même le constructeur pour éviter d'en perdre quelques-uns :

PasswordEditField monTexte = new PasswordEditField("Mot de Passe: ", "");

Ainsi nous retrouverons donc les méthodes setLabel(), setText() et getText() pour gérer notre étiquette et notre texte éditable.


Les composants de base Les boutons

Les boutons

Les champs de texte Les images

Les boutons

Nous allons à présent examiner les différents types de boutons proposés pour les interfaces. Nous y retrouvons tous les grands classiques, à savoir boutons simples, cases à cocher, boutons radios et d'autres. Dans ce chapitre nous ne présenterons que la mise en page de ces éléments. C'est-à-dire que nous ne verrons pas comment gérer une action sur un bouton ; ceci sera traité dans le chapitre sur les évènements.
Sans plus attendre, découvrons ces composants graphiques.

Les boutons classiques
ButtonField

Pour démarrer, voyons les boutons simples de type ButtonField.
Ce sont les boutons les plus utilisés, ils permettent de réaliser une action lorsqu'on clique dessus. Voilà leur apparence dans les interfaces graphiques BlackBerry :

Image utilisateur

Pour créer des boutons, la démarche est la même que pour les champs de texte. Nous allons donc instancier un élément de type ButtonField en appelant son constructeur. Le plus simple est de renseigner le texte qui s'affichera sur le bouton. Voici donc la manière de faire :

ButtonField monBouton = new ButtonField("Button");
add(monBouton);

Il est possible de modifier le texte ou étiquette ou encore label du bouton en utilisant la méthode setLabel(). Pour ceux qui auraient déjà pensé mettre une icône à la place du texte ou encore combiner icône et texte, nous verrons comment faire à la fin du chapitre. Pour réaliser cela nous devrons utiliser la méthode setImage() qui prend en paramètre un objet de type Image que nous introduirons un peu plus loin.

CheckboxField

Les composants de type CheckboxField sont des cases à cocher. Ces composants sont très utilisés, notamment pour activer ou non certains paramètres dans des menus d'options quelconques ou bien pour des acceptations de licences et de conditions. Ces composants possèdent deux états : actif par la valeur true, ou inactif par un false.
Voici comment utiliser un composant de la classe CheckboxField :

CheckboxField monBouton = new CheckboxField("Checkbox",true);
add(monBouton);

Voici à quoi ressemblent ces cases à cocher :

Image utilisateur

Les méthodes setLabel() et getLabel() nous permettent de saisir ou de récupérer l'étiquette de la case à cocher, de la même manière que pour un ButtonField. Enfin vous pouvez gérer l'état de la case à cocher soit en récupérant sa valeur par la méthode getChecked(), soit imposer un état par setChecked().

RadioButtonField

Les boutons RadioButtonField sont des boutons radios. Ils sont proches des cases à cocher sauf qu'un seul bouton ne peut être actif à la fois dans un même groupe. Je m'explique : pour créer ce genre de boutons, nous devons au préalable définir un groupe RadioButtonGroup, puis nous ajouterons alors des boutons à ce groupe.
Pour mieux comprendre nous allons prendre un exemple dont voici le code :

RadioButtonGroup monGroupe = new RadioButtonGroup();
add(new RadioButtonField("Option 1",monGroupe,true));
add(new RadioButtonField("Option 2",monGroupe,false));
add(new RadioButtonField("Option 3",monGroupe,true));

Nous avons ici déclaré trois boutons de type RadioButtonField que nous avons ajouté au même groupe. Je vous propose alors de tester ce code, vous devriez alors voir apparaître ceci sur votre écran :

Image utilisateur

Bien évidemment en temps normal vous aurez besoin de récupérer l'état d'un bouton avec la méthode isSelected(), c'est pourquoi vous devrez déclarer une variable pour chacun de vos boutons :

RadioButtonGroup monGroupe = new RadioButtonGroup();
RadioButtonField monBouton = new RadioButtonField("Option 1",monGroupe,true);
add(monBouton);
Les boutons déroulants

Nous avons déjà vu les cases à cocher et les boutons radios qui servent à faire des choix parmi plusieurs options. Ces boutons sont très utiles mais possèdent l'inconvénient de prendre de la place surtout lorsqu'il y a beaucoup de choix. Ceci est d'autant plus vrai que les écrans des terminaux mobiles sont petits. C'est pourquoi il existe une alternative : les listes déroulantes.

ObjectChoiceField

La première liste déroulante est de type ObjectChoiceField qui est propice à l'affichage de texte. Pour faire cela nous devons au préalable définir l'ensemble de nos différents choix à l'intérieur d'un tableau. La liste sera alors créée à partir des différents éléments de votre tableau, c'est aussi simple que cela.
Voici comment procéder :

String mesChoix[] = {"Option 1","Option 2","Option 3","Option 4"};
ObjectChoiceField maListe = new ObjectChoiceField("Label :",mesChoix,2);
add(maListe);

Vous remarquerez que les listes déroulantes possèdent également une étiquette à renseigner dans le constructeur. Enfin le dernier paramètre correspond tout simplement à l'élément sélectionné initialement à l'apparition de l'écran. Voici à quoi ressemble le composant à l'écran :

Image utilisateur

Si vous faites dérouler la liste vous verrez alors apparaitre l'ensemble des choix disponibles :

Image utilisateur

Les méthodes à retenir pour cette classe sont setChoices(), getSelectedIndex() et getChoice(). La première sert à définir ou redéfinir la liste de choix, la deuxième à récupérer l'indice de l'élément sélectionné et la dernière à récupérer la valeur de l'élément dont l'indice est renseigné en paramètre.
Nous avons ici utilisé des éléments de type String comme vous le ferez souvent, mais sachez qu'il est possible d'y insérer toutes sortes d'objets.

NumericChoiceField

Il existe une classe spécifique pour des listes déroulantes à valeurs numériques qui est NumericChoiceField. Pour définir la liste voici les paramètres à renseigner dans l'ordre :

Ce qui nous donne par exemple :

NumericChoiceField maListe = new NumericChoiceField("Label",1,12,1,4);
add(maListe);

Vous obtiendrez alors une liste très similaire à la précédente :

Image utilisateur

Puis en cliquant :

Image utilisateur

Vous pourrez également récupérer l'élément sélectionné à l'aide de la méthode getSelectedValue().


Les champs de texte Les images

Les images

Les boutons Un peu d'ordre avec les conteneurs

Les images

Préparation des images

Nous allons maintenant nous attaquer aux images que vous attendiez certainement avec impatience. Nous verrons comment charger une image à partir de son nom, puis nous apprendrons à les afficher.
Avant de continuer, nous avons besoin d'une image. Je vous propose donc cette image de Zozor au format PNG, directement tirée de la page d'accueil du Site du Zéro.

Image utilisateur

Pour pouvoir charger une image, celle-ci doit se trouver dans le dossier du projet. Pour cela copier l'image dans res/img au même endroit que l'icône de l'application, puis rafraichissez le projet dans Eclipse pour voir apparaître votre image :

Image utilisateur

Nous voilà fin prêts pour démarrer !

Chargement des images

Pour afficher des images, celles-ci doivent être stockées à l'intérieur de variables. Nous en avons trois types qui sont : Bitmap, EncodedImage, Image.

Bitmap

Le chargement d'une image se fait ici à l'aide de la méthode getBitmapResource() de cette classe, en renseignant le nom de l'image à charger.
Voici comment charger une image dans une variable de type Bitmap :

Bitmap monImage = Bitmap.getBitmapResource("zozor.png");
EncodedImage

La classe EncodedImage permet également de charger une image, et c'est la classe que je vous recommande d'utiliser. Le chargement d'une image se fait de manière similaire que précédemment :

EncodedImage monImage = EncodedImage.getEncodedImageResource("zozor.png");

Les images de type EncodedImage ont l'avantage d'être facilement manipulables, notamment si vous souhaitez redimensionner votre image. Pour cela utilisez la méthode scaleImage32() :

int taille = (int) Math.ceil(10000/echelle);
EncodedImage monImage = EncodedImage.getEncodedImageResource("zozor.png").scaleImage32(Fixed32.tenThouToFP(taille), Fixed32.tenThouToFP(taille));
Image

Enfin pour finir, la classe Image ne permet pas de charger directement une image. Cependant le format Image est parfois utilisé à la place du format EncodedImage pour afficher l'image. Cependant il est très facile de passer de l'un à l'autre grâce à la méthode createImage() de la classe ImageFactory. Cette méthode nous renvoie alors une image de type Image, utilisez donc l'expression :

ImageFactory.createImage(EncodedImage.getEncodedImageResource("zozor.png"))
Affichage d'images

Maintenant que les images sont chargées, nous allons pouvoir les afficher à l'écran. Nous verrons donc comment afficher directement l'image, puis nous verrons comment l'insérer en tant qu'icône dans un bouton de type ButtonField.

BitmapField

Le composant graphique BitmapField permet d'afficher une image à l'écran à partir d'une variable Bitmap ou EncodedImage. Pour un Bitmap, la manipulation est simple puisqu'il suffit de mettre l'image en question en paramètre du constructeur comme ceci :

Bitmap monImage = Bitmap.getBitmapResource("zozor.png");
BitmapField monBitmap = new BitmapField(monImage);
add(monBitmap);

En revanche aucun constructeur ne prend en paramètre des images de type EncodedImage, c'est pourquoi nous devrons procéder autrement. Nous utiliserons alors la méthode setImage() après avoir déclaré notre BitmapField :

EncodedImage monImage = EncodedImage.getEncodedImageResource("zozor.png");
BitmapField monBitmap = new BitmapField();
monBitmap.setImage(monImage);
add(monBitmap);

Dans les deux cas vous verrez apparaître Zozor dans l'angle supérieur gauche de votre écran, comme ceci :

Image utilisateur
Retour sur les ButtonField

Rappelez-vous je vous avais dit qu'il était possible de mettre une icône avec ou à la place d'un texte sur un bouton de type ButtonField. Maintenant que nous savons charger une image, nous allons pouvoir le faire. Pour cela nous nous servirons de la méthode setImage() de la classe ButtonField, qui prend en paramètre une image de type Image.
Pour faire cela, il suffit simplement de réaliser les différentes opérations dans l'ordre :

ButtonField monBouton = new ButtonField("Zozor");
monBouton.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("zozor.png")));
add(monBouton);

Vous verrez alors l'image de Zozor insérée dans le bouton à côté du texte :

Image utilisateur

Il est possible de choisir de placer le texte à gauche ou à droite de l'image grâce aux méthodes setLabelLeft() et setLabelRight(). Enfin si vous ne désirez pas de texte mais uniquement l'icône, il suffit de n'en renseigner aucun dans le constructeur de la classe ButtonField.


Les boutons Un peu d'ordre avec les conteneurs

Un peu d'ordre avec les conteneurs

Les images Le principe des conteneurs

Maintenant que nous avons appris à créer différents composants, nous allons maintenant apprendre à les disposer suivant nos envies grâce aux conteneurs !
Nous introduirons les différents types de conteneurs disponibles et nous verrons donc comment les organiser avec d'autres conteneurs et composants. Ainsi vous commencer à réaliser de vrais interfaces graphiques, correspondant à vos souhaits.
Enfin, nous réaliserons un album photo dans deux versions, que nous finaliserons dans le prochain chapitre !

Le principe des conteneurs

Un peu d'ordre avec les conteneurs Les différents conteneurs

Le principe des conteneurs

Introduction

Nous avions vu que la classe Mainscreen était un conteneur, qui est de type vertical. En effet, essayez d'y mettre plusieurs composants, vous vous apercevrez que ceux-ci s’insèrent verticalement. C'est pourquoi il existe différents types de conteneurs qui permettent d'aligner les éléments autrement.
Ainsi en combinant divers conteneurs, nous pourrons positionner les composants suivant nos envies. Voici par exemple une mise en page de conteneurs de types vertical et horizontal :

Image utilisateur

Chaque conteneur peut accueillir à la fois d'autres conteneurs ou bien des composants tels que ceux vus dans le chapitre précédent. Pour insérer un nouvel objet à un conteneur, nous devons utiliser une méthode déjà bien connue : add().
Bien entendu il existe d'autres manières d'aligner les composants que verticalement et horizontalement. Nous détaillerons tous les types de conteneurs qui vous permettront de réaliser les mises en pages que vous souhaitez.

Le principe de hiérarchie

Pour bien comprendre la philosophie des conteneurs, il est nécessaire de bien saisir la manière dont ceux-ci sont hiérarchisés.
Pour cela, reprenons la mise en page précédente sous la forme d'un arbre, pour bien comprendre comment les différents conteneurs sont liés entre eux :

Image utilisateur

Voici comment analyser cet arbre :

Enfin, il faut noter que les conteneurs se remplissent suivant l'ordre dans lequel sont ajoutés les objets. C'est un point important lors de l'organisation de votre mise à page à l'aide de la méthode add().

Voyons maintenant les différents types de conteneurs proposés !


Un peu d'ordre avec les conteneurs Les différents conteneurs

Les différents conteneurs

Le principe des conteneurs Un album photo

Les différents conteneurs

Dans cette sous-partie, nous allons travailler sur les différents conteneurs proposés. Pour comprendre leur fonctionnement nous devrons utiliser plusieurs éléments à afficher. C'est pourquoi je vous propose de nous servir de ces images de Zozor :

Image utilisateur
Image utilisateur
Image utilisateur

Nous utiliserons donc ici trois composants de type BitmapField contenant chacun une des trois images. Maintenant que vous savez utiliser cet élément, nous ne réécrirons pas à chaque fois le code servant à charger les images.
Toutefois, je le vous donne quand même une fois pour que nous partions sur de bonnes bases :

BitmapField zozor1 = new BitmapField();
BitmapField zozor2 = new BitmapField();
BitmapField zozor3 = new BitmapField();
zozor1.setImage(EncodedImage.getEncodedImageResource("Zozor1.png"));
zozor2.setImage(EncodedImage.getEncodedImageResource("Zozor2.png"));
zozor3.setImage(EncodedImage.getEncodedImageResource("Zozor3.png"));
Les conteneurs
VerticalFieldManager

Les conteneurs de type VerticalFieldManager servent à ajouter les éléments verticalement. Pour l'utiliser nous devrons donc l'ajouter au MainScreen, lui-même de type vertical. Puis nous lui insèrerons alors les éléments les uns après les autres dans l'ordre d'affichage.
Sans plus attendre, voici comment l'utiliser :

VerticalFieldManager monManager = new VerticalFieldManager();
add(monManager);
monManager.add(zozor1);
monManager.add(zozor2);
monManager.add(zozor3);

Sans grande surprise, vous verrez les trois images tracées les unes en dessous des autres :

Image utilisateur
HorizontalFieldManager

Comme son nom l'indique, ce conteneur permet d'aligner les éléments horizontalement. L'utilisation de ce type de conteneur est identique que précédemment.
Voici donc le code correspondant :

HorizontalFieldManager monManager = new HorizontalFieldManager();
add(monManager);
monManager.add(zozor1);
monManager.add(zozor2);
monManager.add(zozor3);

Là encore pas de surprise, les composants sont affichés horizontalement :

Image utilisateur
FlowFieldManager

Les conteneurs de type FlowFieldManager sont un mixe des deux précédents : les éléments sont rangés horizontalement puis verticalement. C'est-à-dire que les composants vont s'ajouter horizontalement jusqu'au bord de l'écran, puis sur une nouvelle ligne, etc.
Encore une fois, seul le nom du conteneur change à l'intérieur du code :

FlowFieldManager monManager = new FlowFieldManager();
add(monManager);
monManager.add(zozor1);
monManager.add(zozor2);
monManager.add(zozor3);
GridFieldManager

Les GridFieldManager sont des conteneurs sous forme de grilles, organisées en lignes et colonnes. Vous définissez le nombre de lignes et colonnes dans le constructeur, puis vous remplissez la grille par lignes.
Voici par exemple, une grille de dimension 2 imes 2 contenant nos trois images :

GridFieldManager monManager = new GridFieldManager(2,2,GridFieldManager.FIELD_LEFT);
add(monManager);
monManager.add(zozor1);
monManager.add(zozor2);
monManager.add(zozor3);

Pour l'instant ne vous souciez pas du GridFieldManager.FIELD_FIELD_LEFT, nous reviendrons dessus plus tard.
Étant donné que la grille possède uniquement deux colonnes, la troisième image sera placée dans la première colonne de la deuxième ligne :

Image utilisateur
AbsoluteFieldManager

Pour finir nous allons voir les conteneurs de type AbsoluteFieldManager. Ceux-ci permettent de placer les éléments suivant leur position absolue par rapport à l'origine qui est l'angle supérieur gauche. Cette distance est exprimée en pixels suivant les axes X horizontal et Y vertical.
Voici nos trois images placées en différents points de l'écran :

AbsoluteFieldManager monManager = new AbsoluteFieldManager() ;
add(monManager);
monManager.add(zozor1,0,0);
monManager.add(zozor2,50,100);
monManager.add(zozor3,200,200);

Ce type de conteneur permet de placer les éléments plus « aléatoirement » ou disons plutôt de manière moins géométrique :

Image utilisateur
Quelques propriétés
L'arrière-plan

Il est probable que la couleur blanche en arrière-plan ne vous convienne pas. C'est pourquoi nous allons voir comment changer la couleur de fond d'un conteneur. Pour cela nous devrons charger une couleur à l'aide de la classe BackgroundFactory puis la stocker dans une variable de type Background.
Voici comment procéder pour le MainScreen :

VerticalFieldManager monManager = (VerticalFieldManager)getMainManager();
Background maCouleur = BackgroundFactory.createSolidBackground(Color.GRAY);
monManager.setBackground(maCouleur);

Vous remarquerez qu'il est nécessaire de récupérer le conteneur MainScreen avant de lui affecter sa couleur de fond.
Voici le résultat :

Image utilisateur

Pour tout autre conteneur, procédez comme suit :

HorizontalFieldManager monManager = new HorizontalFieldManager();
Background maCouleur = BackgroundFactory.createSolidBackground(Color.RED);
monManager.setBackground(maCouleur);
L'alignement

Les conteneurs et les composants possèdent des propriétés qui servent à les aligner. En voici une liste non exhaustive de ces propriétés :

Ces propriétés sont renseignées dans le constructeur de l'élément lui-même. Voici un exemple reprenant nos trois images de Zozor :

BitmapField zozor1 = new BitmapField(Bitmap.getBitmapResource("Zozor1.png"),BitmapField.FIELD_RIGHT);
BitmapField zozor2 = new BitmapField(Bitmap.getBitmapResource("Zozor2.png"),BitmapField.FIELD_HCENTER);
BitmapField zozor3 = new BitmapField(Bitmap.getBitmapResource("Zozor3.png"),BitmapField.FIELD_LEFT);

À l'intérieur d'un conteneur vertical tel que le MainScreen, les alignements précédents nous donnent alors un écran dans ce style :

Image utilisateur

Le principe des conteneurs Un album photo

Un album photo

Les différents conteneurs La gestion des évènements

Un album photo

Après ces deux longs chapitres, nous allons pouvoir renter dans le concret. Pour cela, je vous propose de réaliser un petit album photo simplifié !

Dès à présent nous allons réaliser la mise en page de celui-ci, puis nous finirons l'application dans le chapitre suivant qui porte sur la gestion des évènements. Pour cet album photo nous concevrons deux versions ; la première comportant des boutons « Suivante » et « Précédente » pour faire défiler les photos, et la seconde sera composée de miniatures des photos pour accéder directement à la photo désirée.
Pour vous mettre l'eau à la bouche, voici un aperçu des deux versions de l'album photo :

Image utilisateur
Image utilisateur

Pour réaliser cet album photo, j'ai utilisé des photos gratuites et libres disponibles sur le site stockstockvault.net. Vous pouvez bien entendu utiliser vos propres photos, cependant pour simplifier le code il est préférable que celles-ci aient toutes les mêmes dimensions. Autrement voici les photos que j'ai sélectionné :

Image utilisateurImage utilisateurImage utilisateurImage utilisateurImage utilisateurImage utilisateurSans plus attendre, lançons-nous dans la conception de cet album photo !

Version avec boutons

En premier nous devons créer un nouveau projet que j'ai nommé AlbumPhoto, qui est donc identique au HelloWorld de départ :

Image utilisateur

Ensuite insérez-y les différentes photos de votre album dans le répertoire res/img. De mon côté j'y ai également mis deux icônes pour les boutons « Suivante » et « Précédente ».

Un ButtonField personnalisé

Tout d'abord nous allons créer un composant personnalisé à partir d'un ButtonField. Ce que nous voulons ici c'est redéfinir la largeur des boutons pour que ceux-ci prennent la totalité de la largeur de l'écran. Pour faire cela nous devrons réécrire la méthode getPreferredWidth(), qui permet également de récupérer la largeur d'un composant.
Je vous propose donc de créer une nouvelle classe héritant de ButtonField comme ceci :

package mypackage;

import net.rim.device.api.system.Display;
import net.rim.device.api.ui.component.ButtonField;

public class Bouton extends ButtonField{
    
    public Bouton(String nom){
        super(nom);
    }
    
    public int getPreferredWidth() {
        return (Display.getWidth()/2 - 35);
    }

}
La mise en page

Nous allons maintenant nous intéresser à la classe MyScreen qui hérite de MainScreen.
Tout d'abord, insérez ces attributs qui nous servirons pour le chapitre suivant :

int photoActuelle;
EncodedImage[] mesImages;
BitmapField maPhoto;
int echelle;

Nous avons donc un tableau d'EncodedImage nommé mesImages qui regroupe l'ensemble de nos photos. Ensuite nous avons un entier photoActuelle qui contiendra l'indice dans le tableau de la photo affichée en grand ainsi que le BitmapFieldmaPhoto qui la contiendra. Enfin l'entier echelle retiendra l'échelle de l'image que nous pourrons réutiliser pour la mise à jour de l'affichage.

Maintenant concentrons-nous sur ce que nous allons écrire à l'intérieur du constructeur MyScreen.
Commençons par définir la couleur d'arrière-plan en noir comme nous avons appris à le faire :

VerticalFieldManager monEcran = (VerticalFieldManager)getMainManager();
Background maCouleur = BackgroundFactory.createSolidBackground(Color.BLACK);
monEcran.setBackground(maCouleur);

Ensuite nous allons initialiser nos attributs sauf l'entier echelle qui nécessite au préalable d'avoir effectué quelques tracés.
Pour cela nous allons prendre la première image du tableau comme image de départ. Ensuite nous allons charger nos EncodedImage dans le tableau à l'aide d'une boucle, puis nous initialiserons notre BitmapField avec la première photo.
Voici l'ensemble de ces opérations :

photoActuelle = 0;
mesImages = new EncodedImage[6];
for(int i=0;i<6;i++){
    mesImages[i] = EncodedImage.getEncodedImageResource("photo"+(i+1)+".jpeg");
}
maPhoto = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
maPhoto.setImage(mesImages[photoActuelle]);

Occupons-nous à présent de nos boutons personnalisés créés dans la classe Bouton. Ayant déjà redéfini la largeur du composant dans la classe Bouton, nous pouvons maintenant utiliser ce composant de la même façon qu'un ButtonField.
Nous allons donc déclarer nos deux boutons « Précédente » et « Suivante », puis nous y ajouterons une icône grâce à la méthode setImage(). Pour finir nous déplacerons le texte à gauche de l'icône pour le deuxième bouton, voici ce que cela donne :

Bouton boutonPrec = new Bouton(" Précédente");
boutonPrec.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("precedent.png")));
Bouton boutonSuiv = new Bouton(" Suivante  ");
boutonSuiv.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("suivant.png")));
boutonSuiv.setLabelLeft();

Comme vous le voyez, les boutons sont alignés horizontalement, nous aurons donc besoin d'un conteneur de type HorizontalFieldManager. Nous pourrons alors ajouter nos différents éléments aux divers conteneurs dans un ordre précis :

HorizontalFieldManager mesBoutons = new HorizontalFieldManager(FIELD_HCENTER);
add(maPhoto);
add(new SeparatorField());
add(mesBoutons);
mesBoutons.add(boutonPrec);
mesBoutons.add(boutonSuiv);

Il ne nous reste plus qu'à redimensionner l'image centrale en fonction de la hauteur des boutons. Pour cela, nous initialiserons notre variable echelle dans un premier temps, puis nous mettrons à jour le BitmapField :

echelle = (int) Math.ceil(10000*mesImages[0].getHeight()/(Display.getHeight() - mesBoutons.getPreferredHeight() - 35));
maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));

Étant donné que ce code sera indispensable pour la suite, je vous ai réécrit l'ensemble de la classe MyScreen :

package mypackage;

import net.rim.device.api.math.Fixed32;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;
import net.rim.device.api.ui.image.ImageFactory;

public final class MyScreen extends MainScreen
{
    int photoActuelle;
    EncodedImage[] mesImages;
    BitmapField maPhoto;
    int echelle;
    
    public MyScreen()
    {        
        //Couleur d'arrière-plan
        VerticalFieldManager monEcran = (VerticalFieldManager)getMainManager();
        Background maCouleur = BackgroundFactory.createSolidBackground(Color.BLACK);
        monEcran.setBackground(maCouleur);
    	
        //Initialisation des attributs
        photoActuelle = 0;
        mesImages = new EncodedImage[6];
    	for(int i=0;i<6;i++){
    	    mesImages[i] = EncodedImage.getEncodedImageResource("photo"+(i+1)+".jpeg");
    	}
        maPhoto = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
        maPhoto.setImage(mesImages[photoActuelle]);
        
        //Préparation des boutons
    	Bouton boutonPrec = new Bouton(" Précédente");
        boutonPrec.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("precedent.png")));
        Bouton boutonSuiv = new Bouton(" Suivante  ");
        boutonSuiv.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("suivant.png")));
        boutonSuiv.setLabelLeft();
        
        //Mise en page des conteneurs
    	HorizontalFieldManager mesBoutons = new HorizontalFieldManager(FIELD_HCENTER);
        add(maPhoto);
        add(new SeparatorField());
        add(mesBoutons);
        mesBoutons.add(boutonPrec);
        mesBoutons.add(boutonSuiv);
        
        //Redimensionnement de l'image en cours d'affichage
    	echelle = (int) Math.ceil(10000*mesImages[0].getHeight()/(Display.getHeight() - mesBoutons.getPreferredHeight() - 35));
        maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));        
    }
}
Version avec miniatures

Pour concevoir l'album photo avec miniatures, nous allons reprendre le code précédent sur un certain nombre de points.
Pour commencer, ici nous n'aurons pas besoin de la classe Bouton. Dans la classe MyScreen, nous garderons exactement les mêmes attributs. Ensuite nous allons dans un premier temps garder les deux premiers blocs d'instructions du constructeur, à savoir :

//Couleur d'arrière-plan
VerticalFieldManager monEcran = (VerticalFieldManager)getMainManager();
Background maCouleur = BackgroundFactory.createSolidBackground(Color.BLACK);
monEcran.setBackground(maCouleur);

//Initialisation des attributs
photoActuelle = 0;
mesImages = new EncodedImage[6];
for(int i=0;i<6;i++){
    mesImages[i] = EncodedImage.getEncodedImageResource("photo"+(i+1)+".jpeg");
}
maPhoto = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
maPhoto.setImage(mesImages[photoActuelle]);

N'ayant plus de boutons ici, nous reprendrons directement la mise en page des conteneurs. Mettons en place le conteneur horizontal que j'ai renommé mesMiniatures, ainsi que le BitmapField et le SeparatorField, ce qui nous donne :

//Mise en page des conteneurs
HorizontalFieldManager mesMiniatures = new HorizontalFieldManager(FIELD_HCENTER);
add(maPhoto);
add(new SeparatorField());
add(mesMiniatures);

Nous arrivons maintenant à la partie intéressante de cette version : l'affichage des miniatures !
Pour réaliser cela nous allons utiliser une boucle. À l'intérieur, nous créerons pour chaque miniature un BitmapField à partir des EncodedImage de notre tableau mesImages. En considérant que ces photos ont toutes les mêmes dimensions, nous les remettrons à l'échelle de manière à ce que chacune d'elles fasse \frac{1}{6} de la largeur de l'écran. Puis nous les ajouterons une à une dans notre conteneur horizontal.
Voici le code qui résume tout ça :

//Affichage des vignettes
int taille = (int) Math.ceil(10000*6*mesImages[0].getWidth()/Display.getWidth());
for(int i=0;i<6;i++){
    BitmapField maMiniature = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
    maMiniature.setImage(mesImages[i].scaleImage32(Fixed32.tenThouToFP(taille), Fixed32.tenThouToFP(taille)));
    mesMiniatures.add(maMiniature);
}

Enfin, nous n'avons plus qu'à redimensionner l'image centrale en fonction de la hauteur des miniatures de la même manière que précédemment.

Pour récapituler tout cela, je vous propose la classe MyScreen dans son intégralité :

package mypackage;

import net.rim.device.api.math.Fixed32;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;
import net.rim.device.api.ui.image.ImageFactory;

public final class MyScreen extends MainScreen
{
    int photoActuelle;
    EncodedImage[] mesImages;
    BitmapField maPhoto;
    int echelle;
    
    public MyScreen()
    {        
        //Couleur d'arrière-plan
        VerticalFieldManager monEcran = (VerticalFieldManager)getMainManager();
        Background maCouleur = BackgroundFactory.createSolidBackground(Color.BLACK);
        monEcran.setBackground(maCouleur);
        
        //Initialisation des attributs
        photoActuelle = 0;
        mesImages = new EncodedImage[6];
        for(int i=0;i<6;i++){
            mesImages[i] = EncodedImage.getEncodedImageResource("photo"+(i+1)+".jpeg");
        }
        maPhoto = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
        maPhoto.setImage(mesImages[photoActuelle]);
    	
        //Mise en page des conteneurs
        HorizontalFieldManager mesMiniatures = new HorizontalFieldManager(FIELD_HCENTER);
        add(maPhoto);
        add(new SeparatorField());
        add(mesMiniatures);
        
        //Affichage des vignettes
        int taille = (int) Math.ceil(10000*6*mesImages[0].getWidth()/Display.getWidth());
        for(int i=0;i<6;i++){
            BitmapField maMiniature = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
            maMiniature.setImage(mesImages[i].scaleImage32(Fixed32.tenThouToFP(taille), Fixed32.tenThouToFP(taille)));
            mesMiniatures.add(maMiniature);
        }
        
        //Redimensionnement de l'image en cours d'affichage
        echelle = (int) Math.ceil(10000*mesImages[0].getHeight()/(Display.getHeight() - mesMiniatures.getPreferredHeight() - 35));
        maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));        
    }
}

Les différents conteneurs La gestion des évènements

La gestion des évènements

Un album photo Les évènements de boutons

Il est probable que vous vous demandiez ce qu'est un évènement, je vais donc essayer de vous expliquer ce concept !
Lorsqu'on conçoit une application, il est intéressant d'avoir de l'interaction avec l'utilisateur. Par exemple dans notre album photo, nous voudrions que l'image centrale soit mise à jour si on clique sur un des boutons ou encore si on touche une miniature sur l'écran. Hé bien les évènements servent justement à déclencher une fonction lorsqu'une action s'est produite.
Au cours de ce chapitre, nous allons donc découvrir comment gérer différents types d'évènements et mettre cela en pratique dans notre album photo !

Les évènements de boutons

La gestion des évènements Les évènements « tactiles »

Les évènements de boutons

La théorie

Pour exécuter une fonction à l'appui d'un bouton, nous utilisons ce que l'on appelle un écouteur.
Comme son nom l'indique, il va « écouter » d'éventuels évènements associés à un bouton. Pour cela nous allons utiliser la classe FieldChangeListener, et plus particulièrement nous allons redéfinir la méthode fieldChanged().
Ensuite il ne restera plus qu'à associer l'écouteur au composant grâce à la méthode setChangeListener() disponible pour tout type de bouton :

monBouton.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        // Instructions
    }
});
La gestion des boutons de l'album photo

Pour mieux comprendre, je vous propose de paramétrer nos boutons « Précédente » et « Suivante » de notre album photo.
Pour changer l'image affichée en fonction des boutons, il suffit de mettre à jour la variable photoActuelle, puis le BitmapField correspondant. Par exemple pour le bouton « Précédente », nous allons décrémenter photoActuelle puis appeler la méthode setImage() de la même manière que dans le chapitre précédent.
Voici ce que donne la gestion des évènements des deux boutons de l'album photo :

// Ecouteur du bouton "Précédente"
boutonPrec.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        if(photoActuelle > 0){
            photoActuelle--;
            maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
        }
    }
});
        
// Ecouteur du bouton "Suivante"
boutonSuiv.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        if(photoActuelle < 5){
            photoActuelle++;
            maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
        }
    }
});

Également je vous redonne l'intégralité de la classe MyScreen pour que tout le monde puisse tester cet album photo :

package mypackage;

import net.rim.device.api.math.Fixed32;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;
import net.rim.device.api.ui.image.ImageFactory;

public final class MyScreen extends MainScreen
{
    // Attributs
    int photoActuelle;
    EncodedImage[] mesImages;
    BitmapField maPhoto;
    int echelle;
    
    public MyScreen()
    {        
        //Couleur d'arrière-plan
        VerticalFieldManager monEcran = (VerticalFieldManager)getMainManager();
        Background maCouleur = BackgroundFactory.createSolidBackground(Color.BLACK);
        monEcran.setBackground(maCouleur);
        
        //Initialisation des attributs
        photoActuelle = 0;
        mesImages = new EncodedImage[6];
        for(int i=0;i<6;i++){
            mesImages[i] = EncodedImage.getEncodedImageResource("photo"+(i+1)+".jpeg");
        }
        maPhoto = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
        maPhoto.setImage(mesImages[photoActuelle]);
        
        //Préparation des boutons
        Bouton boutonPrec = new Bouton(" Précédente");
        boutonPrec.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("precedent.png")));
        Bouton boutonSuiv = new Bouton(" Suivante  ");
        boutonSuiv.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("suivant.png")));
        boutonSuiv.setLabelLeft();
        
        //Mise en page des conteneurs
        HorizontalFieldManager mesBoutons = new HorizontalFieldManager(FIELD_HCENTER);
        add(maPhoto);
        add(new SeparatorField());
        add(mesBoutons);
        mesBoutons.add(boutonPrec);
        mesBoutons.add(boutonSuiv);
        
        //Redimensionnement de l'image en cours d'affichage
        echelle = (int) Math.ceil(10000*mesImages[0].getHeight()/(Display.getHeight() - mesBoutons.getPreferredHeight() - 35));
        maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
        
        // Ecouteur du bouton "Précédente"
        boutonPrec.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                if(photoActuelle > 0){
                    photoActuelle--;
                    maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
                }
            }
        });
        
        // Ecouteur du bouton "Suivante"
        boutonSuiv.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                if(photoActuelle < 5){
                    photoActuelle++;
                    maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
                }
            }
        });        
    }
}

La gestion des évènements Les évènements « tactiles »

Les évènements « tactiles »

Les évènements de boutons Un peu plus loin...

Les évènements « tactiles »

La théorie

Les écrans tactiles sont maintenant omniprésents sur les terminaux mobiles. Nous allons maintenant découvrir comment gérer les évènements générés par l'utilisateur sur l'écran.
Pour gérer ces évènements nous allons devoir redéfinir la méthode touchEvent() de notre MainScreen. Pour cela je vous propose de découvrir directement la méthode en question que nous analyserons juste après :

protected boolean touchEvent(TouchEvent message) {
    
    // Récupération des informations liées à l'évènement
    int eventCode = message.getEvent();       
    int touchX =  message.getX(1);
    int touchY =  message.getY(1);
    
    // Gestion des différents évènements
    if(eventCode == TouchEvent.DOWN) {
        // Instructions
    }     
    if(eventCode == TouchEvent.UP) {
        // Instructions
    }    
    if(eventCode == TouchEvent.MOVE) {
        // Instructions
    }
    return true;
}

Dans un premier temps nous devons récupérer les informations liées à l'évènement. Pour cela nous utilisons la méthode getEvent() de la classe TouchEvent pour obtenir le type de l'évènement. Également nous allons récupérer la position de l'évènement par les méthodes getX() et getY() comme présenté ci-dessus.
Une fois ces informations connues, il est alors possible d'exécuter des instructions suivant l'évènement apparu ou la position de celui-ci à l'écran.

La gestion des miniatures de l'album photo

Revenons-en à notre album photo !
Dans le cas des miniatures, la gestion des évènements est également assez simple. Ici, nous nous occuperons uniquement du cas où l'utilisateur pose son doigt sur l'écran soit l'évènement TouchEvent.DOWN. L'opération consiste alors dans un premier temps à vérifier si la zone de l'écran touchée correspond à celle des miniatures. Pour cela il nous suffit de tester si la variable touchY est supérieure à la hauteur de l'image maPhoto.getPreferredHeight(). Ensuite nous devons récupérer le numéro de l'image sélectionné en faisant un petit calcul. Enfin nous n'aurons plus qu'à mettre à jour le BitmapField comme nous l'avons toujours fait jusqu'à présent :

protected boolean touchEvent(TouchEvent message) {
    
    // Récupération des informations liées à l'évènement
    int eventCode = message.getEvent();       
    int touchX =  message.getX(1);
    int touchY =  message.getY(1);
    
    // Gestion de l'évènement
    if(eventCode == TouchEvent.DOWN) {
        if(touchY > maPhoto.getPreferredHeight()){
            photoActuelle = (int) Math.ceil(6*touchX/Display.getWidth());
            maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
        }
    }
    return true;
}

Pour ceux qui seraient intéressés, voici l'intégralité de la classe MyScreen :

package mypackage;

import net.rim.device.api.math.Fixed32;
import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;

public final class MyScreen extends MainScreen
{
    int photoActuelle;
    EncodedImage[] mesImages;
    BitmapField maPhoto;
    int echelle;
    
    public MyScreen()
    {        
        //Couleur d'arrière-plan
        VerticalFieldManager monEcran = (VerticalFieldManager)getMainManager();
        Background maCouleur = BackgroundFactory.createSolidBackground(Color.BLACK);
        monEcran.setBackground(maCouleur);
        
        //Initialisation des attributs
        photoActuelle = 0;
        mesImages = new EncodedImage[6];
        for(int i=0;i<6;i++){
            mesImages[i] = EncodedImage.getEncodedImageResource("photo"+(i+1)+".jpeg");
        }
        maPhoto = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
        maPhoto.setImage(mesImages[photoActuelle]);
        
        //Mise en page des conteneurs
        HorizontalFieldManager mesMiniatures = new HorizontalFieldManager(FIELD_HCENTER);
        add(maPhoto);
        add(new SeparatorField());
        add(mesMiniatures);
        
        //Affichage des vignettes
        int taille = (int) Math.ceil(10000*6*mesImages[0].getWidth()/Display.getWidth());
        for(int i=0;i<6;i++){
            BitmapField maMiniature = new BitmapField(new Bitmap(0,0),FIELD_HCENTER);
            maMiniature.setImage(mesImages[i].scaleImage32(Fixed32.tenThouToFP(taille), Fixed32.tenThouToFP(taille)));
            mesMiniatures.add(maMiniature);
        }
        
        //Redimensionnement de l'image en cours d'affichage
        echelle = (int) Math.ceil(10000*mesImages[0].getHeight()/(Display.getHeight() - mesMiniatures.getPreferredHeight()));
        maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
        
    }
    
    protected boolean touchEvent(TouchEvent message) {
        
        //Récupération des informations liées à l'évènement
        int eventCode = message.getEvent();       
        int touchX =  message.getX(1);
        int touchY =  message.getY(1);
        
        // Gestion de l'évènement
        if(eventCode == TouchEvent.DOWN) {
            if(touchY > maPhoto.getPreferredHeight()){
                photoActuelle = (int) Math.ceil(6*touchX/Display.getWidth());
                maPhoto.setImage(mesImages[photoActuelle].scaleImage32(Fixed32.tenThouToFP(echelle), Fixed32.tenThouToFP(echelle)));
            }
        }
        return true;
    }

}

Je vous invite maintenant à tester cet album photo. Pour ceux qui voudraient aller plus loin, n'hésitez pas à ajouter de nouvelles fonctionnalités ou de retravailler l'interface à votre guise. Voici le visuel final de cette application :

Image utilisateur

Les évènements de boutons Un peu plus loin...

Un peu plus loin...

Les évènements « tactiles » Les vues personnalisées

Un peu plus loin...

Pour terminer ce chapitre sur les évènements, nous allons introduire un dernier point qui peut vous être utile dans vos futures applications.
Pour cela, revenons sur les instructions de récupération de position de l'évènement. Rappelez-vous :

int touchX =  message.getX(1);
int touchY =  message.getY(1);

Je vous avais introduit ces méthodes getX() et getY() sans vous expliquer pourquoi nous passions en paramètre le nombre 1.
En réalité ce chiffre correspond au numéro de l'évènement, je m'explique. Sur les écrans tactiles il est possible d'utiliser deux doigts en mode multipoints, et donc de gérer deux évènements simultanés à l'écran.
Ainsi, un deuxième évènement peut être traité en utilisant le code suivant :

protected boolean touchEvent(TouchEvent message) {
    switch(message.getEvent()) {
    case TouchEvent.MOVE:
        if(message.getX(1) > 0 && message.getY(1) > 0) {
            // Instructions pour la première position
        }
        if(message.getX(2) > 0 && message.getY(2) > 0) {
            // Instructions pour la seconde position
        }
        return true;
    }
    return false;
}

Comme vous avez dû le remarquer, un évènement est actif lorsque chacune de ses coordonnées est non nulle. Vous pouvez alors gérer chacun des évènements séparément pour ajouter toujours plus de fonctionnalités à votre application.


Les évènements « tactiles » Les vues personnalisées

Les vues personnalisées

Un peu plus loin... La structure de base

Nous avons vu jusqu'à maintenant comment réaliser des interfaces graphiques à l'aide de composants prédéfinis. À présent nous allons découvrir comment créer une vue à partir de zéro, pour créer une vue personnalisée.
Ce type de vue est plus complexe à mettre en place, notamment en ce qui concerne la gestion de différentes tailles d'écrans. Néanmoins cela permet d'avoir beaucoup plus de contrôle sur l'affichage, et sert également à personnaliser plus efficacement vos différentes vues.

La structure de base

Les vues personnalisées Insérer des contours

La structure de base

Nous allons ici voir le code minimal nécessaire pour créer des interfaces graphiques personnalisées. Pour cela nous allons créer une nouvelle classe nommée MaClasseDessin qui héritera de la classe MainScreen. Sans plus attendre, je vous laisse découvrir la structure de base que nous utiliserons pour notre nouvelle classe :

import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.container.MainScreen;

public class MaClasseDessin extends MainScreen{
    
    //Attributs
    	
    
    public MaClasseDessin(){
        //Initialisations des attributs
    }
    
    public void paint(Graphics g){
        super.paint(g);
        //Mise en place des éléments à l'écran
    }
    
    protected boolean touchEvent(TouchEvent message) {
        
        //Récupération des informations liées à l'évènement
        int eventCode = message.getEvent();       
        int touchX =  message.getX(1);
        int touchY =  message.getY(1);
        
        if(eventCode == TouchEvent.DOWN) {
            //Instructions lorsque l'utilisateur pose son doigt sur l'écran
            invalidate();
        }
        
        if(eventCode == TouchEvent.UP) {
            //Instructions lorsque l'utilisateur retire son doigt de l'écran
            invalidate();
        }
        
        if(eventCode == TouchEvent.MOVE) {
            //Instructions lorsque l'utilisateur bouge son doigt de l'écran        	
            invalidate();            
        }
        return true;
    }
    
}

Pour ceux qui l'auraient déjà utilisée, vous constaterez que cette structure est très proche de celle utilisée en Java :

package monpackage;

import java.awt.*;
import javax.swing.*;

public class JPanelDessin extends JPanel {
    
    //Attributs
    
    
    public JPanelDessin() {
        super();
        //Initialisations des attributs	
    }
    
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        //Mise en place des éléments à l'écran
    }
}

Pour étudier la structure de cette classe, je vous propose d'y aller étape par étape.

Pour commencer, voyons les attributs. Les attributs correspondent aux variables que nous manipulerons à l'intérieur de nos méthodes. Nous pourrons y trouver des variables de tous types, cependant nous y trouverons en particulier tous les éléments ou paramètres permettant de tracer l'interface.
Par exemple nous pourrions stocker une image à tracer ainsi que ses coordonnées à l'écran :

Bitmap monImage;
int[] position;

N'oubliez pas d'importer les classes que vous utilisez :

import net.rim.device.api.system.Bitmap;

Qui dit attributs dit initialisations, et pour cela nous avons le constructeur. Lorsque vous créez une classe qui hérite d'une autre, n'oubliez pas d'appeler le constructeur de la classe parente : ici la classe MainScreen. Voici donc notre constructeur :

public MaClasseDessin(){
    super();
    monImage = Bitmap.getBitmapResource("image.png");
    position = new int[2];
    position[0] = 0;
    position[1] = 0;
}

La méthode suivante est la plus importante : il s'agit de la méthode paint(). C'est à l'intérieur de celle-ci que nous définirons comment tracer les différents éléments à l'écran. Lorsque nous voudrons rafraîchir l'écran, c'est cette méthode qui sera appelée. C'est à nous de faire en sorte que l'élément soit affiché selon nos désirs. Dans notre exemple nous allons tracer l'image à la position souhaitée, c'est-à-dire aux coordonnées définies dans le tableau position.
Pour tracer nos éléments nous utiliserons des méthodes de la classe Graphics :

public void paint(Graphics g){
    super.paint(g);
    g.drawImage(position[0], position[1], 200, 200, monImage, 0, 0);
}

Enfin pour que notre application est de l'utilité, il va falloir gérer les évènements. Pour cela, je vous propose de tracer l'image monImage à la position où l'utilisateur pose le doigt. Voici donc la méthode touchEvent() proposée :

protected boolean touchEvent(TouchEvent message) {
    
    int eventCode = message.getEvent();       
    int touchX =  message.getX(1);
    int touchY =  message.getY(1);
    
    if(eventCode == TouchEvent.DOWN) {
        position[0] = touchX;
        position[1] = touchY;
        invalidate();
    }
    return true;
}

Dans cette méthode, on reçoit le paramètre de type TouchEvent grâce auquel nous allons pouvoir récupérer toutes les informations liées à l'évènement. Ainsi si l'utilisateur pose un doigt sur l'écran, alors nous mettons à jour la position de l'image.
La méthode invalidate() est très importante. C'est elle qui va appeler la méthode paint() et permettre de retracer les éléments à l'écran.

Voilà, nous avons fait le tour de cette classe. Pour finir, je vous ai donné la classe entière :

import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.system.Bitmap;

public class MaClasseDessin extends MainScreen{
    
    Bitmap monImage;
    int[] position;
    
    public MaClasseDessin(){
        super();
        monImage = Bitmap.getBitmapResource("image.png");
        position = new int[2];
        position[0] = 0;
        position[1] = 0;
    }
    
    public void paint(Graphics g){
        super.paint(g);
        g.drawImage(position[0], position[1], 200, 200, monImage, 0, 0);
    }
    
    protected boolean touchEvent(TouchEvent message) {
         
        int eventCode = message.getEvent();       
        int touchX =  message.getX(1);
        int touchY =  message.getY(1);
        
        if(eventCode == TouchEvent.DOWN) {
            position[0] = touchX;
            position[1] = touchY;
            invalidate();
        }
        return true;
    }

}

Les vues personnalisées Insérer des contours

Insérer des contours

La structure de base Insérer des remplissages

Insérer des contours

Nous allons à présent découvrir les différentes méthodes qui permettent de tracer différentes formes et contours. Toutes les dimensions sont ici exprimées en pixels.

Les bases
drawPoint

Rien de plus classique qu'un point !
Pour tracer un point utilisez la méthode drawPoint() en spécifiant les coordonnées du point, comme ceci :

g.drawPoint(10, 10);
Image utilisateur
drawLine

Comme son nom l'indique, cette méthode sert à tracer des lignes. Pour l'utiliser, renseignez les coordonnées du point de départ puis celles du point d'arrivée.
Voici un exemple :

g.drawLine(0, 0, 100, 50);
Image utilisateur
Les formes elliptiques
drawArc

La méthode drawArc() permet de tracer des arcs de cercle ou des cercles complets. Les différents paramètres dans l'ordre sont : les coordonnées x et y du centre du cercle, la largeur et la hauteur du cercle, et enfin l'angle de départ et celui de fin du tracé.
L'exemple ci-dessous trace un cercle presque complet entre 0° et 300° :

g.drawArc(100, 100, 50, 50, 0, 300);
Image utilisateur
drawEllipse

Cette méthode trace également un arc mais de forme elliptique. Pour l'utiliser, spécifiez dans l'ordre : les coordonnées du centre, les coordonnées d'un point appartenant à l'arc, les coordonnées d'un second point appartenant à l'arc, puis les angles de départ et d'arrivée.

g.drawEllipse(100, 200, 50, 200, 100, 225, 0, 270);
Image utilisateur
Les rectangles
drawRect

Pour la méthode drawRect(), il n'y a rien de compliqué. Placez en paramètres les coordonnées de l'angle supérieur gauche ainsi que la largeur et la hauteur du rectangle, comme ceci :

g.drawRect(100, 10, 100, 50);
Image utilisateur
drawRoundRect

Cette méthode est identique à la précédente, simplement rajoutez la largeur et la hauteur de l'arrondi.

g.drawRoundRect(100, 10, 100, 50, 30, 30);
Image utilisateur
Quelques plus
drawText

Il est également possible d'écrire du texte grâce à la méthode drawText(). Renseignez votre texte à afficher ainsi que sa position.

g.drawText("Texte", 100, 25);
Image utilisateur
setColor

La méthode setColor() permet de choisir la couleur des contours mais également celle des remplissages. La couleur s'écrit sous la forme : 0xAARRVVBB, où AA est la valeur hexadécimale de la transparence et RR, VV, BB celles des couleurs rouge, vert et bleu.
Voici un exemple qui modifie la couleur des tracés en bleu :

g.setColor(0x000000FF);
g.drawText("Texte", 100, 25);
Image utilisateur
clear

Enfin, notez l'existence de la méthode clear() qui sert à effacer l'écran, c'est-à-dire le rafraîchir entièrement de la couleur d'arrière-plan.

g.clear();

La structure de base Insérer des remplissages

Insérer des remplissages

Insérer des contours TP : un taquin

Insérer des remplissages

Les essentiels
fillArc

Cette méthode fonctionne identiquement à drawArc(), cependant celle-ci remplit l'intérieur de l'arc de la couleur actuelle de dessin.

g.fillArc(50, 100, 50, 50, 0, 180);
Image utilisateur
fillEllipse

La méthode fillEllipse() fonctionne également comme drawEllipse().

g.fillEllipse(300, 100, 280, 100, 280, 140, 0, 360);
Image utilisateur
fillRect

Pour créer des remplissages de rectangles, utilisez la méthode fillRect().

g.fillRect(100, 10, 100, 50);
Image utilisateur
fillRoundRect

Enfin pour finir, voici la méthode fillRoundRect() :

g.fillRoundRect(100, 10, 100, 50, 30, 30);
Image utilisateur
Un peu de couleur
drawGradientFilledRect

La méthode drawGradientFilledRect() permet de créer un remplissage de couleur dégradée à l'intérieur d'un rectangle. Celle-ci s'utilise de la même manière que fillRect(), en précisant en plus les couleurs de départ et d'arrivée du dégradé.
Voici un exemple de dégradé du bleu au rouge :

g.drawGradientFilledRect(100, 10, 100, 50, 0x000000FF, 0x00FF0000);
Image utilisateur
drawGradientFilledRoundedRect

Cette méthode est similaire à la précédente, en y ajoutant les arrondis :

g.drawGradientFilledRoundedRect(100, 10, 100, 50, 0x00FFFF00, 0x00FF00FF, 30, 30);
Image utilisateur
setBackbroungColor

Pendant que nous sommes dans les couleurs, nous allons présenter la méthode setBackbroungColor() qui sert à choisir la couleur d'arrière-plan. Celle-ci s'utilise simplement en renseignant la couleur désirée.
Voici par exemple comment créer un fond noir :

g.setBackgroundColor(0x00000000);
Les images

Pour terminer ce chapitre, nous allons voir comment vous pouvez tracer des images grâce à la classe Graphics. Pour cela, vous disposez de deux méthodes qui dépendent du type d'image que vous avez chargé.

drawBitmap

Si vous avez chargé votre image avec la classe Bitmap, vous devrez alors utiliser la méthode drawBitmap(), comme ceci :

Bitmap monImage = Bitmap.getBitmapResource("zozor.png");
g.drawBitmap(100, 50, 200, 200, monImage, 0, 0);

Les deux premiers paramètres correspondent à la position de l'image. Nous reviendrons sur les autres paramètres dans très peu de temps.
Vous devriez donc voir apparaître Zozor à la position spécifiée :

Image utilisateur
drawImage

Si vous utilisez des images de type EncodedImage, vous devrez alors utiliser la méthode drawImage() pour afficher cette image à l'écran. Celle-ci est quasiment identique à la méthode précédente :

EncodedImage monImage = EncodedImage.getEncodedImageResource("zozor.png");
g.drawImage(100, 50, 200, 200, monImage, 0, 0, 0);

Le rendu est donc le même :

Image utilisateur
Rogner l'image

Dans les deux méthodes, différents paramètres permettent de rogner l'image selon vos désirs. Ainsi dans la méthode drawImage(), les troisième et quatrième paramètres permettent de définir les dimensions de l'image. Si celles-ci sont inférieures à la taille de l'image, alors l'image est rognée. Il est également possible de décaler ce rognage grâce aux deux derniers paramètres. Pour mieux comprendre comment cela fonctionne, n'hésitez pas à tester différentes valeurs.
Voici un exemple que je vous propose :

g.drawImage(100, 30, 20, 50, monImage, 0, 20, 0);

Cet exemple va donc rogner l'image à gauche, à droite ainsi qu'en bas comme montré ci-dessous :

Image utilisateur

Insérer des contours TP : un taquin

TP : un taquin

Insérer des remplissages Le cahier des charges

Il est temps pour vous de pratiquer un peu !
Nous allons donc dans ce TP réaliser un jeu de taquin depuis zéro. Pour la correction, nous procèderons étape par étape pour vous permettre de comprendre au mieux. L'objectif de ce chapitre n'est donc pas de vous faire voir de nouvelles notions, mais uniquement de vous faire pratiquer avec les connaissances que vous avez déjà. Ce sera donc l'occasion de faire un point sur ce que vous avez retenu jusqu'à présent.

Le cahier des charges

TP : un taquin La correction

Le cahier des charges

Qu'est-ce qu'un taquin ?

Image utilisateur

Le taquin est un jeu solitaire créé il y a environ 150 ans.
À l'origine, ce jeu était composé de 15 carreaux numérotés de 1 à 15 glissant dans un cadre prévu pour 16. La pièce manquante s’appelait le « blanc ». Il fallait alors déplacer une pièce adjacente au « blanc » pour réorganiser le jeu. Dans un taquin, le but du jeu consiste alors à remettre les carreaux dans l'ordre d'après une configuration initiale quelconque.
De nos jours, le taquin est souvent représenté sous la forme d'une image à reconstituer. On en retrouve de partout, notamment sur internet.

Spécifications du projet

Au cours de ce TP, nous allons réaliser un taquin aux couleurs du site. Vous pourrez utiliser vos propres images ou bien récupérer les miennes plus bas. Nous devrons donc créer une application qui sera soumise à certaines contraintes. Je vous propose de faire un rapide tour de ce que nous devrons prendre en compte lors de sa conception :

Enfin, voici un aperçu de ce que nous pourrions avoir à l'écran lors du déroulement du jeu :

Image utilisateur

Pourquoi avoir choisi ce jeu ?

Pour commencer le taquin est un jeu assez populaire, vous y avez probablement tous joué !
Ce jeu est intéressant, je vous assure que vous y jouerez un sacré moment une fois le TP terminé. Les règles sont simples, ce qui facilite la mise en place du cahier des charges. De plus, la logique simple du jeu facilitera la conception de l'application, bien qu'il nous faudra plusieurs classes pour parvenir au bout du TP.
D'autre part, je trouve qu'un jeu « manuel » tel que le taquin s'intègre très bien sur les plateformes tactiles. En effet, comme sur un vrai jeu nous déplacerons les cases à l'aide des doigts. Bien gérer ces déplacements rendra le jeu plus interactif et plus fluide, c'est ce qui fait la popularité de ce genre d'applications.

Avant de commencer

Ce jeu peut être codé de plusieurs manières différentes, c'est pourquoi je vous invite fortement à essayer de le réaliser par vous-même. Vous pourrez alors comparer votre façon d’appréhender le problème avec la mienne. Ma méthode n'est pas meilleure qu'une autre, il est possible que vous trouviez une méthode plus simple et demandant moins de lignes de code.
Quoi qu'il en soit, pour réaliser ce TP il se peut que vous soyez obligé d'y passer plusieurs jours. N'hésitez pas à prendre votre temps !
Voici les vignettes que j'ai utilisé pour réaliser mon taquin :

Vignette 1
Vignette 1
Vignette 2
Vignette 2
Vignette 3
Vignette 3
Vignette 4
Vignette 4
Vignette 5
Vignette 5
Vignette 6
Vignette 6
Vignette 7
Vignette 7
Vignette 8
Vignette 8
Vignette 9
Vignette 9
Vignette 10
Vignette 10
Vignette 11
Vignette 11

Allons-y !


TP : un taquin La correction

La correction

Le cahier des charges Les explications

La correction

Avant de passer à de plus amples explications, je vous ai ici mis l'ensemble du code source. Cela me permettra dans un premier temps, de vous présenter les différentes classes que j'ai utilisé. Pour réaliser cette application, je me suis servi de cinq classes dont la fonction vous est présentée juste après.
N'hésitez pas à tester cette application en copiant-collant le code source des différentes classes ci-dessous !

La classe MonApp

La classe MonApp est comme depuis le début, la classe de départ de notre programme. Elle n'est pas différente des précédentes, c'est pourquoi nous n'y reviendrons pas dans les explications. Celle-ci se contente simplement de faire appel à la classe MonDessin décrite plus bas. Vous trouverez néanmoins le code source de cette classe ci-dessous :

package sdz.taquin;

import net.rim.device.api.ui.UiApplication;

public class MonApp extends UiApplication
{
    public static void main(String[] args)
    {
        MonApp theApp = new MonApp();       
        theApp.enterEventDispatcher();
    }

    public MonApp()
    {        
        pushScreen(new MonDessin());
    }    
}
La classe MonDessin

La classe MonDessin reprend la syntaxe de la classe MaClasseDessin décrite dans le chapitre sur les vues personnalisées. Nous utilisons dans cette classe tout ce qui avait été vu dans ce précédent chapitre. Nous la détaillerons plus bas, mais en attendant voici le code correspondant :

package sdz.taquin;

import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.TouchEvent;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.container.MainScreen;

public class MonDessin extends MainScreen{
    
    private int echelle;
    private Vignette[] mesVignettes;
    private Partie maPartie;
    private Mouvement monMouvement;
    	
    
    public MonDessin(){
        super();
        echelle = Math.min(Display.getWidth()/320, Display.getHeight()/240);
        maPartie = new Partie();
        monMouvement = new Mouvement();
        mesVignettes = new Vignette[11];
        for(int i=0; i<4; i++){
            for(int j=0; j<3; j++){
                if(maPartie.getVignette(i,j)!=0){
                    mesVignettes[maPartie.getVignette(i,j)-1] = new Vignette(maPartie.getVignette(i,j),echelle, i, j);
                }
            }
        }
    }
    
    public void paint(Graphics g){
        super.paint(g);
        g.setBackgroundColor(0x00000000);
        g.setColor(0x00FFFFFF);
        g.clear();
        for(int i=0; i<4; i++){
            for(int j=0; j<3; j++){
                if(maPartie.getVignette(i,j)!=0){
                    int x = mesVignettes[maPartie.getVignette(i,j)-1].getX();
                    int y = mesVignettes[maPartie.getVignette(i,j)-1].getY();
                    EncodedImage img = mesVignettes[maPartie.getVignette(i,j)-1].getImage();
                    g.drawImage(x, y, 80*echelle, 80*echelle, img, 0, 0, 0);
                    g.drawText(""+maPartie.getVignette(i, j), x+10, y+10);
                }else{
                    g.drawText(""+maPartie.getVignette(i, j), i*80*echelle+10, j*80*echelle+10);
                }
            }
        }
    }
    
    protected boolean touchEvent(TouchEvent message) {
        
        int eventCode = message.getEvent();       
        int touchX =  message.getX(1);
        int touchY =  message.getY(1);
        
        if(eventCode == TouchEvent.DOWN) {
            int i = touchX/(80*echelle);
            int j = touchY/(80*echelle);
            monMouvement.setNumero(maPartie.getVignette(i,j));
            int[][] mouv = maPartie.estJouable(i,j, echelle);
            monMouvement.setMouvements(mouv);
            monMouvement.setAnterieur(touchX, touchY);
            invalidate();
        }
        
        if(eventCode == TouchEvent.UP) {
            if(monMouvement.getNumero()!=0 && monMouvement.estActif()==true){
                int i = (mesVignettes[monMouvement.getNumero()-1].getX()+40*echelle)/(80*echelle);
                int j = (mesVignettes[monMouvement.getNumero()-1].getY()+40*echelle)/(80*echelle);
                monMouvement.setMouvements();
                maPartie.placer(i, j, monMouvement.getNumero());
                mesVignettes[monMouvement.getNumero()-1].setPosition(i*80*echelle, j*80*echelle);
            }
            invalidate();
            if(maPartie.estEnPlace()==true){
                Dialog.alert("Bravo, vous avez réussi!");
                System.exit(0);
                return true;
            }
        }
        
        if(eventCode == TouchEvent.MOVE) {
            int mouvX = touchX - monMouvement.getAnterieur()[0];
            int mouvY = touchY - monMouvement.getAnterieur()[1];
            if(monMouvement.getNumero()!=0 && monMouvement.estActif()==true){
                if((mesVignettes[monMouvement.getNumero()-1].getX()+mouvX)<monMouvement.getMouvements()[0][0]){
                    mesVignettes[monMouvement.getNumero()-1].setX(monMouvement.getMouvements()[0][0]);
                }else if((mesVignettes[monMouvement.getNumero()-1].getX()+mouvX)>monMouvement.getMouvements()[0][1]){
                    mesVignettes[monMouvement.getNumero()-1].setX(monMouvement.getMouvements()[0][1]);
                }else{
                    mesVignettes[monMouvement.getNumero()-1].addX(mouvX);
                }
                if((mesVignettes[monMouvement.getNumero()-1].getY()+mouvY)<monMouvement.getMouvements()[1][0]){
                    mesVignettes[monMouvement.getNumero()-1].setY(monMouvement.getMouvements()[1][0]);
                }else if((mesVignettes[monMouvement.getNumero()-1].getY()+mouvY)>monMouvement.getMouvements()[1][1]){
                    mesVignettes[monMouvement.getNumero()-1].setY(monMouvement.getMouvements()[1][1]);
                }else{
                    mesVignettes[monMouvement.getNumero()-1].addY(mouvY);
                }
            }
            monMouvement.setAnterieur(touchX, touchY);
            invalidate();            
        }
        return true;
    }
    
}
La classe Vignette

Cette classe nommée Vignette rassemble toutes les informations liées à chaque case du jeu. On y retrouve notamment le numéro de la case, l'image à tracer ainsi que sa position à l'écran. Cette classe regroupe également l'ensemble des méthodes permettant de gérer et modifier ces attributs. Voici le code de cette classe :

package sdz.taquin;

import net.rim.device.api.math.Fixed32;
import net.rim.device.api.system.EncodedImage;

public class Vignette {
    
    private int numero;
    private EncodedImage image;
    private int[] position;
    
    public Vignette(int i, int echelle, int x, int y){
        numero = i;
        int taille = (int) Math.ceil(10000/echelle);
        EncodedImage img = EncodedImage.getEncodedImageResource("vignette"+numero+".png");
        image = img.scaleImage32(Fixed32.tenThouToFP(taille), Fixed32.tenThouToFP(taille));
        position = new int[2];
        position[0] = x*echelle*80;
        position[1] = y*echelle*80;
    }
    
    public EncodedImage getImage(){
        return image;
    }
    
    public int getX(){
        return position[0];
    }
    
    public int getY(){
        return position[1];
    }
    
    public void setPosition(int x, int y){
        position[0] = x;
        position[1] = y;
    }
    
    public void setX(int x){
        position[0] = x;
    }
    
    public void setY(int y){
        position[1] = y;
    }
    
    public void addX(int x){
        position[0] += x;
    }
    
    public void addY(int y){
        position[1] += y;
    }
}
La classe Partie

La classe Partie sert à la gestion du jeu en cours. Elle possède en unique attribut un tableau décrivant l'état du jeu ainsi que toutes les méthodes nécessaires au bon déroulement du jeu. Le code source de cette classe est écrit ci-dessous :

package sdz.taquin;

import java.util.Random;

public class Partie {
    
    private int[][] jeu = new int[4][3];
    
    public Partie(){
        melanger(30);
    }
	
    public int getVignette(int i, int j){
        return jeu[i][j];
    }
	
    public void melanger(int n){
        for(int i=0; i<4; i++){
            for(int j=0; j<3; j++){
                jeu[i][j] = i+j*4;
            }
        }
        int k = n;
        int[] positionZero = {0,0};
        Random monRandom = new Random();
        do{
            int nb = monRandom.nextInt(4);
            switch(nb){
                case 0:
                     if(positionZero[0]>0){
                         jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]-1][positionZero[1]];
                         jeu[positionZero[0]-1][positionZero[1]]=0;
                         positionZero[0]--;
                         k--;
                     }
                     break;
                 case 1:
                     if(positionZero[1]>0){
                         jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]][positionZero[1]-1];
                         jeu[positionZero[0]][positionZero[1]-1]=0;
                         positionZero[1]--;
                         k--;
                     }
                     break;
                 case 2:
                     if(positionZero[0]<3){
                         jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]+1][positionZero[1]];
                         jeu[positionZero[0]+1][positionZero[1]]=0;
                         positionZero[0]++;
                         k--;
                     }
                     break;
                 case 3:
                     if(positionZero[1]<2){
                         jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]][positionZero[1]+1];
                         jeu[positionZero[0]][positionZero[1]+1]=0;
                         positionZero[1]++;
                         k--;
                     }
                     break;
             }
        }while(k>0);
    }
    
    public int[][] estJouable(int i, int j, int echelle){
        int[][] mouvements = {{i*echelle*80,i*echelle*80},{j*echelle*80,j*echelle*80}};
        if(i>0){
            if(jeu[i-1][j]==0){
                mouvements[0][0] = (i-1)*echelle*80;
            }
        }
        if(j>0){
            if(jeu[i][j-1]==0){
                mouvements[1][0] = (j-1)*echelle*80;
            }
        }
        if(i<3){
            if(jeu[i+1][j]==0){
                mouvements[0][1] = (i+1)*echelle*80;
            }
        }
        if(j<2){
            if(jeu[i][j+1]==0){
                mouvements[1][1] = (j+1)*echelle*80;
            }
        }
        return mouvements;
    }
    
    public void placer(int i, int j, int numero){
        if(jeu[i][j]==0){
            jeu[i][j]=numero;
            if(i>0){
                if(jeu[i-1][j]==numero){
                    jeu[i-1][j]=0;
                }
            }
            if(j>0){
                if(jeu[i][j-1]==numero){
                    jeu[i][j-1]=0;
                }
            }
            if(i<3){
                if(jeu[i+1][j]==numero){
                    jeu[i+1][j]=0;
                }
            }
            if(j<2){
                if(jeu[i][j+1]==numero){
                    jeu[i][j+1]=0;
                }
            }
        }
    }
    
    public boolean estEnPlace(){
        boolean resultat = true;
        for(int i=0; i<4; i++){
            for(int j=0; j<3; j++){
                if(jeu[i][j] != i+j*4){
                    resultat = false;
                }
            }
        }
        return resultat;
    }
}
La classe Mouvement

La classe Mouvement permet de gérer les mouvements d'une vignette, en particulier lors d'un mouvement transitoire de vignette d'une position à une autre. Ces mouvements ne modifient pas directement l'état du jeu, cependant l'affichage à l'écran doit tout de même être mis à jour. Voici le code de cette classe :

package sdz.taquin;

public class Mouvement {
     
    private int numero;
    public boolean actif;
    private int[][] mouvements;
    private int[] positionAnt;
    
    public Mouvement(){
        numero=0;
        actif = false;
        mouvements = new int[2][2];
        positionAnt = new int[2];
        positionAnt[0] = 160;
        positionAnt[1] = 0;
    }
    
    public void setMouvements(){
        actif = false;
    }
    
    public void setMouvements(int[][] mouv){
        mouvements = mouv;
        actif = false;
        if(mouv[0][0]!=mouv[0][1] || mouv[1][0]!=mouv[1][1]){
            actif = true;
        }
    }
    
    public void setNumero(int num){
        numero = num;
    }
    
    public void setAnterieur(int x, int y){
        positionAnt[0] = x;
        positionAnt[1] = y;
    }
    
    public int getNumero(){
        return numero;
    }
    
    public boolean estActif(){
        return actif;
    }
    
    public int[][] getMouvements(){
        return mouvements;
    }
    
    public int[] getAnterieur(){
        return positionAnt;
    }
}

Le cahier des charges Les explications

Les explications

La correction Travailler avec plusieurs vues

Les explications

À présent, nous allons étudier le contenu de ce code source plus en détails. Encore une fois, la méthode utilisée pour réaliser ce jeu est propre à chacun. Ainsi, la « correction » proposée ci-dessous n'est pas la seule manière de concevoir un jeu de taquin.

La structure du jeu
Préparation des vignettes

Vous vous en doutez certainement, nous allons parler ici de la classe Vignette. Comme dit plus haut, cette classe rassemble toutes les informations liées à chaque case du jeu et nous y trouvons le numéro de la case, l'image à tracer ainsi que sa position à l'écran. Voici donc ces différents attributs :

private int numero;
private EncodedImage image;
private int[] position;

Jusque-là, rien de bien impressionnant. En revanche, le constructeur de la classe est légèrement plus pointu : il va nous falloir initialiser ces attributs. Pour cela nous aurons besoin d'un certain nombre de paramètres :

Voici donc le prototype de notre constructeur :

public Vignette(int i, int echelle, int x, int y){
    // Instructions
}

Le numéro de la vignette doit être stocké, nous pouvons directement initialiser notre attribut numero. De plus en sachant que nos vignettes font à l'origine 80 imes 80 pixels, nous pouvons également initialiser la position de la vignette :

numero = i;
position = new int[2];
position[0] = x*echelle*80;
position[1] = y*echelle*80;

Enfin pour finir, il nous faut charger l'image correspondant à la vignette. Nous allons alors simplement utiliser la méthode présentée au chapitre précédent, à savoir :

int taille = (int) Math.ceil(10000/echelle);
EncodedImage img = EncodedImage.getEncodedImageResource("vignette"+numero+".png");
image = img.scaleImage32(Fixed32.tenThouToFP(taille), Fixed32.tenThouToFP(taille));

L'utilité de cette classe réside uniquement dans ses attributs. C'est pourquoi, les seules méthodes présentes dans cette classe sont exclusivement des accesseurs. Nous ne les détaillerons pas, je vous laisse le soin de les découvrir.

Le cœur du jeu

Comme l'indique le titre, nous allons ici nous occuper du cœur de jeu, à savoir la gestion de la partie et de la disposition des vignettes. Nous avons donc besoin d'une nouvelle classe nommée Partie. Celle-ci possède un seul attribut : il s'agit d'un tableau 2D comportant le numéro des vignettes à chaque position. Celles-ci sont numérotées de 1 à 11, le 0 étant réservé au « blanc ». Dans un premier temps, nous pouvons placer les vignettes dans l'ordre. Voici donc le code correspondant à la déclaration de l'attribut ainsi que le constructeur provisoire :

private int[][] jeu = new int[4][3];

public Partie(){
    for(int i=0; i<4; i++){
        for(int j=0; j<3; j++){
            jeu[i][j] = i+j*4;
        }
    }
}

Attaquons-nous maintenant aux différentes méthodes dont nous aurons besoin. Attaquons par un accesseur, voici une méthode permettant de récupérer le numéro d'une vignette suivant sa position dans le tableau :

public int getVignette(int i, int j){
    return jeu[i][j];
}

Ensuite, voici une méthode permettant de savoir si les vignettes sont en place. Celle-ci nous renvoie un booléen suivant le résultat.

public boolean estEnPlace(){
    boolean resultat = true;
    for(int i=0; i<4; i++){
        for(int j=0; j<3; j++){
            if(jeu[i][j] != i+j*4){
                resultat = false;
            }
        }
    }
    return resultat;
}

La méthode suivante permet de savoir si une vignette peut être déplacée. À partir de la position d'une vignette dans le tableau, nous allons scruter les éléments adjacents pour savoir si l'un d'eux est le « blanc ». Dans ce cas la vignette peut être déplacée dans cette direction. En retour, nous renvoyons un tableau 2D indiquant quels sont les déplacements possibles en pixels.

public int[][] estJouable(int i, int j, int echelle){
    int[][] mouvements = {{i*echelle*80,i*echelle*80},{j*echelle*80,j*echelle*80}};
    if(i>0){
        if(jeu[i-1][j]==0){
            mouvements[0][0] = (i-1)*echelle*80;
        }
    }
    if(j>0){
        if(jeu[i][j-1]==0){
            mouvements[1][0] = (j-1)*echelle*80;
        }
    }
    if(i<3){
        if(jeu[i+1][j]==0){
            mouvements[0][1] = (i+1)*echelle*80;
        }
    }
    if(j<2){
        if(jeu[i][j+1]==0){
            mouvements[1][1] = (j+1)*echelle*80;
        }
    }
    return mouvements;
}

Puis, nous aurons également besoin d'une méthode pour déplacer un élément. Nous plaçons alors cet élément à l'endroit spécifié, et nous remplaçons son ancienne position par le « blanc ».

public void placer(int i, int j, int numero){
    if(jeu[i][j]==0){
        jeu[i][j]=numero;
        if(i>0){
            if(jeu[i-1][j]==numero){
                jeu[i-1][j]=0;
            }
        }
        if(j>0){
            if(jeu[i][j-1]==numero){
                jeu[i][j-1]=0;
            }
        }
        if(i<3){
            if(jeu[i+1][j]==numero){
                jeu[i+1][j]=0;
            }
        }
        if(j<2){
            if(jeu[i][j+1]==numero){
                jeu[i][j+1]=0;
            }
        }
    }
}

Enfin pour finir, nous allons modifier le constructeur pour avoir un jeu mélangé en début de partie. C'est là que nous allons introduire la méthode melanger(). Pour mélanger ce jeu, nous devons retenir la position du « blanc ». Ensuite il nous suffit d'intervertir sa place avec l'un de ses voisins. Après avoir réitéré l'opération plusieurs fois, nous avons un jeu suffisamment mélangé. Voici le code correspondant :

public Partie(){
    melanger(30);
}
	
public void melanger(int n){
    for(int i=0; i<4; i++){
        for(int j=0; j<3; j++){
            jeu[i][j] = i+j*4;
        }
    }
    int k = n;
    int[] positionZero = {0,0};
    Random monRandom = new Random();
    do{
        int nb = monRandom.nextInt(4);
        switch(nb){
            case 0:
                if(positionZero[0]>0){
                    jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]-1][positionZero[1]];
                    jeu[positionZero[0]-1][positionZero[1]]=0;
                    positionZero[0]--;
                    k--;
                }
                break;
            case 1:
                if(positionZero[1]>0){
                    jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]][positionZero[1]-1];
                    jeu[positionZero[0]][positionZero[1]-1]=0;
                    positionZero[1]--;
                    k--;
                }
                break;
            case 2:
                if(positionZero[0]<3){
                    jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]+1][positionZero[1]];
                    jeu[positionZero[0]+1][positionZero[1]]=0;
                    positionZero[0]++;
                    k--;
                }
                break;
            case 3:
                if(positionZero[1]<2){
                    jeu[positionZero[0]][positionZero[1]]=jeu[positionZero[0]][positionZero[1]+1];
                    jeu[positionZero[0]][positionZero[1]+1]=0;
                    positionZero[1]++;
                    k--;
                }
                break;
        }
    }while(k>0);
}

Nous voilà enfin au bout de cette classe. N'hésitez pas à relire cette partie plusieurs fois pour bien comprendre le fonctionnement.

La gestion des mouvements

Nous allons ici introduire la classe Mouvement. Celle-ci est en charge des mouvements transitoires de vignettes d'une position à une autre. Pour pouvoir gérer ces déplacements, nous aurons besoin de différents attributs. Nous allons définir le numéro de la vignette sélectionnée, les mouvements possibles de cette vignette ainsi que la position de l'évènement précédent pour pouvoir calculer le déplacement du doigt à l'écran. Un dernier attribut permet de définir si le déplacement est en cours ou non, c'est-à-dire si l'utilisateur a toujours le doigt sur l'écran ou non. Voici donc la liste des attributs définis pour cette classe :

private int numero;
public boolean actif;
private int[][] mouvements;
private int[] positionAnt;

Le constructeur ne mérite pas de longues explications. Les attributs peuvent être initialisés avec n'importe quelles valeurs, du moment que l'attribut actif est faux. Le constructeur est défini ci-dessous :

public Mouvement(){
    numero=0;
    actif = false;
    mouvements = new int[2][2];
    positionAnt = new int[2];
    positionAnt[0] = 160;
    positionAnt[1] = 0;
}

Dans cette classe encore, nous retrouvons principalement des accesseurs. Nous ne les détaillerons pas tous, mais nous étudierons quand même les méthodes nommées setMouvements(). La première permet de mettre à jour l'attribut mouvements ainsi que d'autoriser le mouvement si la vignette peut être déplacée :

public void setMouvements(int[][] mouv){
    mouvements = mouv;
    actif = false;
    if(mouv[0][0]!=mouv[0][1] || mouv[1][0]!=mouv[1][1]){
        actif = true;
    }
}

Et pour finir, la deuxième méthode permet de désactiver le mouvement :

public void setMouvements(){
    actif = false;
}

Nous voilà fin prêt, il ne nous reste plus qu'à tracer les éléments à l'écran.

Le traçage à l'écran

Maintenant que tout est prêt, il nous faut rassembler l'ensemble et tracer les différents éléments. Tout ceci, nous allons le faire à l'intérieur de la classe MonDessin. Nous utiliserons toutes les notions vues au chapitre précédent sur les vues personnalisées.
Faisons à présent un tour des attributs dont nous aurons besoin. Tout d'abord, nous prendrons une variable pour retenir l'échelle de l'écran (jeu en 320 imes 240 pixels). Ensuite nous aurons besoin de charger l'ensemble des vignettes, nous utiliserons un tableau de Vignette. Enfin nous prendrons une occurrence de chacune des deux classes Partie et Mouvement. Voici les attributs :

private int echelle;
private Vignette[] mesVignettes;
private Partie maPartie;
private Mouvement monMouvement;

Aucune surprise dans le constructeur si ce n'est la nécessité de réaliser une boucle pour faire appel aux constructeurs de la classe Vignette. Voici ce constructeur :

public MonDessin(){
    super();
    echelle = Math.min(Display.getWidth()/320, Display.getHeight()/240);
    maPartie = new Partie();
    monMouvement = new Mouvement();
    mesVignettes = new Vignette[11];
    for(int i=0; i<4; i++){
        for(int j=0; j<3; j++){
            if(maPartie.getVignette(i,j)!=0){
                mesVignettes[maPartie.getVignette(i,j)-1] = new Vignette(maPartie.getVignette(i,j),echelle, i, j);
            }
        }
    }
}

Nous voici arrivés à l'endroit qui va donner vie à notre jeu. Vous l'aurez deviné, il s'agit bien évidemment de la gestion des évènements. Nous utiliserons les évènements suivants: TouchEvent.DOWN, TouchEvent.UP et TouchEvent.MOVE.
Pour commencer, nous allons nous occuper du cas où l'utilisateur pose son doigt sur l'écran. Il faut alors dans un premier temps trouver quelle vignette a été touchée. Ensuite nous regardons si cette vignette peut être déplacée. Puis, il ne nous reste plus qu'à mettre à jour les attributs de la classe Mouvement. Voici donc le code correspondant :

if(eventCode == TouchEvent.DOWN) {
    int i = touchX/(80*echelle);
    int j = touchY/(80*echelle);
    monMouvement.setNumero(maPartie.getVignette(i,j));
    int[][] mouv = maPartie.estJouable(i,j, echelle);
    monMouvement.setMouvements(mouv);
    monMouvement.setAnterieur(touchX, touchY);
    invalidate();
}

Ensuite nous devons gérer le cas où le doigt bouge à l'écran. Tout d'abord nous devons calculer le déplacement effectué depuis le dernier évènement. Ensuite si le mouvement est possible, nous mettons à jour la position de la vignette. Pour cela, nous regardons si le déplacement proposé s'insère dans l'intervalle de déplacement autorisé. Voici comment j'ai réalisé ceci :

if(eventCode == TouchEvent.MOVE) {
    int mouvX = touchX - monMouvement.getAnterieur()[0];
    int mouvY = touchY - monMouvement.getAnterieur()[1];
    if(monMouvement.getNumero()!=0 && monMouvement.estActif()==true){
        if((mesVignettes[monMouvement.getNumero()-1].getX()+mouvX)<monMouvement.getMouvements()[0][0]){
            mesVignettes[monMouvement.getNumero()-1].setX(monMouvement.getMouvements()[0][0]);
        }else if((mesVignettes[monMouvement.getNumero()-1].getX()+mouvX)>monMouvement.getMouvements()[0][1]){
            mesVignettes[monMouvement.getNumero()-1].setX(monMouvement.getMouvements()[0][1]);
        }else{
             mesVignettes[monMouvement.getNumero()-1].addX(mouvX);
        }
        if((mesVignettes[monMouvement.getNumero()-1].getY()+mouvY)<monMouvement.getMouvements()[1][0]){
            mesVignettes[monMouvement.getNumero()-1].setY(monMouvement.getMouvements()[1][0]);
        }else if((mesVignettes[monMouvement.getNumero()-1].getY()+mouvY)>monMouvement.getMouvements()[1][1]){
            mesVignettes[monMouvement.getNumero()-1].setY(monMouvement.getMouvements()[1][1]);
        }else{
            mesVignettes[monMouvement.getNumero()-1].addY(mouvY);
        }
    }
    monMouvement.setAnterieur(touchX, touchY);
    invalidate();            
}

Enfin, une fois que l'utilisateur retire son doigt de l'écran, nous devons vérifier si le jeu a été modifié. Donc nous devons déterminer sur quelle position se trouve alors la vignette déplacée par un petit calcul. Ensuite, nous devons réactualiser le jeu, en appelant quelques accesseurs et autres méthodes :

if(eventCode == TouchEvent.UP) {
    if(monMouvement.getNumero()!=0 && monMouvement.estActif()==true){
        int i = (mesVignettes[monMouvement.getNumero()-1].getX()+40*echelle)/(80*echelle);
        int j = (mesVignettes[monMouvement.getNumero()-1].getY()+40*echelle)/(80*echelle);
        monMouvement.setMouvements();
        maPartie.placer(i, j, monMouvement.getNumero());
        mesVignettes[monMouvement.getNumero()-1].setPosition(i*80*echelle, j*80*echelle);
    }
    invalidate();
    if(maPartie.estEnPlace()==true){
        Dialog.alert("Bravo, vous avez réussi!");
        System.exit(0);
        return true;
    }
}

La deuxième partie de ce bloc d'instructions correspond à la fin de la partie. Une fois que toutes les pièces sont en place, nous affichons une boîte de dialogue puis mettons fin à l'application.


La correction Travailler avec plusieurs vues

Travailler avec plusieurs vues

Les explications Préparation des vues

Pour démarrer en douceur cette troisième partie, je vous propose que nous revenions un peu sur les vues. Dans la partie précédente, nous avons appris à réaliser divers types de vues. Maintenant nous allons voir comment travailler avec plusieurs vues, et comment passer facilement d'une à autre. Nous verrons donc très peu de théorie, mais plutôt diverses techniques de gestion des vues.
Ce chapitre va donc nous permettre de démarrer cette nouvelle partie en douceur. Je vous invite donc à vous lancer dans la suite de ce cours !

Préparation des vues

Travailler avec plusieurs vues Mise à jour d'un écran

Préparation des vues

Nous allons ici préparer les différentes vues que nous utiliserons dans la suite. Ce sera pour vous l'occasion de faire quelques révisions sur ce que nous avons déjà vu !

La page d'accueil
Objectif

Pour notre première vue, nous allons utiliser principalement deux éléments, à savoir une image et un bouton. Ceux-ci seront alors centrés à l'écran avec un dégradé de couleurs en arrière-plan.
Voilà donc à quoi cela va ressembler :

Accueil
Le fond et le centrage

Pour gérer l'ensemble de la vue de la page d'accueil, nous allons déclarer un conteneur nommé accueil. Afin que les éléments soient centrés au final, nous allons créer un conteneur de type HorizontalFieldManager auquel nous imposerons d'utiliser toute la place disponible verticalement.
Voilà donc comment nous commençons :

HorizontalFieldManager accueil = new HorizontalFieldManager(Field.USE_ALL_HEIGHT);

Nous allons également profiter de cette déclaration pour créer le dégradé souhaité. Pour cela, nous allons redéfinir la méthode paint() du conteneur :

HorizontalFieldManager accueil = new HorizontalFieldManager(Field.USE_ALL_HEIGHT){
    public void paint(Graphics g){
        g.drawGradientFilledRect(0, 0, Display.getWidth(), Display.getHeight(), 0x009FCFFD, 0x0079B6FC);
        super.paint(g);
    }
};

À présent nous allons créer un nouveau conteneur vertical. Celui-ci sera centré verticalement et utilisera toute la largeur de l'écran. À l'intérieur, nous pourrons ainsi ajouter nos deux éléments et les centrer.
Voici donc le code correspondant :

VerticalFieldManager conteneur = new VerticalFieldManager(Field.FIELD_VCENTER|Field.USE_ALL_WIDTH);
Ajout des composants

Maintenant occupons-nous des composants de notre vue, à savoir une image et un bouton !
Pour l'image, j'ai utilisé le logo du Site du Zéro que je vous invite à télécharger pour pouvoir travailler en même temps que moi :

logo_sdz.png
logo_sdz.png

Nous allons donc nous servir de la classe BitmapField pour tracer l'image à l'écran. Nous en profiterons également pour centrer celle-ci horizontalement.
Voilà comment faire :

Bitmap logo = Bitmap.getBitmapResource("logo_sdz.png");
BitmapField monBitmap = new BitmapField(logo,Field.FIELD_HCENTER);

Ensuite, créons notre bouton :

ButtonField entrer = new ButtonField("ENTRER",Field.FIELD_HCENTER);

Enfin, il ne nous reste plus qu'à ajouter l'ensemble dans les différents conteneurs :

conteneur.add(monBitmap);
conteneur.add(entrer);
accueil.add(conteneur);

À ce stade, nous avons donc un conteneur nommé accueil qui contient l'ensemble de notre page d'accueil !

La page de description
Objectif

Maintenant nous allons créer une page de description du Site du Zéro.
Pour cela, nous utiliserons donc deux champs de texte, un pour l'intitulé et le second pour la description du site.

Page
Page
L'entête

Comme précédemment, nous allons commencer par créer un conteneur nommé page, qui contiendra donc l'ensemble des éléments de la page de description :

VerticalFieldManager page = new VerticalFieldManager();

Ensuite, nous allons créer un second conteneur de type VerticalFieldManager qui nous servira à appliquer une couleur de fond à notre entête. Nous lui imposerons donc d'utiliser toute la largeur de l'écran.
Voici le code :

VerticalFieldManager entete = new VerticalFieldManager(Field.USE_ALL_WIDTH);
Background maCouleur = BackgroundFactory.createSolidBackground(0x0079B6FC);
entete.setBackground(maCouleur);

En ce qui concerne le titre de la vue, nous allons utiliser un champ de texte de type LabelField. En effet, il s'agit du seul champ de texte qui ne prend pas l'ensemble de la largeur lors de sa création. Ainsi nous pourrons le centrer, et aussi nous redéfinirons sa couleur d'écriture.
Voilà comment faire tout ceci :

LabelField titre = new LabelField("Le Site du zér0",Field.FIELD_HCENTER){
    public void paint(Graphics g){
        g.setColor(0x00FFFFFF);
        super.paint(g);
    }
};
La description

Pour la suite, il n'y a rien de compliqué ou de surprenant. Effectivement, nous allons simplement utiliser un RichTextField de la manière suivante :

RichTextField description = new RichTextField();
description.setText("Tutoriels pour débutants en programmation et développement web.");

Nous n'avons alors plus qu'à tout insérer dans les différents conteneurs :

entete.add(titre);
page.add(entete);
page.add(description);

Pour la suite, nous avons donc également un conteneur nommé page qui contient l'ensemble de la page de description.


Travailler avec plusieurs vues Mise à jour d'un écran

Mise à jour d'un écran

Préparation des vues Travailler par héritage

Mise à jour d'un écran

Le principe
Définition des vues

La première technique consiste à utiliser un seul écran, à savoir notre MyScreen !
Dans ce cas, nous pouvons déclarer deux attributs accueil et page qui contiendront donc l'ensemble de nos deux vues. Nous pouvons alors définir ces deux vues à l'intérieur du constructeur de notre classe.

Voilà donc à quoi ressemble la définition de nos deux vues :

package mypackage;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;

public final class MyScreen extends MainScreen
{
    private HorizontalFieldManager accueil;
    private VerticalFieldManager page;
    
    public MyScreen()
    {        
        super(Manager.NO_VERTICAL_SCROLL);
        
        // Accueil
        accueil = new HorizontalFieldManager(Field.USE_ALL_HEIGHT){
            public void paint(Graphics g){
                g.drawGradientFilledRect(0, 0, Display.getWidth(), Display.getHeight(), 0x009FCFFD, 0x0079B6FC);
                super.paint(g);
            }
        };
        VerticalFieldManager conteneur = new VerticalFieldManager(Field.FIELD_VCENTER|Field.USE_ALL_WIDTH);
        Bitmap logo = Bitmap.getBitmapResource("logo_sdz.png");
        BitmapField monBitmap = new BitmapField(logo,Field.FIELD_HCENTER);
        ButtonField entrer = new ButtonField("ENTRER",Field.FIELD_HCENTER);
        conteneur.add(monBitmap);
        conteneur.add(entrer);
        accueil.add(conteneur);
        
        // Page
        page = new VerticalFieldManager();
        VerticalFieldManager entete = new VerticalFieldManager(Field.USE_ALL_WIDTH);
        Background maCouleur = BackgroundFactory.createSolidBackground(0x0079B6FC);
        entete.setBackground(maCouleur);
        LabelField titre = new LabelField("Le Site du zér0",Field.FIELD_HCENTER){
            public void paint(Graphics g){
                g.setColor(0x00FFFFFF);
                super.paint(g);
            }
        };
        RichTextField description = new RichTextField();
        description.setText("Tutoriels pour débutants en programmation et développement web.");
        entete.add(titre);
        page.add(entete);
        page.add(description);
    }
}
La mise à jour de l'écran

À l'ouverture de l'application, nous voulons que l'utilisateur tombe sur la vue accueil. C'est pourquoi pour commencer, nous n'avons qu'à ajouter cette vue au conteneur principal :

add(accueil);

L'objectif est alors de remplacer la vue accueil par page lors du clic du bouton. Pour cela nous allons nous servir de la méthode deleteAll() de la classe Manager. Effectivement, cette méthode permet de supprimer l'ensemble des composants enfants d'un conteneur. Ainsi nous pourrons par la suite ajouter notre nouvelle vue : page.
Voilà donc le code qui nous permettra de faire la transition entre les deux vues :

deleteAll();
add(page);

Finalement, nous n'avons plus qu'à ajouter ce code à l'intérieur de la méthode de gestion des évènements du bouton, comme ceci :

entrer.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        deleteAll();
        add(page);
    }
});
Le code complet

Au final pour utiliser les deux vues définies précédemment, nous avons donc le code suivant :

package mypackage;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;

public final class MyScreen extends MainScreen
{
    private HorizontalFieldManager accueil;
    private VerticalFieldManager page;
    
    public MyScreen()
    {        
        super(Manager.NO_VERTICAL_SCROLL);
        
        // Accueil
        accueil = new HorizontalFieldManager(Field.USE_ALL_HEIGHT){
            public void paint(Graphics g){
                g.drawGradientFilledRect(0, 0, Display.getWidth(), Display.getHeight(), 0x009FCFFD, 0x0079B6FC);
                super.paint(g);
            }
        };
        VerticalFieldManager conteneur = new VerticalFieldManager(Field.FIELD_VCENTER|Field.USE_ALL_WIDTH);
        Bitmap logo = Bitmap.getBitmapResource("logo_sdz.png");
        BitmapField monBitmap = new BitmapField(logo,Field.FIELD_HCENTER);
        ButtonField entrer = new ButtonField("ENTRER",Field.FIELD_HCENTER);
        conteneur.add(monBitmap);
        conteneur.add(entrer);
        accueil.add(conteneur);
        
        // Page
        page = new VerticalFieldManager();
        VerticalFieldManager entete = new VerticalFieldManager(Field.USE_ALL_WIDTH);
        Background maCouleur = BackgroundFactory.createSolidBackground(0x0079B6FC);
        entete.setBackground(maCouleur);
        LabelField titre = new LabelField("Le Site du zér0",Field.FIELD_HCENTER){
            public void paint(Graphics g){
                g.setColor(0x00FFFFFF);
                super.paint(g);
            }
        };
        RichTextField description = new RichTextField();
        description.setText("Tutoriels pour débutants en programmation et développement web.");
        entete.add(titre);
        page.add(entete);
        page.add(description);
        
        // Gestion de la transition entre les vues
        add(accueil);
        entrer.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                deleteAll();
                add(page);
            }
        });
    }
}

Préparation des vues Travailler par héritage

Travailler par héritage

Mise à jour d'un écran L'internationalisation

Travailler par héritage

Utiliser réellement plusieurs vues
Introduction

Comme vous vous en doutez certainement, la méthode précédente n'est pas vraiment la plus appropriée pour gérer plusieurs vues !

Pourquoi, cette technique fonctionne bien ?

Effectivement la technique précédente fonctionne très bien et est dans ce cas-là relativement simple d'utilisation. Toutefois nous n'avons pas réalisé de vues extrêmement complexes et nous n'avions que deux vues différentes. Imaginez maintenant que vous vouliez faire une application qui sera divisée en une multitude de vues. Vous pourriez bien évidemment utiliser une seule classe pour décrire l'ensemble de vos vues, mais il faut avouer que ce serait un peu le bazar !

Le principe

Je vous ai déjà légèrement lâché le morceau, mais effectivement nous allons utiliser plusieurs classes. Jusqu'à présent, nous n'avons utilisé qu'une unique classe pour décrire les vues de nos applications : MyScreen. C'est pourquoi nous allons maintenant définir de « vraies » vues, en définissant plusieurs classes qui héritent de MainScreen. Nous pourrons ainsi passer d'une vue à une autre en utilisant la méthode pushScreen() de la classe UiApplication, dont je suis certain que vous aviez oublié l'existence !
Cependant, nous sommes ici confronté à un léger problème.

En effet, comment accéder à la méthode pushScreen() depuis l'intérieur d'une classe « enfant » ?

Dans certains langages tels que l'Actionscript, il existe un mot-clé parent qui permet d'accéder à la classe « parente » dont l'instance en est un enfant. Cependant, ce mot-clé n'a pas d'équivalent en Java, et nous allons donc devoir faire autrement.
En réalité, nous allons déclarer un attribut de type UiApplication à l'intérieur de nos MainScreen, afin de pouvoir passer une référence de notre application. Ne vous inquiétez pas si vous ne comprenez pas bien, nous allons tout de suite voir ce que cela donne au niveau du code.

Nos deux vues

Comme nous l'avons dit, nous allons à présent utiliser une classe pour chacune de nos vues. Je vous laisse donc découvrir les classes Accueil et Page, où nous avons séparé la définition des deux vues.

La classe Accueil
package mypackage;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;

public final class Accueil extends MainScreen
{
    private UiApplication monApp;
    
    public Accueil(UiApplication app)
    {        
        super(Manager.NO_VERTICAL_SCROLL);
        monApp = app;
        // Accueil
        HorizontalFieldManager accueil = new HorizontalFieldManager(Field.USE_ALL_HEIGHT){
            public void paint(Graphics g){
                g.drawGradientFilledRect(0, 0, Display.getWidth(), Display.getHeight(), 0x009FCFFD, 0x0079B6FC);
                super.paint(g);
            }
        };
        VerticalFieldManager conteneur = new VerticalFieldManager(Field.FIELD_VCENTER|Field.USE_ALL_WIDTH);
        Bitmap logo = Bitmap.getBitmapResource("logo_sdz.png");
        BitmapField monBitmap = new BitmapField(logo,Field.FIELD_HCENTER);
        ButtonField entrer = new ButtonField("ENTRER",Field.FIELD_HCENTER);
        conteneur.add(monBitmap);
        conteneur.add(entrer);
        accueil.add(conteneur);
        add(accueil);
    }
}
La classe Page
package mypackage;

import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;

public class Page extends MainScreen{
    
    private UiApplication monApp;
    
    public Page(UiApplication app){
        super(Manager.NO_VERTICAL_SCROLL);
        monApp = app;
        // Page
        VerticalFieldManager page = new VerticalFieldManager();
        VerticalFieldManager entete = new VerticalFieldManager(Field.USE_ALL_WIDTH);
        Background maCouleur = BackgroundFactory.createSolidBackground(0x0079B6FC);
        entete.setBackground(maCouleur);
        LabelField titre = new LabelField("Le Site du zér0",Field.FIELD_HCENTER){
            public void paint(Graphics g){
                g.setColor(0x00FFFFFF);
                super.paint(g);
            }
        };
        RichTextField description = new RichTextField();
        description.setText("Tutoriels pour débutants en programmation et développement web.");
        entete.add(titre);
        page.add(entete);
        page.add(description);
        add(page);
        
    }
    
}
Afficher les vues
Au lancement de l'application

Étant donné que nous avons ajouté un attribut UiApplication à chacune de nos classes, et que leurs constructeurs prennent un nouveau paramètre, nous allons donc devoir modifier légèrement la classe principale. Pour cela, nous enverrons donc une référence à l'application en cours en utilisant le mot-clé this.
Voilà donc le nouveau constructeur de notre classe principale :

public MonApp()
{        
    pushScreen(new Accueil(this));
}

Ainsi nous aurons donc accès à cette méthode pushScreen() depuis l'intérieur de nos classes de vues.

La transition entre les vues

Maintenant que nous avons une référence à notre application, nous pouvons donc changer de vue, simplement grâce à l'instruction suivante :

monApp.pushScreen(new Page(monApp));

Il nous suffit donc de l'insérer à l'intérieur de la méthode fielChanged() qui nous permet de gérer les évènements de notre bouton entrer.
Voilà donc l'action associée au bouton entrer :

entrer.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        monApp.pushScreen(new Page(monApp));
    }
});
La classe Accueil complète

Pour finir, je vous propose donc le code complet de la classe Accueil, afin que vous puissiez tester par vous-mêmes :

package mypackage;

import net.rim.device.api.system.Bitmap;
import net.rim.device.api.system.Display;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.UiApplication;
import net.rim.device.api.ui.component.BitmapField;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;

public final class Accueil extends MainScreen
{
    private UiApplication monApp;
    
    public Accueil(UiApplication app)
    {        
        super(Manager.NO_VERTICAL_SCROLL);
        monApp = app;
        // Accueil
        HorizontalFieldManager accueil = new HorizontalFieldManager(Field.USE_ALL_HEIGHT){
            public void paint(Graphics g){
                g.drawGradientFilledRect(0, 0, Display.getWidth(), Display.getHeight(), 0x009FCFFD, 0x0079B6FC);
                super.paint(g);
            }
        };
        VerticalFieldManager conteneur = new VerticalFieldManager(Field.FIELD_VCENTER|Field.USE_ALL_WIDTH);
        Bitmap logo = Bitmap.getBitmapResource("logo_sdz.png");
        BitmapField monBitmap = new BitmapField(logo,Field.FIELD_HCENTER);
        ButtonField entrer = new ButtonField("ENTRER",Field.FIELD_HCENTER);
        entrer.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                monApp.pushScreen(new Page(monApp));
            }
        });
        conteneur.add(monBitmap);
        conteneur.add(entrer);
        accueil.add(conteneur);
        add(accueil);
    }
}

Mise à jour d'un écran L'internationalisation

L'internationalisation

Travailler par héritage Créer des ressources

Plus tard, lorsque vous créerez des applications, vous serez amenés à définir l'étendue d'utilisation de celle-ci. J'entends par là qu'il vous faudra déterminer si votre application est susceptible d'être utilisée à l'étranger. Si c'est le cas, vous devrez alors l'adapter à différentes langues.
Dans ce chapitre, nous allons donc voir comment utiliser des ressources et comment s'en servir pour créer des applications multilingues.

Créer des ressources

L'internationalisation Utiliser les ressources

Créer des ressources

Préparation du projet
Introduction

Comme je vous l'ai dit en introduction, pour gérer des applications multilingues nous allons devoir utiliser ce qu'on appelle des ressources !

Qu'est-ce qu'une ressource ?

Les ressources sont en fait des fichiers dans lesquels nous allons pouvoir définir nos champs de texte en différentes langues. Le principe est alors de définir différents champs que nous nommerons des clés. Celles-ci sont alors associées à diverses chaînes de caractères, une pour chaque langue désirée. L'idée est ensuite non pas d'associer une chaîne de caractères à un objet graphique, mais plutôt une clé qui elle contiendra l'ensemble des traductions.
Nous allons donc voir dans ce chapitre comment réaliser tout ceci.

Créer une ressource

Pour créer un fichier de ressources, cliquez sur File > New > BlackBerry Resource File :

Image utilisateur

Nous allons donc créer un nouveau fichier de ressources nommé « Langue.rrh » à l'intérieur du dossier mypackage :

Image utilisateur

Vous devriez alors voir apparaître deux fichiers dans l'arborescence de votre projet nommés Langue.rrh et Langue.rrc, comme ci-dessous :

Image utilisateur
Ajouter une nouvelle ressource

Précédemment, nous n'avons en réalité créé une ressource uniquement pour pour la langue par défaut. C'est pourquoi pour ajouter une nouvelle langue à notre application, nous allons devoir ajouter un nouveau fichier « .rrc ».
Je vous propose donc d'ajouter un fichier pour la langue française nommé Langue_fr.rrc :

Image utilisateur
Implémentation des ressources

L'utilisation des ressources est assez spéciale, puisqu'elle consiste à implémenter celles-ci. Le nom de la « pseudo-interface » à implémenter alors est le nom du fichier « .rrh » suivi du terme Resource.
Ainsi dans notre cas, le nom sera LangueResource :

public final class MyScreen extends MainScreen implements LangueResource

Une fois implémentée, la ressource doit également être déclarée en tant qu'attribut. Pour faire cela, utilisez la syntaxe suivante :

private static ResourceBundle langue = ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME);

Dans la suite, nous accèderons donc aux différents champs de la ressource grâce à cet attribut langue.


L'internationalisation Utiliser les ressources

Utiliser les ressources

Créer des ressources Une application multilingue

Utiliser les ressources

Travailler les ressources

À ce stade, vous devriez normalement avoir les trois fichiers suivants : Langue.rrh, Langue.rrc et Langue_fr.rrc.

Ajouter une clé

Avant toute chose, commencez par ouvrir le fichier Langue.rrh. Vous verrez alors apparaître une sorte de tableau comme ci-dessous :

Image utilisateur

Comme vous pouvez le voir, nous allons ici pouvoir associer des valeurs à des clés. Il nous faut donc commencer par ajouter une nouvelle clé, en cliquant sur Add Key. Renseignez alors le nom du champ :

Image utilisateur

Nous venons donc de créer notre première clé que vous pouvez maintenant voir :

Image utilisateur
Remplir les champs

À l'ouverture du fichier de ressources, vous tombez normalement sur les ressources de la langue par défaut, c'est-à-dire l'anglais. Vous pouvez donc commencer à remplir les champs en version anglaise dans la section values.
Voilà donc ce que nous avons à présent :

Image utilisateur

Pour passer d'une langue à une autre, il suffit de naviguer dans les différents onglets visibles au bas de la fenêtre d'édition :

Image utilisateur

Si vous passez dans l'onglet fr, vous verrez alors que le champ values de CHAMP est vide. C'est normal puisqu'il s'agit de la valeur pour la version française, vous pouvez alors renseigner une chaîne de caractères différente de la version anglaise :

Image utilisateur

Nos fichiers de ressources sont à présent prêts à être utilisés. Nous n'avons ici défini qu'une seule clé, mais vous pouvez en ajouter autant que vous le souhaitez.

Les clés à l'intérieur du code

La partie intéressante maintenant est de pouvoir récupérer les valeurs de nos clés à l'intérieur du code. Pour cela nous allons utiliser la méthode getString() de notre objet langue de type ResourceBundle.
Voici donc comment récupérer la valeur de notre clé CHAMP :

String monChamp = langue.getString(CHAMP);

La valeur sélectionnée lors de l'exécution dépend alors de la langue par défaut du smartphone. Toutefois, il est possible de changer la langue en utilisant l'instruction suivante :

Locale.setDefault (Locale.get(Locale.LOCALE_fr, null));

Il est alors possible à tout moment de revenir à n'importe quelle langue :

Locale.setDefault (Locale.get(Locale.LOCALE_en, null));

Créer des ressources Une application multilingue

Une application multilingue

Utiliser les ressources Le stockage de données

Une application multilingue

Préparation des ressources
Les chaînes de caractères anglaises

Dans l'exemple d'application que nous allons réaliser, nous réutiliserons les ressources définies plus haut. Nous aurons donc deux langues à savoir : l'anglais et le français.
Au lieu de faire un long discours, je vous laisse découvrir les différentes clés ajoutées ainsi que leurs valeurs anglaises associées :

Ressources anglaises
Ressources anglaises
Les chaînes de caractères françaises

Pour la langue française, nous retrouverons donc les mêmes clés. Nous allons donc uniquement changer leur valeur comme suit :

Ressources françaises
Ressources françaises
Les sources

Le code utilisé dans cette application n'est pas très complexe, c'est pourquoi je vous propose directement l'intégralité de celui-ci. Les points qui méritent votre attention sont l'affectation des ressources et la gestion des évènements liés à la liste déroulante.
Voici donc le code de cette application :

package mypackage;

import net.rim.device.api.i18n.Locale;
import net.rim.device.api.i18n.ResourceBundle;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.LabelField;
import net.rim.device.api.ui.component.ObjectChoiceField;
import net.rim.device.api.ui.component.RichTextField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;

public final class MyScreen extends MainScreen implements LangueResource
{
    private static ResourceBundle langue = ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME);
    private ObjectChoiceField maListe;
    private LabelField nomLabel;
    private RichTextField nomTexte;
    private LabelField descriptionLabel;
    private RichTextField descriptionTexte;
    private LabelField adresseLabel;
    private RichTextField adresseTexte;
    
    public MyScreen()
    {        
        // Affectation des ressources
        setTitle(langue.getString(TITLE));
        nomLabel = new LabelField(langue.getString(NAME_LABEL) + " : ");
        nomTexte = new RichTextField("Site du Zéro");
        descriptionLabel = new LabelField(langue.getString(DESCRIPTION_LABEL) + " : ");
        descriptionTexte = new RichTextField(langue.getString(DESCRIPTION_TEXT));
        adresseLabel = new LabelField(langue.getString(ADRESSE_LABEL) + " : ");
        adresseTexte = new RichTextField("www.siteduzero.com");
        
        // Création de la liste déroulante
        String mesLangues[] = {"English","Français"};
        maListe = new ObjectChoiceField(langue.getString(LANGUAGE_LABEL) + " : ",mesLangues,0);
        
        // Création des conteneurs
        HorizontalFieldManager nomManager = new HorizontalFieldManager();
        HorizontalFieldManager descriptionManager = new HorizontalFieldManager();
        HorizontalFieldManager adresseManager = new HorizontalFieldManager();
        
        // Mise en place des composants
        nomManager.add(nomLabel);
        nomManager.add(nomTexte);
        descriptionManager.add(descriptionLabel);
        descriptionManager.add(descriptionTexte);
        adresseManager.add(adresseLabel);
        adresseManager.add(adresseTexte);
        
        // Gestion de l'affichage final
        add(nomManager);
        add(new SeparatorField());
        add(descriptionManager);
        add(new SeparatorField());
        add(adresseManager);
        add(new SeparatorField());
        add(maListe);
        
        // Gestion des évènements liés à la liste déroulante
        maListe.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                if(maListe.getChoice(maListe.getSelectedIndex()) == "English"){
                    Locale.setDefault (Locale.get(Locale.LOCALE_en, null));
                }else{
                    Locale.setDefault (Locale.get(Locale.LOCALE_fr, null));
                }
                // Mise à jour des champs
                setTitle(langue.getString(TITLE));
                nomLabel.setText((langue.getString(NAME_LABEL) + " : "));
                nomTexte.setText(("Site du Zéro"));
                descriptionLabel.setText((langue.getString(DESCRIPTION_LABEL) + " : "));
                descriptionTexte.setText((langue.getString(DESCRIPTION_TEXT)));
                adresseLabel.setText((langue.getString(ADRESSE_LABEL) + " : "));
                adresseTexte.setText("www.siteduzero.com");
                maListe.setLabel(langue.getString(LANGUAGE_LABEL) + " :");
            }
        });
    }
}
Résultat final

Nous allons enfin pouvoir admirer le résultat de ces manipulations de ressources. Lancez donc l'application réalisée !

Version anglaise

Étant donné que par défaut votre simulateur est en anglais, vous devriez donc tomber sur l'écran suivant :

Version anglaise
Version anglaise
Version française

Si vous avez analysé le code précédent, vous devez savoir que pour changer la langue il suffit de mettre à jour la liste déroulante. Je vous invite donc à passer sur la valeur « Fançais ».
Vous verrez alors l'ensemble des champs se mettre à jour :

Version française
Version française

Utiliser les ressources Le stockage de données

Le stockage de données

Une application multilingue Gérer des données

À présent nous allons apprendre à stocker des données à l'intérieur du smartphone. Bien sûr, il est possible de stocker tout type de données. Toutefois dans ce cours, nous nous contenterons de gérer des fichiers de type texte avec l'extension « .txt ». Nous verrons donc comment créer un fichier, puis comment y écrire dedans et lire son contenu.
Soyez attentif au cours de ce chapitre, car les notions vues ici vous seront grandement utiles dans le chapitre suivant qui est un TP !

Gérer des données

Le stockage de données Accéder « physiquement » aux données

Gérer des données

Introduction au stockage

Comme dit en introduction, nous allons dans ce chapitre nous intéresser au stockage de données. Contrairement aux variables qui sont stockées dans la mémoire vive, les données seront ici stockées sur le disque dur du smartphone ou éventuellement sur une carte mémoire externe. Dans le second cas, il s'agit d'une carte SD que vous pouvez insérer dans l'appareil.

Afin d'éviter de s'embêter plus tard avec les chemins absolus correspondant au disque dur et à la carte externe, je suggère que nous créions deux constantes : DEVICE et SD_CARD. Ainsi nous n'aurons plus à nous préoccuper des chemins depuis la « racine », et surtout cela nous évitera de faire des erreurs.
Voici donc ces constantes :

public static final String SD_CARD = "file:///SDCard/";
public static final String DEVICE = "file:///store/home/user/documents/";

Ainsi ces constantes seront accessibles par les expressions MyScreen.SD_CARD et MyScreen.DEVICE.

L'interface FileConnection
Une histoire de connexion

Quand nous parlons de stockage de données, nous faisons généralement allusion à l'écriture ou la lecture d'un fichier. Pour réaliser ces actions, nous faisons en réalité une connexion à ce fichier. Une fois la connexion établie, il est ensuite possible de réaliser toutes sortes de modifications sur ce fichier. Une fois celles-ci terminées, il est alors nécessaire de fermer cette connexion afin de « casser » le lien avec le fichier.

En ce qui nous concerne, la connexion à un fichier, ou à un dossier, se réalise à l'aide de l'interface FileConnection. Une fois la connexion réalisée, cette interface permet également de manipuler le fichier ou dossier correspondant grâce à ses méthodes.
Avant d'aller plus loin, je vous propose de découvrir l'instruction permettant de réaliser cette liaison :

FileConnection fc = (FileConnection)Connector.open(/* Chemin */);

Étant donné que la connexion à un fichier ou à un dossier peut échouer et donc générer des erreurs, nous devrons réaliser l'ensemble des opérations à l'intérieur d'un bloc try{....} catch{...}.
Voici donc la structure de base pour utiliser des données externes au programme :

try 
{
    FileConnection fc = (FileConnection)Connector.open(/* Chemin */);
    // Manipulation de la donnée
    fc.close();
}
catch (IOException ioe) 
{
    System.out.println(ioe.getMessage());
}
Manipulation d'une donnée

Maintenant que vous savez comment réaliser une connexion à un fichier ou dossier, nous allons pouvoir commencer à travailler avec les différentes méthodes de l'interface FileConnection.
Pour démarrer, je vais vous introduire la méthode exists() que nous utiliserons maintenant à chaque fois. En effet, il est probable que la donnée que vous voulez atteindre n'existe tout simplement pas. C'est pourquoi à partir de maintenant, nous allons devoir vérifier à chaque fois si la donnée en question existe. Dans le cas contraire, nous la créerons !
Sachant que la méthode exists() renvoie une valeur booléenne, nous pouvons directement l'insérer dans une condition :

try 
{
    FileConnection fc = (FileConnection)Connector.open(systeme + dossier + "/");
    if (!fc.exists())
    {
        // Créer la donnée
    }
    fc.close();
}
catch (IOException ioe) 
{
    System.out.println(ioe.getMessage());
}

À l'intérieur des accolades du if, nous allons donc créer notre donnée. Il peut donc s'agir d'un dossier ou d'un fichier. Comme vous l'imaginez, l'instruction n'est pas la même dans les deux cas.
Tout d'abord, voici comment créer un dossier à l'aide de la méthode mkdir() :

fc.mkdir();

Pour créer un fichier, l'instruction est tout aussi simple grâce à la méthode create(). Voici comment faire :

fc.create();

Une autre méthode qui pourrait vous être utile est delete(), qui permet de supprimer un fichier comme un dossier. Néanmoins celle-ci suppose que vous ayez la donnée correspondante. C'est pourquoi elle doit s'utiliser avec la condition suivante :

if (fc.exists())
{
    fc.delete();
}
Création de données

Maintenant que vous possédez toutes les bases pour gérer des données, je vais simplement vous présenter des méthodes toutes faites.

Créer un dossier

Pour commencer, voici une méthode que j'ai nommée creerDossier() et qui bien évidemment permet de créer un dossier :

public void creerDossier(String dossier, String systeme){
    try 
    {
        FileConnection fc = (FileConnection)Connector.open(systeme + dossier);
        if (!fc.exists())
        {
            fc.mkdir();
        }
        fc.close();
    }
    catch (IOException ioe) 
    {
        System.out.println(ioe.getMessage() );
    }
}

Rien de nouveau ici, si ce n'est la construction par « segment » du chemin absolu du dossier.
Voici donc un exemple d'utilisation de cette méthode :

creerDossier("SdZ/", MyScreen.SD_CARD);
Créer un fichier

Cette méthode creerFichier() est identique à la précédente mise à part la création à l'aide de create() :

public void creerFichier(String fichier, String systeme){
    try 
    {
        FileConnection fc = (FileConnection)Connector.open(systeme + fichier);
        if (!fc.exists())
        {
            fc.create();
        }
        fc.close();
    }
    catch (IOException ioe) 
    {
        System.out.println(ioe.getMessage() );
    }
}

Voici comment l'utiliser :

creerFichier("fichier.txt", MyScreen.DEVICE);
Supprimer une donnée

Comme nous l'avons dit, l'opération pour supprimer un fichier ou un dossier est strictement la même. C'est pourquoi nous ne créerons qu'une méthode permettant de supprimer tout type de données.
Voici cette méthode nommée supprimerDonnee() :

public void supprimerDonnee(String donnee, String systeme){
    try 
    {
        FileConnection fc = (FileConnection)Connector.open(systeme + donnee);
        if (fc.exists())
        {
            fc.delete();
        }
        fc.close();
    }
    catch (IOException ioe) 
    {
        System.out.println(ioe.getMessage() );
    }
}

Grâce à cette méthode nous pouvons donc supprimer notre fichier fichier.txt, comme cela :

supprimerDonnee("fichier.txt", MyScreen.DEVICE);

Puis voici comment supprimer notre dossier SdZ créé plus haut :

supprimerDonnee("SdZ/", MyScreen.SD_CARD);

Le stockage de données Accéder « physiquement » aux données

Accéder « physiquement » aux données

Gérer des données Lire et écrire dans un fichier

Accéder « physiquement » aux données

Précédemment nous avons appris à créer et supprimer des fichiers comme des dossiers. Toutefois, la seule manière de bien visualiser ces manipulations est d'aller « fouiller » directement dans les fichiers du smartphone. Nous allons donc maintenant voir comment récupérer ces données depuis l'extérieur de notre application.

Stockage à l'intérieur du smartphone

Dans un premier temps, nous allons créer un nouveau dossier. Pour cela, nous utiliserons donc notre méthode creerDossier() comme ceci :

creerDossier("SdZ/", MyScreen.DEVICE);

Après exécution, le dossier SdZ est normalement créé. Pour vérifier, nous allons entrer dans les dossiers du disque dur du smartphone. Pour faire ça, commencez par cliquer sur l'icône Applications comme ci-dessous :

Image utilisateur

Ensuite, entrez dans l'explorateur de fichiers représenté par l'icône sélectionnée ci-dessous :

Image utilisateur

Puis rendez-vous dans l'explorateur de dossiers par hiérarchie en cliquant sur File Folders comme présenté sur l'image suivante :

Image utilisateur

N'ayant pas encore inséré de carte mémoire, nous n'avons pour l'instant accès uniquement au smartphone. N'ayant pas le choix, cliquez donc sur Device :

Image utilisateur

Maintenant suivez le cheminement que nous avons réalisé, c'est-à-dire home/user/documents/. Arrivé à l'intérieur du répertoire documents, vous devriez alors trouver notre dossier SdZ.
Voilà ce que j'obtiens :

Image utilisateur
Stockage sur une carte externe
Créer une carte mémoire

Comme nous avons vu plus haut, il est possible de stocker des données à l'intérieur d'une carte mémoire externe. En revanche pour que cela soit réalisable, il faut bien entendu que le smartphone dispose d'une carte mémoire. Nous allons donc commencer par ajouter une carte mémoire à notre simulateur.
Pour cela, lancez votre simulateur, puis allez dans Change SD Card... du menu Simulate :

Image utilisateur

Vous arrivez alors sur le gestionnaire de cartes mémoires. Pour en créer une nouvelle, cliquez sur le bouton suivant :

Image utilisateur

Maintenant nous allons pouvoir paramétrer notre carte. Ainsi, renseignez un emplacement où stocker votre carte sur votre ordinateur puis spécifiez la taille de la mémoire :

Image utilisateur

Une fois le bouton Create cliqué, vous verrez apparaître votre carte dans le gestionnaire. Cependant celle-ci n'est pas encore insérée dans le smartphone. C'est pourquoi vous allez cocher la case « Remount SD Card On Startup » et cliquer sur le bouton Mount selected en haut à droite de la fenêtre :

Image utilisateur

Une fois la fenêtre fermée, il est probable qu'on vous demande de formater votre carte mémoire. Acceptez en cliquant sur Yes :

Image utilisateur

Attendez durant le formatage de celle-ci. Une fois terminé, votre carte est prête à être utilisée :

Image utilisateur
Accéder au dossier de la carte mémoire

Maintenant que notre carte est insérée dans l'appareil, nous allons pouvoir y stocker des données. Créons donc un dossier grâce à l'instruction suivante :

creerDossier("SdZ", MyScreen.SD_CARD);

Pour visualiser notre nouveau dossier, nous devons retourner dans l'explorateur de fichiers. Une autre manière d'y accéder est de se placer sur la catégorie Media, puis d'appuyer sur la touche menu de votre smartphone :

Image utilisateur

Dans le menu qui apparaît, cliquez sur Explore :

Image utilisateur

Dans File Folders, vous devriez normalement voir une nouvelle entrée : Media Card. En cliquant dessus, vous entrez donc dans la carte mémoire externe :

Image utilisateur

Comme nous nous y attendions, un dossier SdZ est présent :

Image utilisateur
Un fichier de test

Juste par curiosité, nous allons créer cette fois un fichier et non pas un dossier. Nous allons donc réutiliser la méthode creerFichier() que nous avions auparavant réalisée :

creerFichier("fichier.txt", MyScreen.DEVICE);

Sans grande surprise, nous trouvons notre fichier nommé fichier.txt à l'emplacement prévu :

Image utilisateur

Gérer des données Lire et écrire dans un fichier

Lire et écrire dans un fichier

Accéder « physiquement » aux données TP : un éditeur de texte

Lire et écrire dans un fichier

Un système binaire
Un peu de théorie

Avant de nous lancer dans la suite, revenons un peu sur de la théorie, en particulier sur le langage binaire.
Pour illustrer ce qui va être dit, nous allons prendre comme comparaison notre belle langue natale, le français !
La base de notre langage, c'est la lettre. Nous avons donc un alphabet de 26 lettres, grâce auquel nous pouvons créer des mots et faire des phrases. Ainsi, à l'aide de seulement 26 lettres (ou symboles), nous sommes capables de réaliser des millions et des millions de phrases et dire tout ce qu'on veut. Un appareil électronique fonctionne de la même manière.

Image utilisateur

Ci-dessus, vous pouvez voir un signal électrique tel qu'on peut en trouver dans tout appareil électronique. On définit alors un 1 « logique » et un 0 « logique » pour représenter les états haut et bas du signal. On obtient donc notre base du langage binaire (binaire pour deux symboles). Et de la même manière que les phrases, on peut réaliser des instructions grâce à une succession de 0 et de 1. Voilà le seul langage que les appareils électroniques comprennent !

Pour en revenir à notre problème, le stockage de données se fait également en binaire. Ainsi les données sont enregistrées sous la forme de mots binaires appelés bytes. C'est pourquoi lorsque nous voudrons enregistrer ou lire du texte, nous allons devoir faire la conversion en bytes.

La classe String

En fait la conversion d'une chaîne de caractères en bytes est appelée l'encodage !
Il existe plusieurs manières d'encoder du texte, et le système d'exploitation BlackBerry OS en prend en charge plusieurs, à savoir :

Ainsi la classe String dispose de méthodes qui permettent de faire l'encodage suivant ces différentes normes. La méthode getBytes() par exemple sert à encoder un texte.
Voici donc comment convertir une chaîne de caractères nommée monTexte de type String en bytes :

monTexte.getBytes("UTF-8");

En réalité, en Java il existe un type byte représentant un mot binaire. Ainsi l'instruction précédente renvoie en fait un tableau de type byte[].
L'opération inverse est bien évidemment réalisable. Pour cela, nous pouvons utiliser directement le constructeur de la classe String. Voici par exemple comment retrouver une chaîne de caractères à partir d'une variable donnees de type byte[] :

monTexte = new String(donnees, "UTF-8");
Écriture dans un fichier
La classe OutputStream

L'interface FileConnection ne permet pas de lire ou d'écrire dans un fichier. C'est pourquoi nous sommes obligés de passer par des classes « annexes » pour le faire. En ce qui concerne l'écriture, nous disposons de la classe abstraite OutputStream.
Pour déclarer et initialiser une variable de type OutputStream, nous devons alors passer par la méthode openOutputStream() de l'interface FileConnection :

OutputStream os = fc.openOutputStream();

Une fois réalisé, nous pouvons enfin écrire à l'intérieur de ce fichier grâce à la méthode write(). Celle-ci prend alors en paramètre les données à stocker sous la forme d'un tableau byte[]. Nous aurons ainsi besoin ici d'utiliser la méthode getBytes() présentée plus haut.
Voici donc comment insérer une chaîne de caractères nommée monTexte dans le fichier :

os.write(monTexte.getBytes());

Enfin pour finir, il est nécessaire de fermer la connexion comme pour l'interface FileConnection :

os.close();
Une méthode complète

Pour vous simplifier les choses dans l'avenir, je vous propose donc une méthode ecrireFichier() qui contient tout le code pour pouvoir écrire un texte nommé donnees dans un fichier.
Voici le code de cette méthode :

public void ecrireFichier(String fichier, String systeme, String donnees){
    try 
    {    
        FileConnection fc = (FileConnection)Connector.open(systeme + fichier);
        if (fc.exists())
        {
            fc.delete();
        }
        fc.create();
        OutputStream os = fc.openOutputStream(); 
        os.write(donnees.getBytes());
        os.close();
        fc.close();
    }
    catch (IOException ioe) 
    {
        System.out.println(ioe.getMessage() );
    }
}

Voici également comment utiliser cette méthode pour écrire à l'intérieur du fichier fichier.txt créé tout à l'heure :

String monTexte = "Ceci est le texte de mon fichier !";
ecrireFichier("fichier.txt", MyScreen.DEVICE, monTexte);
Ouverture du fichier

Le meilleur moyen de vérifier si le code précédent marche est encore d'ouvrir le fichier en question. Pour faire ça, rendez-vous dans l'explorateur de fichier, puis cliquez sur fichier.txt pour l'ouvrir :

Image utilisateur

Une fois le fichier chargé, vous devriez normalement retrouver la chaîne de caractères écrite plus tôt :

Image utilisateur
Lecture d'un fichier
La classe InputStream

Identiquement à OutputStream, nous disposons de la classe abstraite InputStream pour lire le contenu d'un fichier.
La déclaration d'une variable de ce type est donc très similaire :

InputStream is = fc.openInputStream();

La récupération du contenu du fichier se fait de manière assez spéciale. En effet pour cela il faut utiliser la méthode streamToBytes() de la classe IOUtilities. Celle-ci renvoie alors une variable de type byte[] que nous pourrons décrypter par la suite.
Voici comment ça se passe :

byte[] data = IOUtilities.streamToBytes(is);

Enfin, il faut bien évidemment fermer ceci :

is.close();
Une méthode complète

Comme d'habitude, je vais maintenant vous fournir une méthode tout prête à l'emploi.
Voici donc le code de cette méthode que j'ai appelée lireFichier() :

public String lireFichier(String fichier, String systeme){
    String donnees = new String();
    try 
    {    
        FileConnection fc = (FileConnection)Connector.open(systeme + fichier);
        if (!fc.exists())
        {
            fc.create();
        }
        InputStream is = fc.openInputStream();
        byte[] data = IOUtilities.streamToBytes(is);
        is.close();
        fc.close();
        donnees = new String(data);
    }
    catch (IOException ioe) 
    {
        System.out.println(ioe.getMessage() );
    }
    return(donnees);
}

La méthode lireFichier() renvoie une variable de type String. Nous pouvons ainsi afficher son contenu à l'aide d'un composant de type RichTextField par exemple.
Voilà donc ce que je vous propose :

setTitle("fichier.txt");
add(new RichTextField(lireFichier("fichier.txt", MyScreen.DEVICE)));

En lançant l'application, vous verrez apparaître le contenu de votre fichier à l'intérieur du champ de texte, comme vous pouvez le voir ci-dessous :

Image utilisateur

Accéder « physiquement » aux données TP : un éditeur de texte

TP : un éditeur de texte

Lire et écrire dans un fichier Le cahier des charges

Nous revoici dans un chapitre un peu spécial, puisqu'il s'agit d'un TP !
Ce sera donc l'occasion pour vous de pratiquer à nouveau un peu avant de passer à d'autres notions plus théoriques. Au cours de ce TP nous allons, ou plutôt vous allez, réaliser un éditeur de texte qui vous permettra d'éditer différents types de fichiers. Bien entendu je vous fournirai la correction de celui-ci ainsi que des explications, qui vous permettront de finaliser ou de comparer votre solution avec la mienne.

Le cahier des charges

TP : un éditeur de texte La correction

Le cahier des charges

Spécification du projet

Au cours de ce TP, nous allons donc réaliser un éditeur de texte simplifié. Évidemment vous pourrez adopter le « design » que vous voulez pour cette application, néanmoins essayez de respecter au maximum mon interface graphique si vous souhaitez pouvoir comparer votre code avec le mien.
Sans plus attendre, je vous propose donc de faire un tour des éléments que vous aurez besoin de réaliser :

Puis pour finir, je vous laisse découvrir ce que j'obtiens en utilisant le code de la correction :

Image utilisateur
Image utilisateur

Enfin, pour fluidifier et améliorer l'utilisation des boutons, j'ai ajouté une boîte de dialogue pour confirmer l'enregistrement du fichier. D'autre part, j'interdis également l'enregistrement ou l'ouverture d'un fichier si aucun nom de fichier n'est renseigné. Pour cela j'utilise aussi des boîtes de dialogue.

Avant de commencer
Le champ de texte d'édition

Dans la partie précédente, je vous ai introduit les champs de texte éditables uniquement grâce à la classe EditField. Néanmoins il existe d'autres manières d'y parvenir, notamment à l'aide de la classe TextField.
Ainsi dans le cas de l'éditeur de texte, j'utilise ce type de champ de la façon suivante :

monTexte = new TextField(Field.EDITABLE);

Toutefois, vous pouvez tout à fait utiliser des champs de texte de type EditField ou encore BasicEditField. À vous de choisir la classe que vous souhaitez, tant que vous parvenez au bout du TP.

Le défilement du contenu

Les défilements sont en réalité liés aux conteneurs et non aux composants en eux-mêmes. Ainsi en ce qui concerne le défilement vertical dans notre application, vous serez certainement amenés à utiliser les constantes de la classe Manager. En particulier retenez votre attention sur les constantes suivantes : Manager.NO_VERTICAL_SCROLL, Manager.VERTICAL_SCROLL, Manager.VERTICAL_SCROLLBAR.
Je ne vous en dis pas plus, à vous de trouver comment utiliser des constantes pour obtenir le rendu désiré !

Les icônes des boutons

Enfin avant de finir, je vous propose de télécharger les différentes icônes de boutons afin que vous puissiez au mieux vous rapprocher des aperçus présentés plus haut.
Voici donc les quatre images en question :

nouveau.png
nouveau.png
ouvrir.png
ouvrir.png
sauvegarder.png
sauvegarder.png
quitter.png
quitter.png

Allez, c'est parti !


TP : un éditeur de texte La correction

La correction

Le cahier des charges Les explications

La correction

Comme dans le dernier TP, je vous propose dans un premier temps uniquement l'ensemble du code source sans explications. Je vous présenterai donc les trois classes que j'ai utilisées pour réaliser cette application.
N'hésitez donc pas à essayer cette application en copiant-collant le code source !

La classe MonApp

Aucune surprise pour cette classe maintenant bien connue :

package mypackage;

import net.rim.device.api.ui.UiApplication;

public class MonApp extends UiApplication
{
    public static void main(String[] args)
    {
        MonApp theApp = new MonApp();       
        theApp.enterEventDispatcher();
    }
    
    public MonApp()
    {        
        pushScreen(new Editeur());
    }    
}
La classe ConnexionFichier

La classe ConnexionFichier permet exclusivement de faire les connexions vers le fichier en cours d'édition. Nous retrouverons ainsi à l'intérieur les méthodes lireFichier() et ecrireFichier() définies dans le chapitre précédent.
Voici donc le code de cette classe :

package mypackage;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import net.rim.device.api.io.IOUtilities;

public class ConnexionFichier {
    
    private static final String DEVICE = "file:///store/home/user/documents/";
    
    public static void ecrireFichier(String fichier, String donnees){
        try 
        {    
            FileConnection fc = (FileConnection)Connector.open(DEVICE + fichier);
            if (fc.exists())
            {
                fc.delete();
            }
            fc.create();
            OutputStream os = fc.openOutputStream(); 
            os.write(donnees.getBytes());
            os.close();
            fc.close();
        }
        catch (IOException ioe) 
        {
            System.out.println(ioe.getMessage() );
        }
    }
    
    public static String lireFichier(String fichier){
        String donnees = new String("");
        try 
        {    
            FileConnection fc = (FileConnection)Connector.open(DEVICE + fichier);
            if (!fc.exists())
            {
                fc.create();
            }
            InputStream is = fc.openInputStream();
            byte[] data = IOUtilities.streamToBytes(is);
            is.close();
            fc.close();
            donnees = new String(data);
        }
        catch (IOException ioe) 
        {
            System.out.println(ioe.getMessage() );
        }
        return(donnees);
    }
    
}
La classe Editeur

Enfin, la classe Editeur contient l'ensemble de la logique de l'application. C'est donc cette classe que nous détaillerons dans la suite pour expliquer le fonctionnement de l'application.
En attendant, voici son code :

package mypackage;

import net.rim.device.api.system.EncodedImage;
import net.rim.device.api.ui.Color;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.Graphics;
import net.rim.device.api.ui.Manager;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.component.EditField;
import net.rim.device.api.ui.component.SeparatorField;
import net.rim.device.api.ui.component.TextField;
import net.rim.device.api.ui.container.HorizontalFieldManager;
import net.rim.device.api.ui.container.MainScreen;
import net.rim.device.api.ui.container.VerticalFieldManager;
import net.rim.device.api.ui.decor.Background;
import net.rim.device.api.ui.decor.BackgroundFactory;
import net.rim.device.api.ui.image.ImageFactory;

public final class Editeur extends MainScreen
{
    private EditField fichier;
    private HorizontalFieldManager mesBoutons;
    private TextField monTexte;
    
    public Editeur()
    {        
        super(Manager.NO_VERTICAL_SCROLL);
        
        mesBoutons = new HorizontalFieldManager(Field.FIELD_HCENTER|Field.USE_ALL_WIDTH);
        Background maCouleur = BackgroundFactory.createSolidBackground(Color.DARKGRAY);
        mesBoutons.setBackground(maCouleur);
        
        ButtonField nouveau = new ButtonField("");
        nouveau.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("nouveau.png")));
        nouveau.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                monTexte.setText("");
                fichier.setText("");
            }
        });
        
        ButtonField ouvrir = new ButtonField("");
        ouvrir.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("ouvrir.png")));
        ouvrir.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                if(fichier.getText().equals("")){
                    Dialog.alert("Veuillez renseigner un nom de fichier.");
                }else{
                    monTexte.setText(ConnexionFichier.lireFichier(fichier.getText()));
                }
            }
        });
        
        ButtonField sauvegarder = new ButtonField("");
        sauvegarder.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("sauvegarder.png")));
        sauvegarder.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                if(fichier.getText().equals("")){
                    Dialog.alert("Veuillez renseigner un nom de fichier.");
                }else{
                    ConnexionFichier.ecrireFichier(fichier.getText(), monTexte.getText());
                    Dialog.alert("Le fichier a été enregistré.");
                }
            }
        });
        
        VerticalFieldManager ajustement = new VerticalFieldManager(Field.USE_ALL_WIDTH);
        
        ButtonField quitter = new ButtonField("",Field.FIELD_RIGHT);
        quitter.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("quitter.png")));
        quitter.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                System.exit(0);
            }
        });
        ajustement.add(quitter);
        
        mesBoutons.add(nouveau);
        mesBoutons.add(ouvrir);
        mesBoutons.add(sauvegarder);
        mesBoutons.add(ajustement);
        
        fichier = new EditField("Fichier : ",""){
            public void paint(Graphics g){
                g.setColor(0x00FFFFFF);
                super.paint(g);
            }
        };
        maCouleur = BackgroundFactory.createSolidBackground(Color.GRAY);
        fichier.setBackground(maCouleur);
        
        VerticalFieldManager scrollTexte = new VerticalFieldManager(Manager.VERTICAL_SCROLLBAR|Manager.VERTICAL_SCROLL);
        monTexte = new TextField(Field.EDITABLE);
        monTexte.select(true);
        monTexte.setText("");
        scrollTexte.add(monTexte);
        
        add(new SeparatorField());
        add(mesBoutons);
        add(fichier);
        add(new SeparatorField());
        add(scrollTexte);
    }
}

Le cahier des charges Les explications

Les explications

La correction Le multimédia

Les explications

Les champs de texte
Le champ de texte d'édition

Tout d'abord, nous allons nous occuper du champ de texte d'édition, c'est-à-dire de celui dans lequel nous éditerons le contenu du fichier. Cependant avant de nous lancer sur la création de ce champ, nous allons dans un premier temps régler cette histoire de défilement. Comme nous l'avons dit, nous voulons un défilement uniquement sur ce champ de texte. C'est pourquoi nous allons commencer par bloquer le défilement du conteneur principal qui hérite de MainScreen.
Pour cela, nous allons donc appeler le constructeur de la superclasse comme ceci :

super(Manager.NO_VERTICAL_SCROLL);

Maintenant que le défilement est neutralisé sur le conteneur principal, nous allons pouvoir en créer un spécifique à notre champ de texte, qui lui en revanche autorisera le défilement.
Voici donc le conteneur en question :

VerticalFieldManager scrollTexte = new VerticalFieldManager(Manager.VERTICAL_SCROLLBAR|Manager.VERTICAL_SCROLL);

À présent, nous pouvons enfin nous préoccuper de notre champ de texte éditable. J'ai donc utilisé un champ de texte de type TextField que j'ai rendu éditable, sélectionnable et que j'ai initialisé.
Voici le code correspondant :

monTexte = new TextField(Field.EDITABLE);
monTexte.select(true);
monTexte.setText("");

Enfin, il suffit d'ajouter le champ de texte au conteneur pour que celui-ci puisse gérer le défilement suivant la taille du champ de texte :

scrollTexte.add(monTexte);
La saisie du nom de fichier

Pour renseigner le nom du fichier qui doit être édité, nous utiliserons cette fois la classe EditField dont nous utiliserons l'étiquette ainsi que le champ de saisie de texte. Étant donné que nous redéfinirons la couleur d'arrière-plan de ce champ, j'ai opté pour un texte de couleur blanche.
Voici donc l'initialisation de ce champ, ainsi que la redéfinition de la couleur d'écriture :

fichier = new EditField("Fichier : ",""){
    public void paint(Graphics g){
        g.setColor(0x00FFFFFF);
        super.paint(g);
    }
};

Comme promis, modifions la couleur d'arrière-plan :

maCouleur = BackgroundFactory.createSolidBackground(Color.GRAY);
fichier.setBackground(maCouleur);
Les différents boutons

À présent nous allons nous occuper des différents boutons qui permettent de gérer toute l'interaction de l'application. Ceux-ci seront alors stockés à l'intérieur d'un conteneur horizontal dont nous allons changer la couleur de fond.
Voici donc la définition de ce conteneur :

mesBoutons = new HorizontalFieldManager(Field.FIELD_HCENTER|Field.USE_ALL_WIDTH);
Background maCouleur = BackgroundFactory.createSolidBackground(Color.DARKGRAY);
mesBoutons.setBackground(maCouleur);
Nouveau

Commençons par le plus à gauche !
Le bouton « Nouveau » va nous servir à réinitialiser les deux champs de texte définis plus haut. Comme pour l'ensemble de nos boutons, nous utiliserons une icône plutôt qu'un texte pour définir celui-ci.
Déclarons donc ce bouton :

ButtonField nouveau = new ButtonField("");
nouveau.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("nouveau.png")));

Comme nous l'avons dit, ce bouton va nous servir à réinitialiser nos champs de texte. L'évènement associé au bouton n'est pas très compliqué, puisqu'il se contente d'appeler les méthodes setText() des deux champs de texte.
Voilà donc comment procéder :

nouveau.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        monTexte.setText("");
        fichier.setText("");
    }
});
Ouvrir

Comme pour le bouton précédent, déclarons-le :

ButtonField ouvrir = new ButtonField("");
ouvrir.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("ouvrir.png")));

Ici nous allons charger le contenu du fichier à l'intérieur du champ de texte d'édition. Pour cela, nous allons donc nous servir de la méthode lireFichier() de notre classe ConnexionFichier. Sachant que la difficulté résidait principalement dans le code de la méthode lireFichier(), nous n'avons à présent plus qu'à l'utiliser à l'intérieur d'une condition. Celle-ci nous permettra également d'avertir l'utilisateur si aucun nom de fichier n'est précisé, grâce à une boîte de dialogue.
Voici le résumé des opérations :

ouvrir.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        if(fichier.getText().equals("")){
            Dialog.alert("Veuillez renseigner un nom de fichier.");
        }else{
            monTexte.setText(ConnexionFichier.lireFichier(fichier.getText()));
        }
    }
});
Sauvegarder

Nous avons, comme d'habitude :

ButtonField sauvegarder = new ButtonField("");
sauvegarder.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("sauvegarder.png")));

La gestion de l'évènement du bouton « Sauvegarder » est en fait très proche de celle du bouton « Ouvrir ». Nous nous servirons cette fois la méthode ecrireFichier(), et nous utiliserons une boîte de dialogue sans quoi l'utilisateur ne verra absolument rien se passer à l'écran.
Voici le code de cet évènement :

sauvegarder.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        if(fichier.getText().equals("")){
            Dialog.alert("Veuillez renseigner un nom de fichier.");
        }else{
            ConnexionFichier.ecrireFichier(fichier.getText(), monTexte.getText());
            Dialog.alert("Le fichier a été enregistré.");
        }
    }
});
Quitter

Si vous avez bien visualisé les aperçus de l'application en début de chapitre, vous aurez noté que le bouton « Quitter » est aligné à droite, contrairement à tous les autres qui sont à gauche. C'est pourquoi nous allons donc être obligés d'utiliser un nouveau conteneur. Afin de gérer correctement la mise en place des boutons, nous allons définir un conteneur qui prendra toute la place disponible en largeur, afin de « pousser » les autres boutons à gauche.
Voici donc comment faire :

VerticalFieldManager ajustement = new VerticalFieldManager(Field.USE_ALL_WIDTH);

Puis nous allons créer notre bouton, et l'aligner à droite à l'intérieur de ce conteneur :

ButtonField quitter = new ButtonField("",Field.FIELD_RIGHT);
quitter.setImage(ImageFactory.createImage(EncodedImage.getEncodedImageResource("quitter.png")));

L'évènement de ce bouton est très simple puisqu'il s'agit uniquement de sortir de l'application.
Voilà donc le code correspondant :

quitter.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        System.exit(0);
    }
});

Enfin, n'oubliez pas d'ajouter cet élément au conteneur que nous venons de définir :

ajustement.add(quitter);
La mise en place des boutons

Maintenant que nous avons créé tous les boutons, nous allons pouvoir les disposer à l'intérieur du conteneur mesBoutons que nous avions déclaré auparavant. La seule chose à prendre en compte est l'ordre dans lequel ceux-ci doivent être ajoutés.
Voici ce que j'ai fait :

mesBoutons.add(nouveau);
mesBoutons.add(ouvrir);
mesBoutons.add(sauvegarder);
mesBoutons.add(ajustement);
La mise en page finale

Enfin pour clore ce TP, il ne nous reste plus qu'à ajouter l'ensemble de nos composants à l'intérieur du conteneur principal.
Voilà donc le code qui termine ce chapitre :

add(new SeparatorField());
add(mesBoutons);
add(fichier);
add(new SeparatorField());
add(scrollTexte);

La correction Le multimédia

Le multimédia

Les explications Transférer des fichiers

Dans ce chapitre, nous allons voir comment utiliser des fichiers multimédias à l'intérieur d'une application. Nous aborderons donc les fichiers de types audio et vidéo. Dans un premier temps, nous verrons comment transférer des fichiers depuis notre ordinateur vers le simulateur. Puis nous apprendrons à manipuler et gérer la lecture de ces fichiers.
Pour vous faciliter les choses à l'avenir, je vous proposerai également des classes toutes prêtes, que vous pourrez directement utiliser et qui vous feront gagner un temps précieux !

Transférer des fichiers

Le multimédia Jouer de l'audio

Transférer des fichiers

Créer un dossier de partage
Introduction

Sûrement vous êtes-vous déjà demandé comment transférer divers fichiers depuis votre ordinateur jusqu'au simulateur ?

Étant donné que nous allons apprendre à lire des fichiers multimédias dans ce chapitre, nous allons également devoir voir comment transférer facilement des fichiers vers notre simulateur. Pour réaliser ceci, il existe diverses techniques. Néanmoins, la solution la plus facile consiste à réutiliser le système de gestion de cartes mémoires externes.
La dernière fois, nous avons ajouté une carte en créant un fichier de type « .dmp », qui contenait alors l'intégralité des fichiers stockés à l'intérieur de notre carte mémoire. Sachez qu'il existe également une autre manière de créer une carte mémoire externe pour notre simulateur : utiliser un dossier de partage !
Le principe consiste à utiliser un « vrai » dossier de notre disque dur pour simuler la carte mémoire. Nous pourrons ainsi ajouter aisément des fichiers à l'intérieur de ce dossier, qui seront alors visibles depuis le simulateur et ses applications.

Mise en place

Tout d'abord, pour créer un dossier de partage, commencez par lancer votre simulateur. Ensuite, rendez-vous dans le gestionnaire de cartes mémoires par le menu Simulate > Change SD Card..., comme la dernière fois.
Puis cliquez sur le bouton Add Directory pour associer un dossier à une carte mémoire.
Je vous laisse ensuite choisir un dossier de votre disque dur à associer à votre nouvelle carte (voir la figure suivante).

Image utilisateur

Une fois créée, votre carte devrait apparaître dans le gestionnaire avec une taille non spécifiée « NA », ce qui veut dire que vous pouvez y insérer autant de fichiers que vous le souhaitez.
Il ne vous reste alors plus qu'à l'insérer dans le simulateur, en la sélectionnant et en cliquant sur le bouton Mount Selected.

Image utilisateur

Après quelques messages d'avertissement à l'intérieur de votre simulateur, votre carte est enfin prête à être utilisée.

Transférer des fichiers multimédias
Trouver des fichiers compatibles

Dans un premier temps, nous allons voir comment lire des fichiers sonores. C'est pourquoi nous aurons besoin d'une musique. Je vous propose donc de prendre la musique de votre choix au format MP3, et de la renommer simplement en « musique.mp3 ». Placez-la alors dans le dossier de partage créé juste avant.

Si les formats audio sont plutôt bien supportés dans l'ensemble, il n'en est pas de même en ce qui concerne les vidéos. Je vous avouerai qu'il a été plutôt difficile de trouver une vidéo qui soit directement compatible. Peut-être avez-vous déjà entendu parler du projet Orange nommé « Elephants Dream ». Si je vous en parle aujourd'hui, c'est parce que je vous propose de télécharger ce film : ED_1024.avi. Une fois le téléchargement terminé, placez également cette vidéo dans le dossier de partage afin que nous y ayons accès depuis notre simulateur.

Tester la compatibilité des fichiers

Enfin, avant de vous lancer tête baissée dans votre application et de passer des heures à essayer de la débugger, pensez au moins à vérifier la compatibilité de vos fichiers. Ainsi vous serez certains que les erreurs proviennent de votre code et non des fichiers que vous tentez de lire.

Pour commencer, rendez-vous dans le gestionnaire des fichiers afin de vérifier que les fichiers y sont bien présents.

Image utilisateur

Ensuite, utilisez les lecteurs par défaut du simulateur pour vérifier le bon fonctionnement de l'ensemble de vos fichiers. Pour cela, il vous suffit de cliquer sur chacun d'eux.
Voici par exemple sur la figure suivante la vidéo « Elephants Dream ».

Image utilisateur

Voyons maintenant comment utiliser ces divers fichiers multimédias au sein d'une application !


Le multimédia Jouer de l'audio

Jouer de l'audio

Transférer des fichiers Lire une vidéo

Jouer de l'audio

Lire un fichier audio
Jouer une musique

L'objectif du chapitre est de vous permettre de charger et manipuler différents fichiers multimédias. Ainsi la manipulation de ces données se fait via la classe Java nommée Player, elle-même associée à un fichier multimédia.
Pour démarrer, il nous faut donc déclarer un nouveau Player :

Player p;

Le chargement du fichier est alors une étape relativement simple, puisqu'il suffit d'utiliser la méthode createPlayer() de la classe Manager.

Voici donc comment instancier notre Player :

p = Manager.createPlayer("file:///SDCard/musique.mp3");

Si vous jetez un œil à la documentation de la classe Player, vous verrez que celle-ci passe par différentes étapes avant de pouvoir lancer la lecture du média définie par la constante STARTED (voir la figure suivante).

Image utilisateur

Ainsi il nous est donc nécessaire d'utiliser les trois méthodes realize(), prefetch() et start() dans cet ordre précis :

p.realize();
p.prefetch();
p.start();

Une fois la méthode start() appelée, la lecture du fichier audio est lancée. Vous pouvez alors, à tout moment, arrêter la lecture grâce à la méthode stop(). Nous verrons un peu plus loin comment combiner ces méthodes start() et stop() pour créer des boutons « Lecture » et « Pause ».
Également, vous pouvez régler le volume de sortie en utilisant la classe VolumeControl, comme ceci :

VolumeControl volume = (VolumeControl)p.getControl("VolumeControl");
volume.setLevel(30);

Vous devez aussi savoir que l'ensemble de ces opérations pourrait générer des erreurs lors de l'exécution du code. Je vous invite donc à encercler le tout d'un bloc try{....} catch{...}.
Voici donc un résumé de l'intégralité des opérations :

String SD_CARD = "file:///SDCard/";
Player p;
try 
{
    p = Manager.createPlayer("file:///SDCard/musique.mp3");
    p.realize();
    VolumeControl volume = (VolumeControl)p.getControl("VolumeControl");
    volume.setLevel(30);
    p.prefetch();
    p.start();
} 
catch (MediaException me)
{
    Dialog.alert(me.toString());
}
catch (IOException ioe) 
{
    Dialog.alert(ioe.toString());
}
Une classe prête à l'emploi

Sachant que j'aime bien ça et que cela vous facilite la vie, je vous propose donc une classe dont vous pouvez vous servir directement :

package mypackage;

import java.io.IOException;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
import javax.microedition.media.control.VolumeControl;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.container.VerticalFieldManager;

public class LecteurAudio extends VerticalFieldManager{
    
    private static final String SD_CARD = "file:///SDCard/";
    private Player p;
    
    public LecteurAudio(String fichier)
    {
        try 
        {
            p = Manager.createPlayer(SD_CARD + fichier);
            p.realize();
            VolumeControl volume = (VolumeControl)p.getControl("VolumeControl");
            volume.setLevel(30);
            p.prefetch();
            p.start();
        } 
        catch (MediaException me)
        {
            Dialog.alert(me.toString());
        }
        catch (IOException ioe) 
        {
            Dialog.alert(ioe.toString());
        }
    }
}

Vous remarquerez que j'ai créé une classe qui hérite de VerticalFieldManager. Ceci n'a pas de grande utilité pour l'instant, mais vous verrez que cela sera utile lorsqu'il s'agira par exemple d'ajouter des boutons de contrôle à celle-ci.
Voici alors comment utiliser cette classe LecteurAudio :

LecteurAudio monLecteur = new LecteurAudio("musique.mp3");
add(monLecteur);
Insérer du son à l'application
Jouer un son

Étant donné que nous parlons d'audio, je pense que c'est le bon moment pour vous montrer comment insérer du son à l'intérieur de votre application. En général, cette technique n'est pas vraiment faite pour insérer des musiques mais plutôt divers sons et bruitages utilisés principalement dans les jeux vidéo. Toutefois dans notre cas, nous réutiliserons notre fichier musique.mp3.
Je vous invite donc à ajouter ce fichier aux ressources de votre application. De mon côté, j'ai créé un nouveau dossier audio pour y stocker l'ensemble des fichiers de type audio.

Image utilisateur

Comme vous vous en doutez, la façon de créer un Player va être différente de ce que nous avons déjà vu. En revanche une fois celui-ci créé, nous le manipulerons identiquement.
Une manière de réaliser ceci est d'ouvrir le fichier en utilisant la classe principale de notre application. Pour cela, nous allons utiliser une classe un peu particulière : Class. Nous allons en fait stocker une référence à notre classe principale en nous servant de la méthode forName(), comme ceci :

Class cl = null;
try
{
    cl = Class.forName("mypackage.MyApp");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

Ensuite nous allons accéder au fichier audio via la méthode getResourceAsStream() et la classe InputStream, que vous connaissez déjà.
Voilà comment réaliser ceci :

if (cl!=null) {
    InputStream is = cl.getResourceAsStream("/musique.mp3");
}

Ainsi nous pouvons enfin créer notre Player grâce à l'instruction suivante :

p = Manager.createPlayer(is, "audio/mpeg");

Comme je l'ai déjà dit, une fois ouvert, notre Player s'utilise exactement de la même manière que précédemment.

Ajouter un bouton de contrôle

Pour aller un peu plus loin ici, nous allons réaliser un bouton de contrôle qui permettra d'arrêter et relancer la lecture du fichier audio.
Pour commencer, créons d'abord notre bouton, puis ajoutons-lui un écouteur « vide » :

ButtonField monBouton = new ButtonField("Pause",Field.FIELD_HCENTER);
monBouton.setChangeListener(new FieldChangeListener() {
    public void fieldChanged(Field field, int context) {
        // Instructions
    }
});

Étant donné que nous n'utiliserons qu'un seul bouton, nous allons devoir tester l'état de notre Player. Si vous avez bien suivi, vous devriez donc savoir qu'une fois en lecture, le Player est dans l'état STARTED.
Ainsi nous pouvons réaliser le contenu de la méthode fieldChanged() à l'aide d'une condition if :

if(p.getState() == Player.STARTED){
    p.stop();
    monBouton.setLabel("Lecture");
}else{
    p.start();
    monBouton.setLabel("Pause");
}
Une classe toute prête

Comme précédemment, je vous propose une classe intégrale, prête à l'emploi :

package mypackage;

import java.io.IOException;
import java.io.InputStream;
import javax.microedition.media.Manager;
import javax.microedition.media.MediaException;
import javax.microedition.media.Player;
import javax.microedition.media.control.VolumeControl;
import net.rim.device.api.ui.Field;
import net.rim.device.api.ui.FieldChangeListener;
import net.rim.device.api.ui.component.ButtonField;
import net.rim.device.api.ui.component.Dialog;
import net.rim.device.api.ui.container.VerticalFieldManager;

public class LecteurSon extends VerticalFieldManager{
    
    private Player p;
    private ButtonField monBouton;
    
    public LecteurSon(String fichier)
    {
        super(Field.USE_ALL_WIDTH);
        Class cl = null;
        try
        {
            cl = Class.forName("mypackage.MyApp");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if (cl!=null) {
            InputStream is = cl.getResourceAsStream("/" + fichier);
            try 
            {
                p = Manager.createPlayer(is, "audio/mpeg");
                p.realize();
                VolumeControl volume = (VolumeControl)p.getControl("VolumeControl");
                volume.setLevel(30);
                p.prefetch();
                p.start();
            }
            catch(MediaException me)
            {
                Dialog.alert(me.toString());
            }
            catch(IOException ioe)
            {
                Dialog.alert(ioe.toString());
            }
        }
        monBouton = new ButtonField("Pause",Field.FIELD_HCENTER);
        monBouton.setChangeListener(new FieldChangeListener() {
            public void fieldChanged(Field field, int context) {
                try
                {
                    if(p.getState() == Player.STARTED){
                        p.stop();
                        monBouton.setLabel("Lecture");
                    }else{
                        p.start();
                        monBouton.setLabel("Pause");
                    }
                	
                }
                catch(MediaException me)
                {
                    Dialog.alert(me.toString());
                }
            }
        });
        add(monBouton);
    }
}

Évidemment cette classe s'utilise de la même façon que LecteurAudio que nous avions créée auparavant :

LecteurSon monLecteur = new LecteurSon("musique.mp3");
add(monLecteur);

Avec l'arrivée du nouveau bouton, vous devriez maintenant voir l'intérêt de créer une classe qui hérite de VerticalFieldManager :

Image utilisateur