Création d'une application sans base de données avec le Visual Web Pack :
I. Introduction
Les fonctionnalités de Java Studio Creator sont maintenant disponibles dans Netbeans via le Visual Web Pack (VWP), ce qui facilite considérablement la vie du développeur qui peut maintenant se reposer sur les nombreuses fonctionnalités de Netbeans, y compris les nouveautés de Java EE 5. Mais qu'en est-il si on souhaite travailler directement avec des fichiers comme source de données?
La version finale du VWP vient à peine d'être publiée, et je n'ai pas encore suffisamment de recul pour vérifier que toutes les instabilités touchant la version Technology Preview ont bien été corrigées. En particulier, si le mode design ne fonctionne plus ou n'affiche pas vos tableaux, une solution: arrêtez Tomcat, faîtes un clean et rebuild de votre projet, puis fermez et rouvrez le projet dans NB.
Pour ce tutorial, je vais (dans une certaine mesure) recréer le projet créé par le tutorial
Insertion, mise à jour et suppression de Creator, tout en me contentant de travailler en lecture seule.
Pour commencer, j'ai récupéré et un peu modifié les données des tables PERSON et TRIP de l'exemple fourni - en rajoutant dans la table TRIP les données de la table TRIPTYPE. J'obtiens en résultat deux fichiers person.txt et trip.txt, avec le caractère tabulation comme délimiteur de champs.
Le tutorial est rédigé en suivant les règles du J2SE 1.4 (pas de generics, pas d'autoboxing). Lorsqu'on créé un projet VWP dans Netbeans, en choisissant Tomcat 5 comme cible, Netbeans recommande de fixer le source level à 1.4 (et je n'ai pas souhaité modifier ces options par défaut). Sachez simplement que l'utilisation des fonctionnalités du JDK 1.5 est tout à fait possible même avec Tomcat 5.
Le projet Netbeans fini est disponible ici.
II. Installation
Les éléments suivants sont requis pour faire mettre en pratique ce tutorial (à installer dans l'ordre). Netbeans et le JDK existent aussi en bundle, c'est à dire un téléchargement pour installer les deux programmes.
- JDK 1.5 minimum, requis pour faire fonctionner Netbeans (
téléchargement)
- Netbeans 5.5 (
téléchargement)
- Visual Web Pack pour Netbeans 5.5 (téléchargement accessible depuis la page précédente)
III. Gérer les fichiers
III.A. Attributs, getters et setters
Dans NB, commençons par créer un nouveau projet Visual Web Pack: Menu File | New Project... | Web | Visual Web Application. Appelons ce projet nodb, et pour limiter les risques de collision définissons le package par défaut com.developpez.dejardin.nodb.
A l'aide de notre explorateur de fichiers externe, déposons les fichiers person.txt et trip.txt dans le répertoire web de notre projet.
Déposons les fichiers de données dans le répertoire web du projet
Ensuite, pour commencer occupons nous de la lecture et de la récupération des données. Créons un nouveau fichier java, Person.java: Menu File | new File... | Java Class | Class name: Person et package com.developpez.dejardin.nodb, et initialisons le rapidement avec les noms des colonnes.
package com.developpez.dejardin.nodb;public class Person { int id; String name; String function; boolean frequentFlier; /** Creates a new instance of Person */ public Person(int id, String name, String function, boolean frequentFlier) { this.id = id; this.name = name; this.function = function; this.frequentFlier = frequentFlier; } }
Un petit refactoring va s'occuper de créer nos getters et setters (ainsi que passer les attributs en private): plaçons le curseur dans la classe, Menu Refactor | Encapsulate field. Sélectionnons le cas échéant tous les getters et setters, dévalidons "Use accessor even when field is accessible", affichons la prévisualisation des changements et appliquons. Il ne manque plus que la javadoc à créer (votez pour le bug
http://www.netbeans.org/issues/show_bug.cgi?id=48296). Vous pouvez constater que Netbeans a pris soin de régler l'accessibilité des champs sur private.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 518x402 et 15Ko |
La fonction Encapsulate field
J'ai recommandé de décocher l'option "Use accessor even when field is accessible" car cette option remplace l'usage direct des noms des champs dans la classe par le getter ou le setter. Par exemple, elle remplace this.id = id en this.setId(id). Je trouve cette encapsulation excessive et je préfère ne pas l'appliquer.
Voici le résultat:
package com.developpez.dejardin.nodb;public class Person { private int id; private String name; private String function; private boolean frequentFlier; /** Creates a new instance of Person */ public Person(int id, String name, String function, boolean frequentFlier) { this.id = id; this.name = name; this.function = function; this.frequentFlier = frequentFlier; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getFunction() { return function; } public void setFunction(String function) { this.function = function; } public boolean isFrequentFlier() { return frequentFlier; } public void setFrequentFlier(boolean frequentFlier) { this.frequentFlier = frequentFlier; } public int compareTo(Object o) { return compareTo((Person) o); } public int compareTo(Person person) { return (this.name).compareTo(person.getName()); }}
III.B. Rendre notre classe Comparable
Maintenant, nous allons implémenter l'interface Comparable. De cette manière, il sera facilement possible d'affecter un ordre par défaut à nos données (l'ordre alphabétique sur les noms dans notre cas), au lieu de se contenter de l'ordre dans lequel les données sont présentes dans le fichier person.txt. Modifions la déclaration de votre classe en
public class Person implements Comparable {
Netbeans va alors signaler en rouge que notre classe contient une erreur: en effet, nous indiquons que notre classe implémente Comparable, mais les méthodes nécessaires ne sont pas présentes. Cliquons sur l'ampoule que Netbeans affiche, et sélectionnons la correction proposée: Implements all abstract methods. Netbeans rajoute alors la méthode compareTo()... mais encore une fois soulignée de rouge: en effet la méthode est censée retourner un int, mais ici elle ne retourne rien.
Nous pouvons terminer d'implémenter Comparable comme suit: c'est la valeur de l'attribut name de Person qui va permettre de les classer. Utilisons également la méthode toLowerCase() pour éviter les erreurs de tri si des personnes ne sont pas toutes saisies de la même manière.
public int compareTo(Object o) { return compareTo((Person) o); } public int compareTo(Person person) { return (this.name.toLowerCase()).compareTo(person.getName ().toLowerCase()); }
III.C. Rendre notre classe Serializable
Lorsque Tomcat s'arrête, il essaie de conserver les informations relatives aux sessions des utilisateurs, afin de pouvoir redémarrer plus tard dans les mêmes conditions que précédemment. Pour ce faire, Tomcat va tenter de sérialiser tous les objets en mémoire. Afin d'éviter de remplir nos fichiers de logs avec de disgracieuses java.io.NotSerializableException, nous allons déclarer que notre classe implémente l'interface Serializable.
Complétons la classe Person en rajoutant Serializable dans la liste des interfaces:
public class Person implements Comparable, Serializable {
L'ampoule et le surlignage rouge signalant une erreur apparaissent. Cliquons sur l'ampoule, une suggestion apparaît signalant de déclarer la classe java.io.Serializable. Pas de problème, on applique cette suggestion. Puis, rien plus d'erreur. A la différence de l'interface Comparable, Serializable ne nécessite pas d'implémenter de classe abstraites, l'interface sert uniquement de marqueur pour signaler que la classe est bien sérialisable (ce qui est le cas car tous les champs sont eux-mêmes sérialisables). Java s'occupe de tout.
Toutefois, une petite modification du code est nécessaire: en effet, une classe sérialisable doit posséder un constructeur vide, ce qui n'est pas le cas de la notre. De plus, il est fortement recommandé de rajouter un champ private static final long serialVersionUID dans notre classe pour lui attribuer un numéro de version unique. Complétons donc notre méthode avec le code suivant:
private static final long serialVersionUID = 1L; /** Creates a new instance of Person */ public Person() { }
Plus d'information sur la sérialisation: La
FAQ de développez.com Article
La sérialisation binaire en Java par Yann D'ISANTO sur developpez.com La
javadoc
III.D. Les classes terminées
Notre fichier Person.java est désormais terminé. Nous pouvons le retrouver
ici. De la même manière, il faut également créer le fichier Trip.java, que nous trouverons
ici. Voici ci dessous le descripteur de la classe ainsi que ses attributs.
// skipping package and import...public class Trip implements Comparable, Serializable { private int tripId; private int personId; private Date departureDate; private String fromCity; private String toCity; private String tripType; private String tripDesc; // Skipping contructors, getters, setters, compareTo()...
III.E. Lire et gérer les données en session
Il est maintenant nécessaire de s'occuper de la gestion des fichiers par l'application. Un peu à la manière des datasources "databases" de Creator, nous allons travailler depuis le bean SessionBean1.java créé avec le projet. Il faut d'abord indiquer à l'application où se trouvent les fichiers de données. Pour cela nous allons utiliser les paramètres de contexte de l'application web. En effet, une application JSF reste avant tout une application JSP/Servlet, elle dispose d'un fichier web.xml qu'il est possible d'exploiter.
Dans le projet, cliquer sur "Configurations files" et ouvrir le web.xml. Dans Général | context parameters, ajoutons des paramètres indiquants que les fichiers sont dans le répertoire racine de l'application (une fois déployée, ce qui correspond au répertoire web du projet).
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 570x458 et 16Ko |
Indiquons à notre application où trouver ses données
Il serait inutile de relire et reparser les fichiers à chaque requête de l'utilisateur. Nous allons donc créer deux méthodes, une pour chaque fichier, pour trouver dans le contexte de l'application le chemin des fichiers, lire leur date de dernière modification, la comparer avec une valeur stockée en session, et le cas échéant ordonner une mise à jour.
/** * last time the person file was modified */ private Date personDate = new Date(0); /** * last time the trip file was modified */ private Date tripDate = new Date(0); public void checkPerson() { // Get the parameter person set in web.xml // This parameter value is relative to the root of the application String personPath = getExternalContext().getInitParameter("person"); // compare last modified time of the file to the internal last value if (personPath != null) { // getting servlet context ServletContext context = (ServletContext) getExternalContext().getContext(); // getting the full path of the file, depending of the place from // where you run this example personPath = context.getRealPath(personPath); File personFile = new File(personPath); if ((personFile.exists()) && (personFile.lastModified() > personDate.getTime())) { managePerson(personFile); } } } // idem for checkTrip()
Toujours dans Session1.java, nous allons rajouter la logique pour parser et gérer les fichiers. Chaque ligne des fichiers person.txt et trip.txt sera bien sûr mémorisée respectivement dans un objet Person et Trip, mais comment organiser ces données? Il nous faut une collection.
Souvenons-nous de Person.java. Nous avions pris soin d'implémenter l'interface Comparable dans cette classe, c'est le moment de l'utiliser. Nous choisissons la collection TreeSet. L'avantage de cette collection est que chaque nouvel élément qui y est rajouté est trié en respectant l'ordre naturel des éléments, c'est à dire l'ordre obtenu par le biais de l'interface Comparable. De cette manière, même si les personnes ne sont pas rangées dans l'ordre dans notre fichier person.txt, elles seront dans l'ordre alphabétique en mémoire et dans toutes les fonctions qui y feront appel.
C'est décidé, les personnes seront stockées dans un TreeSet. Mais en ce qui concerne les Trips? Réfléchissons à l'usage que nous souhaitons en faire... Nous voulons afficher les voyages réalisés par une personne. Il serait donc intéressant de pouvoir dans un premier temps regrouper tous les voyages réalisés par une personne. Comme pour Person.java, Trip.java implémente Comparable, utilisons à nouveau un TreeSet pour stocker les voyages (triés par date de départ cette fois). Et pour regrouper tous ces TreeSet, une simple Hashtable, avec l'id des personnes comme clef, fera l'affaire.
Dans cet exemple, la gestion des exceptions est rudimentaire: toute ligne de données qui est mal formée sera simplement ignorée.
/** * handle the contens of the person file */ private TreeSet personTreeSet = new TreeSet(); /** * handle the content of the trip file, keyed by personId */ private Hashtable tripHashtable = new Hashtable(); /** * parses the person file and store results in personTreeSet * @param personFile the File where to find data */ private void managePerson(File personFile) { String strLine = null; try { // getting file to work with FileInputStream in = new FileInputStream(personFile); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // for each line of the file while ((strLine = reader.readLine()) != null) { int personId = 0; String name = null; String function = null; boolean frequentFlier = false; // parsing each line -- take care, bad exceptions handling String[] elements = strLine.split("t"); try { personId = Integer.parseInt(elements[0]); name = elements[1]; function = elements[2]; frequentFlier = ("1".equals(elements[3])) ? true : false; } catch (NumberFormatException nfe) { // Exception handling here continue; } // creating new Person object and adding it to the treeset // I used treeset and the interface comparable in the Person // class to have a sorted collection Person person = new Person(personId, name, function, frequentFlier); personTreeSet.add(person); } // end while this.personDate = new Date(personFile.lastModified()); } catch (FileNotFoundException ex) { log("Error finding person.txt file: " + ex.toString()); error("Error finding person.txt file: " + ex.toString()); ex.printStackTrace(); } catch (IOException ex) { log("Error parsing person.txt file: " + ex.toString()); error("Error parsing person.txt file: " + ex.toString()); ex.printStackTrace(); } } /** * parses the trip file and store results in tripHashtable * @param tripFile the File where to find data */ private void manageTrip(File tripFile) { String strLine = null; DateFormat df = DateFormat.getDateInstance(DateFormat.SHORT, Locale.FRANCE); try { // getting file to work with FileInputStream in = new FileInputStream(tripFile); BufferedReader reader = new BufferedReader(new InputStreamReader(in)); // for each line of the file while ((strLine = reader.readLine()) != null) { int tripId = 0; int personId = 0; Date departureDate = null; String fromCity = null; String toCity = null; String tripType = null; String tripDesc = null; // parsing each line -- take care, bad exceptions handling String[] elements = strLine.split("t"); try { tripId = Integer.parseInt(elements[0]); personId = Integer.parseInt(elements[1]); departureDate = df.parse(elements[2]); fromCity = elements[3]; toCity = elements[4]; tripType = elements[5]; tripDesc = elements[6]; } catch (NumberFormatException nfe) { // Exception handling here continue; } catch (ParseException ex) { // Exception handling here continue; } // creating new Trip object Trip trip = new Trip(tripId, personId, departureDate, fromCity, toCity, tripType, tripDesc); // storing Trip object into a hashtable of treeset TreeSet trips4user = (TreeSet) tripHashtable.get(new Integer(personId)); if (trips4user == null) { trips4user = new TreeSet(); } trips4user.add(trip); tripHashtable.put(new Integer(personId), trips4user); } // end while this.tripDate = new Date(tripFile.lastModified()); } catch (FileNotFoundException ex) { log("Error finding trip.txt file: " + ex.toString()); error("Error finding trip.txt file: " + ex.toString()); ex.printStackTrace(); } catch (IOException ex) { log("Error parsing trip.txt file: " + ex.toString()); error("Error parsing trip.txt file: " + ex.toString()); ex.printStackTrace(); } }
Maintenant, il suffit de rajouter l'appel à ces fonctions lors de l'initialisation du bean de session, dans Init():
// Perform application initialization that must complete // *after* managed components are initialized // TODO - add your own initialization code here checkPerson(); checkTrip();
Enfin, comme ces données devront être accessibles depuis la Page1.java, nous rajoutons un getter sur personTreeSet et TripHashtable.
public Hashtable getTripHashtable() { return tripHashtable; } public TreeSet getPersonTreeSet() { return personTreeSet; }
IV. Construire un dropdown
Concentrons nous désormais sur quelque chose qui concerne davantage le VWP: nous devons rajouter un dropdown sur notre page d'accueil. Avec une base de données, pas de problème: il suffit de glisser-déposer une table sur un dropdown en mode design. Ici, c'est un peu plus compliqué, mais ce n'est pas non plus irréalisable!
Plaçons d'abord un dropdown sur notre page, appelons-le dropdownPerson, ajoutons lui un label "Choisissez un nom".
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 629x295 et 13Ko |
Création et initialisation de notre dropdown.
Passons en mode java et rajoutons une méthode getPerson4Dropdown():
/** * Return a list of options describing a dropdown elements. The value of the option is an Integer, while the text * displayed shall be the name of the person. */ public List getPerson4Dropdown() { List returnValue = new ArrayList(); for (Iterator it = this.getSessionBean1().getPersonTreeSet().iterator (); it.hasNext();) { Person elem = (Person) it.next(); Integer id = new Integer(elem.getId()); String name = elem.getName(); // creating option: value = id, label = name Option option = new Option(id, name); // adding the new option to the list returnValue.add(option); } return returnValue; }
Désormais, il reste à lier cette méthode au dropdown. En mode design, sélectionnons le dropdown, puis cliquons sur les ... de l'attribut items. Nous pouvons désormais lier (en anglais "to bind") les éléments de notre dropdown à la propriété person4Dropdown de la classe Page1. Notez que le VWP avait pris le soin de créer une propriété dropDownPersonDefaultOptions. Dès que nous affecterons le dropdown à notre propre méthode, cette propriété "par défaut" sera supprimée automatiquement.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 590x521 et 16Ko |
Lions notre dropdown
Il est désormais possible de tester notre dropdown, simplement en cliquant sur l'icône Run: Tomcat se lance, et notre page apparaît:
Premier aperçu de notre page
V. Remplir une table
Maintenant, rajoutons une table sur notre page web. Appelons la "tableTrips", et modifions son title en "Voyages".
V.A. Créer un dataProvider pour nos données
Pour pouvoir afficher nos données dans une table, il faut utiliser un objet dataProvider. Le VWP est livré avec plusieurs types de dataProviders, il suffit d'étendre celui qui nous intéresse. Ainsi, créons une nouvelle classe TripArrayDataProvider qui étend ObjectArrayDataProvider: Menu File | new File | Java Classes | Java Class | name = TripArrayDataProvider, package = com.developpez.dejardin.nodb. Modifions le fichier comme suit:
package com.developpez.dejardin.nodb;import com.sun.data.provider.impl.ObjectArrayDataProvider ;import java.util.Date;/** * * @author valere dejardin */public class TripArrayDataProvider extends ObjectArrayDataProvider { /** Creates a new instance of TripArrayDataProvider */ public TripArrayDataProvider() { Date date = new Date(); Trip trip1 = new Trip(1, 2, date, "Paris", "Anvers", "CONF", "Javapolis"); Trip trip2 = new Trip(3, 4, date, "Paris", "San Francisco", "CONF", "Javaone"); this.setArray(new Trip[] { trip1, trip2 }); }}
Ensuite, rajoutons une référence à ce nouvel objet dans Page1.java:
private TripArrayDataProvider tripArrayDataProvider = new TripArrayDataProvider(); public TripArrayDataProvider getTripArrayDataProvider() { return tripArrayDataProvider; }
La manière dont le constructeur est initialisé joue deux rôles: elle permet au VWP d'afficher un look par défaut à une table qui utilise ce dataprovider. Ensuite, le VWP récupère le premier élément du dataprovider et examine ses beans patterns pour déterminer quelles sont les données à afficher. Repassons en mode design, sélectionnons la nouvelle table et affichons sont table layout (clic droit | Table layout). Nous avons alors un dropdown qui nous propose le choix entre notre nouveau tripArrayDataProvider et le DefaultTableDataProvider. Choisissons le tripArrayDataProvider puis OK, la magie du VWP entre en oeuvre!
Astuce: la version Technology Preview du VWP présentait un problème à ce niveau: le mode design du VWP ne listait pas le nouveau dataProvider. La méthode pour résoudre ce problème était Menu Build | Clean and Rebuild Main Project, puis fermer et rouvrir le projet. De plus, pour faire un clean du projet il est parfois nécessaire de stopper Tomcat. La version finale qui vient de sortir semble corriger ce défaut, mais vous disposez toujours de cette solution.
Il ne reste plus qu'à faire un peu de mise en page du tableau. Dans le Table Layout, Modifiez les noms des colonnes, et classez- les de manière plus appropriée. Il reste une petite contrariété: la date du départ est affichée dans un format long assez disgracieux. Nous allons la mettre au format français. Sélectionnons, en mode design, la case affichant la date (attention à sélectionner un composant de type staticField et non tableColumn). Dans le panneau Properties de droite sélectionnons la valeur (new DateTimeConverter).
Un nouveau composant, dateTimeConverter1, a été rajouté à notre page. Sélectionnons le dans le panneau Outline en bas à gauche, et modifions ses propriétés comme suit:
Configuration du dateTimeConverter1
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 739x175 et 6Ko |
Le résultat final pour notre table en mode design
V.B. Récupérer les données de voyages relatives à une personne.
A cette étape, nous avons une jolie table affichée en mode design, mais si nous l'exécutons, la table restera affichée dans le navigateur avec les valeurs par défaut, ce qui n'est pas trop le but souhaité. Il faut donc remplir proprement le dataProvider.
Toujours en mode design, sélectionnons le dropdown, clic droit et validons "Submit value on change". Puis de nouveau clic droit | Edit Event Handler | Process Value Change. Nous passons en mode java et le VWP nous rajoute une méthode dropDownPerson_processValueChange().
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 512x290 et 8Ko |
Créons la méthode qui va gérer les évènements sur le dropdown
Rajoutons le code suivant dans le corps de cette méthode:
public void dropDownPerson_processValueChange(ValueChangeEvent event) { String value = (String) dropDownPerson.getValue(); Integer iValue; if (value == null) { // if value is null, then we get the first value of the persons collection iValue = new Integer(((Person) this.getSessionBean1().getPersonTreeSet().first()) .getId()); } else { iValue = new Integer(Integer.parseInt(value)); } // we get the treeset of trips patchin the given personId Set set = (Set) this.getSessionBean1().getTripHashtable().get(iVal ue); Trip[] trips2Display = (Trip[]) set.toArray(new Trip[0]); // we store the array and the personId into the session for further use this.getSessionBean1().setTrips2display(trips2Disp lay); this.getSessionBean1().setCurrentPersonId(iValue); // we update the dataprovider with the new array tripArrayDataProvider.setArray(trips2Display); }
Vous pouvez constater que cette méthode va stocker le tableau des voyages à afficher dans le bean de session, ainsi que la référence à la valeur actuelle du dropdown. Nous procédons de cette manière pour simplifier la navigation lorsque plusieurs pages sont impliquées. Il faut donc rajouter quelques lignes de code dans SessionBean1.java:
private Trip[] trips2display = null; private Integer currentPersonId = null; public Trip[] getTrips2display() { return trips2display; } public void setTrips2display(Trip[] trips2display) { this.trips2display = trips2display; } public Integer getCurrentPersonId() { return currentPersonId; } public void setCurrentPersonId(Integer currentPersonId) { this.currentPersonId = currentPersonId; }
Il reste simplement à initialiser le tableau lors du premier affichage. Profitons-en également pour vérifier si les données n'ont pas été modifiées avec de nouvelles versions des fichiers de référence. Dans init() de Page1.java rajoutons les lignes suivantes.
// Perform application initialization that must complete // *after* managed components are initialized // TODO - add your own initialization code here this.getSessionBean1().checkPerson(); this.getSessionBean1().checkTrip(); tripArrayDataProvider.setArray(this.getSessionBean 1().getTrips2display());
Et dans prerender, rajoutons ce qui suit.
public void prerender() { String person = (String) dropDownPerson.getValue(); if (person == null) { if (this.getSessionBean1().getCurrentPersonId() == null) { // this is the fisrt call to the page, nothing is initialized Integer personId = new Integer(((Person) this.getSessionBean1().getPersonTreeSet().first()) .getId()); Set tripSet2Display = (TreeSet) this.getSessionBean1().getTripHashtable().get(pers onId); Trip[] trips2Display = (Trip[]) tripSet2Display.toArray(new Trip[0]); // we store the array into the session for further use this.getSessionBean1().setTrips2display(trips2Disp lay); this.getSessionBean1().setCurrentPersonId(personId ); dropDownPerson.setValue(personId.toString()); // we update the dataprovider with the new array tripArrayDataProvider.setArray(trips2Display); } else { // we are arriving here from another page (Page2?) The dropdown default value has to be set with // the session stored value. Integer personId = this.getSessionBean1().getCurrentPersonId(); dropDownPerson.setValue(personId.toString()); } } }
Nous pouvons désormais exécuter notre page pour avoir le résultat suivant:
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 731x220 et 13Ko |
La table affiche bien les informations souhaitées
VI. Rajouter une seconde page et de la navigation
A partir de notre Page1, nous allons rajouter une seconde page. Ici, il faut faire exactement comme dans le cas traditionnel avec une base de données. Cette navigation peut parfois poser problème et on peut se retrouver avec des résultats surprenants (la Page2 affiche les données par défaut du ObjectArrayDataProvider, ou bien lorsqu'on revient à la page de départ la dernière valeur sélectionnée du dropdown a été oubliée). Cette Page2 est donc, si on veut, une preuve de la validité de l'architecture.
Commençons par créer cette Page2, de manière très simple: Menu File | New | Web Page | Page. Sur cette page je rajoute simplement un objet StaticText et un bouton auquel j'attribue la valeur "Retour". Puis retournons dans SessionBean1.java pour rajouter un champ texte:
private String summary = null; public String getSummary() { return summary; } public void setSummary(String summary) { this.summary = summary; }
Nous allons lier le texte affiché dans notre staticText à ce tout nouveau champ: clic droit sur le staticText | Bind to Data | Bind to an object | SessionBean1 | Summary.
La Page 2
Cette page est finie, retournons dans Page1. En effet, si nous utilisons la méthode getSummary(), il faut bien que initialiser cette valeur avec un setSummary() quelque part. Ouvrons le Table Layout de notre tableau, rajoutons une colonne. Nous l'appelons Details, réglons son type à "Button" et son texte à "..." (par exemple).
Ajoutons une colonne de boutons à notre table
Après avoir validé la fenêtre, la colonne de boutons apparaît bien. Sélectionnons le bouton (pas la colonne), clic droit | Edit Action. L'affichage passe en mode Java et la méthode button1_action() est créée. Nous la remplissons comme suit:
public String button1_action() { String from = (String) getValue("#{currentRow.value['fromCity']}"); String to = (String) getValue("#{currentRow.value['toCity']}"); this.getSessionBean1().setSummary("De " + from + " à " + to); return null; }
Il ne reste plus qu'à valider la navigation. Dans l'onglet Projects, double clic sur Navigation. Cliquons sur Page1, et tirons un trait de button1 vers la Page2. Appelons cette règle de navigation goDetails. De même, créons la règle goMaster entre le bouton de la Page2 et la Page1. Nous pouvons remarquer que la méthode button1_action() de Page1 ne retourne plus null, mais "goDetails".
La fenêtre Navigation
Et hop! un dernier "run" pour afficher le résultat final!
Le résultat final
VII. Conclusion
Cet article vous a montré plusieurs fonctionnalités de base du Visual Web Pack de Netbeans, telles que l'accès et la modification de données de la page JSP depuis la partie Java, l'usage d'un DateTimeConverter ou du TableLayout. Mais surtout, nous somme allés plus loin dans l'usage du VWP pour l'adapter véritablement à nos données: Créer un dropdown dynamiquement, et surtout utiliser les possibilités offertes par les dataProviders.
Cette approche manuelle nous a permis d'appréhender davantage le fonctionnement du VWP, et de mettre en évidence à la fois sa puissance et sa souplesse.
VIII. Sources et Crédits
De nombreuses ressources m'ont permis depuis plusieurs années de comprendre et maîtriser Java Studio Creator puis le VWP. Je citerais les
tutoriaux Creator, le
forum Creator, et depuis la sortie en Technology Preview du VWP
la mailing list nbusers de Netbeans.
Winston Prakash publie un
blog très intéressant sur les possibilités de Creator, et il explique d'ailleurs dans deux de ses billets l'utilisation des ObjectListDataProviders et ObjectArrayDataProvider, mais j'ai retrouvé ces deux billets (relativements vieux) après avoir rédigé le brouillon de cet article. Le declic qui m'a permis de réaliser ce tutorial est venu d'un message parmi d'autres sur nbusers expliquant comment construire ses dataproviders.
Créez une application en Java avec GWT (Google Web Toolkit) :
I. Introduction
I-a. Présentation
Au cours de cet article, nous verrons comment utiliser et intégrer le framework GWT avec Eclipse.
La présentation se fera par la réalisation d'une application permettant la consultation et le filtre d'une liste de contacts.
Les exemples de cet article se feront sous Windows, mais devraient s'adapter à un autre environnement sans trop de difficultés.
II. Mise en place de l'environnement
II-A. Environnement de développement
Pour commencer, il faut créer le répertoire de base du projet.
Création du répertoiremkdir demoGWT
On se place dans ce répertoire pour travailler.
cd demoGWT
Création des fichiers projet Eclipse
projectCreator -eclipse demoGWT
Created directory D:gwt-windows-1.1.10demoGWTtestCreated file D:gwt-windows-1.1.10demoGWT.projectCreated file D:gwt-windows-1.1.10demoGWT.classpath
Création de l'arborescence applicative
applicationCreator -eclipse demoGWT com.developpez.exemple.gwt.client.MonApplication
Created directory D:gwt-windows-1.1.10demoGWTsrcCreated directory D:gwt-windows-1.1.10srcdemoGWTcomdeveloppezexemplegwtCreat ed directory D:gwt-windows-1.1.10srcdemoGWTsrccomdeveloppezexemplegwt clientCreated directory D:gwt-windows-1.1.10srcdemoGWTsrccomdeveloppezexemplegwt publicCreated file D:gwt-windows-1.1.10srcdemoGWTcomdeveloppezexemplegwtMonA pplication.gwt.xmlCreated file D:gwt-windows-1.1.10srcdemoGWTcomdeveloppezexemplegwtpubl icMonApplication.htmlCreated file D:gwt-windows-1.1.10srcdemoGWTcomdeveloppezexemplegwtclie ntMonApplication.javaCreated file D:gwt-windows-1.1.10demoGWTMonApplication.launchCreated file D:gwt-windows-1.1.10demoGWTMonApplication-shell.cmdCreated file D:gwt-windows-1.1.10demoGWTMonApplication-compile.cmd
Maintenant que la structure applicative est créée, on pourrait simplement utiliser un éditeur de texte.
Mais utilisons Eclipse, pourquoi se priver de fonctionnalités avancées?
Pour cela, il suffit d'ouvrir Eclipse.
Dans le menu
file, choisir
import
Choisir
Existing Projects into workspace
Cibler le répertoire demoGWT et importer.
On obtient
II-B. Environnement de production
En production, on aura évidemment besoin d'un serveur applicatif ou d conteneur web tel Tomcat.
Pour l'installation et le fonctionnement de Tomcat, je vous laisse consulter
ce tutoriel.
Ensuite, il vous suffit de déployer votre application comme n'importe quelle autre application Java.
III. Bonjour le monde
Comme tout bon tutoriel, nous allons commencer par un "Hello World !".
Pour cela, lancer le script MonApplication-shell.cmd. Un navigateur va se lancer.
Il vous permettra de voir le rendu de votre application en cours de développement.
La page que vous voyez apparaître devrait ressembler à
celle-ci.
Félicitation, vous venez de réussir votre premier "HelloWorld" avec le framework GWT.
IV. On complique un peu
IV-A. Un peu de nettoyage
Le ménage commence par la page HTML qui sert de support à notre application.
On supprime le surplus du HelloWorld pour ne conserver que le titre de la page et les balises propres à GWT.
On obtient alors la page suivante :
MonApplication.html <head> <!-- --> <!-- Ici on défini le titre de notre fenêtre --> <!-- --> <title>MonApplication GWT</title> <!-- --> <!-- La balise suivante permet de faire le --> <!-- lien entre la page html et le code GWT --> <!-- --> <meta name='gwt:module' content='com.developpez.exemple.gwt.MonApplication '> </head> <body> <!-- --> <!-- L'inclusion de gwt.js est obligatoire. --> <!-- Elle placer ici pour des raisons de --> <!-- preformances. --> <!-- --> <script language="javascript" src="gwt.js"></script> <!-- La balise suivante est optionnelle, elle gère l'historique des pages --> <iframe id="__gwt_historyFrame" style="width:0;height:0;border:0"></iframe> </body> </html>
IV-B. Entrypoint
Lorsque l'on développe une application AWT/SWING, on utilise une classe avec une méthode main pour lancer notre application.
Etant donné que les classes Java que nous allons écrire seront converties en code Javascript, l'utilisation d'une méthode main est impossible.
Dans notre cas, notre classe de lancement sera la page html de notre application et notre méthode main la balise meta.
Déclaration du module<meta name='gwt:module' content='com.developpez.exemple.gwt.MonApplication '>
L'attribut content de la balise permet de faire le lien vers le fichier de configuration de notre module GWT.
MonApplication.gwt.xml<module> <!-- Inherit the core Web Toolkit stuff. --> <inherits name='com.google.gwt.user.User'/> <!-- Specify the app entry point class. --> <entry-point class='com.developpez.exemple.gwt.client.MonApplic ation'/></module>
La balise entry-point permet de spécifier quelle sera la classe principale de notre application.
Cette classe doit implémenter l'interface Entrypoint.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 714x314 et 116Ko |
Il est intéressant aussi d'implémenter l'interface WindowResizeListener.
Cette interface permet de recevoir les informations liées à la taille de la fenêtre du navigateur et donc de redimensionner notre application proportionnellement à celle-ci.
Le code de la classe devrait ressembler à ça:
MonApplication.javapackage com.developpez.exemple.gwt.client;import com.google.gwt.core.client.EntryPoint;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.WindowResizeListener;/** * Entry point classes define <code>onModuleLoad()</code>. */public class MonApplication implements EntryPoint , WindowResizeListener { /** * This is the entry point method. */ public void onModuleLoad() { // Hook the window resize event, so that we can adjust the UI. Window.addWindowResizeListener(this); // Get rid of scrollbars, and clear out the window's built-in margin, // because we want to take advantage of the entire client area. Window.enableScrolling(false); Window.setMargin("10px"); } public void onWindowResized(int width, int height) { // TODO Auto-generated method stub }}
On obtient donc une application GWT pleinement fonctionnelle, mais qui ne fait rien.
IV-C. Création des éléments graphiques
Les classes des éléments graphiques de GWT reprennent les noms et comportements que l'on a l'habitude de manipuler quand on réalise une application AWT/SWING. Avec GWT, nous manipulerons donc des Panels et autres Label et TextBox.
IV-C-1. Les Panels
Les panels se déclinent en plusieurs classes qui ont chacune un comportement spécifique.
Dans cette mise en application nous utiliserons les panels suivants :
- DockPanel
- HorizontalPanel
- VerticalPanel
Le DockPanel servira de base d'accueil pour notre application. Il contiendra dans sa partie
Nord un VerticalPanel et dans sa partie
Centre un HorizontalPanel.
Le verticalPanel contiendra les champs de recherche et le VerticalPanel présentera les résultats.
On commence par ajouter le DockPanel à l'application. Ce qui nous donne pour la classe MonApplication.java le code suivant :
MonApplication.javapackage com.developpez.exemple.gwt.client;import com.google.gwt.core.client.EntryPoint;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.WindowResizeListener;import com.google.gwt.user.client.ui.DockPanel;import com.google.gwt.user.client.ui.RootPanel;/** * Entry point classes define <code>onModuleLoad()</code>. */public class MonApplication implements EntryPoint , WindowResizeListener { private DockPanel outer = new DockPanel(); /** * This is the entry point method. */ public void onModuleLoad() { outer.setWidth("100%"); outer.setHeight("100%"); // Hook the window resize event, so that we can adjust the UI. Window.addWindowResizeListener(this); // Get rid of scrollbars, and clear out the window's built-in margin, // because we want to take advantage of the entire client area. Window.enableScrolling(false); Window.setMargin("10px"); RootPanel.get().add(outer); } public void onWindowResized(int width, int height) { // TODO Auto-generated method stub }}
Nous allons créer deux panels, RechechePanel et ContactsPanel qui étendent respectivement HorizontalPanel et VerticalPanel.
Le fait de ne pas utiliser directement les panels génériques nous permet d'alléger la classe MonApplication et surtout nous permettra d'avoir un code lisible.
Pour la classe ContactsPanel

et pour la classe RecherchePanel

Il ne nous reste plus qu'à les insérer dans le DockPanel.
MonApplication.javapackage com.developpez.exemple.gwt.client;import com.developpez.exemple.gwt.client.panel.ContactsPa nel;import com.developpez.exemple.gwt.client.panel.RechercheP anel;import com.google.gwt.core.client.EntryPoint;import com.google.gwt.user.client.Window;import com.google.gwt.user.client.WindowResizeListener;import com.google.gwt.user.client.ui.DockPanel;import com.google.gwt.user.client.ui.HorizontalPanel;import com.google.gwt.user.client.ui.VerticalPanel;import com.google.gwt.user.client.ui.RootPanel;/** * Entry point classes define <code>onModuleLoad()</code>. */public class MonApplication implements EntryPoint , WindowResizeListener { private DockPanel outer = new DockPanel(); /** * This is the entry point method. */ public void onModuleLoad() { HorizontalPanel recherchePanel =new RecherchePanel(); VerticalPanel contactsPanel =new ContactsPanel(); outer.setWidth("100%"); outer.setHeight("100%"); // Hook the window resize event, so that we can adjust the UI. Window.addWindowResizeListener(this); // Get rid of scrollbars, and clear out the window's built-in margin, // because we want to take advantage of the entire client area. Window.enableScrolling(false); Window.setMargin("10px"); outer.add(recherchePanel, DockPanel.NORTH); outer.add(contactsPanel, DockPanel.CENTER); RootPanel.get().add(outer); } public void onWindowResized(int width, int height) { // TODO Auto-generated method stub }}
IV-C-2. Les éléments graphiques
Maintenant que la structure d'accueil de notre application est prête, nous allons pouvoir mettre un peu de contenu.
En plus des composants graphiques de base, nous utiliserons aussi la classe Composite.
La classe Composite permet d'envelopper un ou plusieurs autres éléments graphiques. Ce groupe de composant agit alors comme un seul et même élément.
Nous allons commencer par créer une classe Texte que héritera de Composite. Cette classe nous permettra de gérer les zones de saisie utilisateur.
Elle gèrera le libellé et la zone de saisie. on utilisera la classe Label pour le libélé et la classe TextBox pour la zone de saisie.
Texte.java
package com.developpez.exemple.gwt.client.panel;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.TextBox;
public class Texte
extends Composite {
private Label label =
new Label();
private TextBox textBox =
new TextBox();
public Texte() { HorizontalPanel panel =
new HorizontalPanel(); panel.add(label); panel.add(textBox); initWidget(panel); }
public void setLabel(
String texte){ label.setText(texte+
" : "); }
public void setTexte(
String texte){ textBox.setText(texte); }
public String getTexte(){
return textBox.getText(); }}
Ensuite, il suffit d'ajouter ce composant à notre RecherchePanel, pour avoir nos zones de filtre.
RecherchePanel.javapackage com.developpez.exemple.gwt.client.panel;import com.google.gwt.user.client.ui.Button;import com.google.gwt.user.client.ui.HorizontalPanel;public class RecherchePanel extends HorizontalPanel { public RecherchePanel(){ this.setTitle("Recherche"); this.setWidth("100%"); Texte tb = new Texte(); tb.setLabel("Nom"); add(tb); Texte tb2 = new Texte(); tb2.setLabel("Prénom"); add(tb2); }}
Avant de compléter notre application, nous allons créer une classe utilitaire Contact.
Ce sera la représentation objet des différentes personnes de notre application.
Afin d'avoir quelques éléments graphiques pour la démonstration, on rajoute une méthode getAllContacts() sur la classe.
Cette méthode va nous renvoyer un tableau de contacts à afficher, le temps que l'on implémente le service d'accès à une base de données.
A terme cette méthode disparaîtra.
Les différents contacts connus seront affichés sous forme de tableau.
Nous utiliserons pour cela la classe FlexTable. Le contenu d'un tableau se positionne par ses coordonées.
Nous utiliserons la première ligne pour afficher les entêtes de tableau.
ContactsPanel.javapackage com.developpez.exemple.gwt.client.panel;import com.developpez.exemple.gwt.client.util.Contact;import com.google.gwt.user.client.ui.FlexTable;import com.google.gwt.user.client.ui.VerticalPanel;public class ContactsPanel extends VerticalPanel { private FlexTable t = new FlexTable(); public ContactsPanel() { t.setTitle("Contacts"); t.setText(0, 0,"Nom"); t.setText(0, 1,"Prénom"); t.setText(0, 2,"Email"); t.setText(0, 3,"Tel"); t.setWidth("100%"); t.setCellSpacing(2); for (int i = 0; i < Contact.getAllContacts().length; ++i) addContact(Contact.getAllContacts()[i],i+1); this.add(t); this.setWidth("100%"); } private void addContact(Contact contact,int i) { t.setText(i, 0,contact.getNom()); t.setText(i, 1,contact.getPrenom()); t.setText(i, 2,contact.getEmail()); t.setText(i, 3,contact.getTel()); } }
Une fois compilée, notre application donne
ça.
IV-D. Utilisation des styles css
Maintenant que l'on a tous nos éléments, on va leur donner un aspect un peu plus convivial.
Pour cela, on va utiliser un fichier css.
On ajoute donc un fichier MonApplication.css au répertoire public de notre arborescence.
Afin que ces styles soient disponibles dans les classes GWT, il faut éditer le fichier MonApplication.gwt.xml, et déclarer le fichier css dans notre module.
Pour cela il suffit d'ajouter la ligne suivante dans la déclaration du module.
MonApplication.gwt.xml <stylesheet src='MonApplication.css'/>
Les différentes classes graphiques du framework GWT héritent toutes de la classe UIObject.
Cette classe nous donne la méthode
setStyleName sur tous nos composants et nous permet donc d'assigner un style précis à chacun de nos objets.
Par exemple, pour changer le style du RecherchePanel, il nous suffit d'ajouter le code suivant à son constructeur pour en modifier l'apparence.
this.setStyleName("gwt-RecherchePanel");
Bien évidemment le style
gwt-RecherchePanel a été défini dans le fichier css.
Par convention, les noms de style sont composés du nom du module tiret l'élément grahique auquel il s'applique.
En appliquant nos styles à nos différents éléments, on obtient
ça.
Mais bien sûr, nous n'avons encore aucun composant actif dans notre page.
V. Activation du filtre
Nous allons commencer à mettre un peu d'intelligence dans notre application. Pour cela, nous allons écouter les événements sur les zones de saisie et modifier le contenu du tableau en conséquence.
On va commencer par se donner des accesseurs à nos Panels en leur appliquant le pattern Singleton. Puis on va rajouter les méthodes
getNom et
getPrenom sur le RecherchePanel et la méthode
filtre sur le contactsPanel.
Les méthodes
getNom et
getPrenom retourneront les valeurs saisies tandis que
filtre modifiera le contenu du tableau de résultats. Ensuite, on va écouter les événements clavier qui surviennent dans nos zones de saisie en ajoutant le code suivant à notre classe Texte.
Texte.javatextBox.addKeyboardListener(new KeyboardListener() { public void onKeyDown(Widget sender, char keyCode, int modifiers) {} public void onKeyPress(Widget sender, char keyCode, int modifiers) {} public void onKeyUp(Widget sender, char keyCode, int modifiers) { ContactsPanel.instance.filtre(); } });
On remplit la méthode filtre.
ContactsPanel.javapublic void filtre(){ String nom = RecherchePanel.instance.getNom(); String prenom = RecherchePanel.instance.getPrenom(); for (int i = t.getRowCount()-1; i >0 ; i--) { t.removeRow(1); } int next =1; for (int i = 0; i < Contact.getAllContacts().length; ++i){ Contact contact =Contact.getAllContacts()[i]; if (contact.getNom().toUpperCase().startsWith(nom.toU pperCase()) && contact.getPrenom().toUpperCase().startsWith(preno m.toUpperCase())) { addContact(contact,next); next++; } }}
Une fois nos classes modifiées, on obtient
ça.
VI. Dialogue avec le serveur
L'application semble fonctionnelle, mais la liste des contacts reste encore contenue dans la classe Contact, ce qui est peu satisfaisant.
Nous allons donc dans un premier temps créer un package server qui recevra les classes distantes et supprimer la méthode static getAllContact de la classe Contact.
VI-A. Mise en place d'un service
Le framework GWT dispose de sa propre solution de communication entre le client et le serveur, basée sur le principe d'appel de procédure distante (RCP).
Le serveur et le client dialogue dialoguent en Asynchrone (vous savez le premier A de AJAX ).
Il faut donc deux flux de communication, le premier direct vers le serveur pour émettre les requêtes, le second asynchrone pour recevoir les réponses du serveur.
Le flux ascendant est modélisé par une interface qui doit étendre l'interface RemoteService.
On définit dessus les différentes méthodes que l'on souhaite obtenir du serveur.
On va donc créer une interface ContactService.
Afin que le client puisse recevoir la réponse du serveur, on va créer une seconde interface ContactServiceAsync.
Cette interface doit répondre à plusieurs critères :
- Le nom de l'interface doit être le même que celui pour le flux ascendant avec à la fin "Async".
- Toutes ses méthodes sont de type void.
- Le nombre et le nom de ses méthodes sont identiques à ceux pour le flux ascendant.
- Chacune de ses méthodes doit prendre les mêmes paramètres que pour le flux montant plus un paramètre supplémentaire de la classe AsyncCallback.
C'est au travers de l'objet AsyncCallback que le serveur retournera sa réponse au client.
Maintenant que nous avons nos flux de communication avec le serveur, il nous faut ensuite créer une servlet sur le serveur capable de recevoir notre demande et d'y répondre.
Pour cela dans le package server, on va créer une classe ContactServiceImpl qui étendra RemoteServiceServlet et impémentera notre interface ContactService.
Hériter de la classe RemoteServiceServlet permet de faire abstraction de l'élaboration de l'objet AsyncCallback.
Le mécanisme RCP fonctionne comme un miroir, les méthodes appelées coté client seront exécutées coté serveur sur notre servlet.
C'est donc dans le corps des méthodes issues de l'implémentation de notre interface que l'on placera le code que l'on souhaite exécuter sur le serveur.
Notre classe servlet elle aussi doit répondre à plusieurs critères :
- Le nom de la servlet doit être le même que celui pour le flux ascendant avec à la fin "Impl".
- La servlet doit implémenter RemoteServiceServlet.
- La servlet doit implémenter l'interface du service ascendant.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 830x287 et 147Ko |

ContactServiceImpl.java
package com.developpez.exemple.gwt.server;
import com.developpez.exemple.gwt.client.ContactService;
import com.developpez.exemple.gwt.client.util.Contact;
import com.google.gwt.user.server.rpc.RemoteServiceServle t;
public class ContactServiceImpl
extends RemoteServiceServlet
implements ContactService {
public Contact[] getAllContact() {
return ContactData.getAllContacts(); }}
La classe ContactData est une classe utilitaire chargée de fournir la liste des contacts.
La méthode d'obtention des contacts importe peu, jdbc, Hibernate, fichier XML, LDAP, libre à vous d'utiliser celle qu'il vous plait.
Dernière action pour finaliser la création de notre service, la déclaration dans le fichier MonApplication.gwt.xml.
Pour cela, il suffit d'ajouter la ligne suivante à la déclaration du module.
MonApplication.gwt.xml <servlet path="/contactService" class="com.developpez.exemple.gwt.server.ContactServiceI mpl"/>
VI-B. Exploitation du service
Maintenant que le service est créé, nous allons pouvoir l'utiliser. On va l'appeler directement dans notre classe ContactsPanel.
On rajoute une méthode getAllContacts sur notre classe, qui après quelques modifications ressemble à ça :
ContactsPanel.javapackage com.developpez.exemple.gwt.client.panel;import com.developpez.exemple.gwt.client.ContactService;import com.developpez.exemple.gwt.client.ContactServiceAs ync;import com.developpez.exemple.gwt.client.util.Contact;import com.google.gwt.core.client.GWT;import com.google.gwt.user.client.rpc.AsyncCallback;import com.google.gwt.user.client.rpc.ServiceDefTarget;import com.google.gwt.user.client.ui.FlexTable;import com.google.gwt.user.client.ui.VerticalPanel;public class ContactsPanel extends VerticalPanel { private FlexTable t = new FlexTable(); private Contact[] contacts; public static ContactsPanel instance; public ContactsPanel() { getAllContacts(); t.setTitle("Contacts"); t.setText(0, 0,"Nom"); t.setText(0, 1,"Prénom"); t.setText(0, 2,"Email"); t.setText(0, 3,"Tel"); t.setWidth("100%"); t.setCellSpacing(2); for (int i = 0; i < 4; i++) { t.getCellFormatter().addStyleName(0, i, "contact-ContactPanel"); } this.add(t); this.setWidth("100%"); instance=this; } private void addContact(Contact contact,int i) { t.setText(i, 0,contact.getNom()); t.setText(i, 1,contact.getPrenom()); t.setText(i, 2,contact.getEmail()); t.setText(i, 3,contact.getTel()); for (int j = 0; j < 4; j++) { if(i%2==0) t.getCellFormatter().addStyleName(i, j, "contact-ContactPanel-line1"); else t.getCellFormatter().addStyleName(i, j, "contact-ContactPanel-line2"); } } public void filtre(){ String nom = RecherchePanel.instance.getNom(); String prenom = RecherchePanel.instance.getPrenom(); for (int i = t.getRowCount()-1; i >0 ; i--) { t.removeRow(1); } int next =1; for (int i = 0; i < contacts.length; ++i){ Contact contact =contacts[i]; if (contact.getNom().toUpperCase().startsWith(nom.toU pperCase()) && contact.getPrenom().toUpperCase().startsWith(preno m.toUpperCase())) { addContact(contact,next); next++; } } } private void getAllContacts(){ contacts = new Contact[0]; // define the service you want to call ContactServiceAsync svc = (ContactServiceAsync) GWT.create(ContactService.class); ServiceDefTarget endpoint = (ServiceDefTarget) svc; String moduleRelativeURL = GWT.getModuleBaseURL() + "contactService"; endpoint.setServiceEntryPoint(moduleRelativeURL); AsyncCallback callback = new AsyncCallback() { public void onSuccess (Object result) { contacts = (Contact[]) result; filtre(); } public void onFailure (Throwable ex) { contacts = new Contact[0]; filtre(); } }; // execute the service svc.getAllContact(callback); }}
Si on détaille un peu la méthode getAllContacts() :
Déclaration du service que l'on souhaite appeler ContactServiceAsync svc = (ContactServiceAsync) GWT.create(ContactService.class);
On définit ou trouver le service, son url ServiceDefTarget endpoint = (ServiceDefTarget) svc;
String moduleRelativeURL = GWT.getModuleBaseURL() +
"contactService"; endpoint.setServiceEntryPoint(moduleRelativeURL);
On définit le comportement de retour AsyncCallback callback =
new AsyncCallback() {
public void onSuccess (Object result) { contacts = (Contact[]) result; filtre(); }
public void onFailure (Throwable ex) { contacts =
new Contact[0]; filtre(); } };
J'ai utilisé une classe interne car le comportement voulu était très simple, mais dans un cadre plus complexe, il serait souhaitable d'utiliser une classe externe.
on appelle le service svc.getAllContact(callback);
Dans notre exemple la mise place et l'appel du service sont fait dans la même méthode car l'appel du service n'est fait qu'une seule fois au chargement de la page.
Néanmoins, on peut très bien placer notre service sur un slot et y faire ensuite appel en différente occasion.
Chaque appel à un service génère un aller retour serveur. Il faut donc prévoir les temps de latence possible.
De plus il est intéressant comme dans notre application, de stocker en cache côté client un certain nombre d'information, afin d'alléger le nombre de requète au serveur.
Typiquement dans notre exemple, il était inutile de faire un aller/retour serveur pour le filtre (à la volumétrie près).
Notre application est maintenant terminée et nous obtenons la liste des contacts d'un serveur distant.
VII. Déploiement sur Tomcat
La structure standard d'une application sous Tomcat est la suivante :
La structure de notre projet sous Eclipse est la suivante :
Pour déployer dans une arborescence tomcat il faut :
- Créer une webapp avec le nom de notre service.
- Copier le contenu du répertoire bin dans le repertoire classes de notre webapps.
- Placer le jar gwt-servlet.jar dans le répertoire lib de notre application ou dans le répertoire common/lib à la racine de tomcat.
- Copier le contenu du répertoire www à la racine de notre webapp.

Dans notre exemple le répertoire
www contient un sous répertoire
com.developpez.exemple.gwt.MonApplication.
Lors d'un déploiement, il est possible de ne pas conserver ce sous répertoire en ne copiant que son contenu à la racine de la webapps.
Cela permet de simplifier l'adresse de notre application.
Enfin, il faut déclarer notre servlet. Il faut donc placer un fichier web.xml dans le répertoire WEB-INF.
web.xml<?xml version=
"1.0" encoding=
"ISO-8859-1"?>
<!-- Copyright 2004 The Apache Software Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.--><
web-app xmlns=
"http://java.sun.com/xml/ns/j2ee" xmlns:xsi=
"http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=
"http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" version=
"2.4"> <
display-name>GWT</
display-name> <
description> GWT example </
description> <
servlet> <
servlet-name>ContactService</
servlet-name> <
servlet-class>com.developpez.exemple.gwt.server.ContactServiceI mpl</
servlet-class> </
servlet> <
servlet-mapping> <
servlet-name>ContactService</
servlet-name> <
url-pattern>/contactService</
url-pattern> </
servlet-mapping> <
welcome-file-list> <
welcome-file>MonApplication.html</
welcome-file> </
welcome-file-list> </
web-app>
VIII. Conclusion
Au cours de cet article, nous avons vu tout l'intérêt du framework GWT.
Nous avons réalisé une application web interactive, dialoguant avec un serveur, en faisant abstraction de tout autre language à part Java.
De plus le code généré est compatible avec la majorité des navigateurs.
Le framework GWT apporte au développement WEB tous les avantages du développement d'un client lourd, en permettant notamment la modélisation des classes gérant l'interface graphique.
IX. Sources
Fichiers sources de la première partie du tutoriel
ici ou
ici.
Fichiers sources du chapitre VI
ici ou
ici.
Créez des applications Delphi compatibles Windows Vista :
I. Donc, vos applications "tournent"
Aujourd'hui il faut l'espérer vous avez, en développeur responsable, téléchargé
l'une des versions publiques du prochain système d'exploitation de Microsoft,
Windows Vista, et vous êtes assuré que vos applications fonctionnent sous le nouvel OS.
Bien que 99% des applications Delphi correctement codée n'auront aucun problème sous Vista (la considération des droits d'administration, de l'élévation du rang de l'utilisateur et de l'UAC [contrôle des comptes utilisateur] sort du cadre de cet article), elles n'apporteront sûrement pas les nouvelles améliorations dans l'expérience de l'utilisateur que l'on peut trouver dans Windows Vista. En réalité plus vous approfondirez vos recherches au sujet des nouveautés concernant l'expérience utilisateur dans Vista, plus vous découvrirez que vos applications n'y répondent que très peu.
Cependant, pas de panique ! Il est complètement possible, et relativement aisé, de rectifier ces écarts et de rendre vos applications compatibles à 100% avec la nouvelle expérience fournie par Windows Vista. Examinons quelques imperfections trouvées dans les applications générées par Delphi, et voyons comment les corriger ainsi que quelques étapes supplémentaires pour intégrer encore mieux nos applications.
II. L'application exemple
L'application qui servira d'exemple tout au long de ce tutoriel est un simple programme similaire au bloc-notes, intégrant deux fiches et quelques actions. Elle permet de charger et sauver du texte, et d'afficher une boîte de dialogue "A propos". Elle offrira également la possibilité de sauvegarder vos modifications lors de l'ouverture d'un fichier ou de la sortie du programme, si modifications il y a eu.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 765x471 et 24Ko |

Application finale
Téléchargez l'application exemple - Etape 1 (
miroir) Maintenant, bien que notre application d'exemple fonctionne correctement sous Windows Vista, quelques problèmes apparaissent lorsque l'on s'intéresse à son intégration aux éléments clés l'expérience de l'utilisateur sous Windows Vista.
III. Oh non... Pas encore (ou, nouvelles polices)
Le premier problème, le plus facile à résoudre -et le plus long dans des projets de grandeur nature- est la police de caractères. Windows Vista est accompagné d'une nouvelle police par défaut pour l'interface utilisateur :
Segoe UI. Cela n'est que la moitié du problème. Le point important est que, non seulement une nouvelle police est utilisée, mais de plus sa taille par défaut a été augmentée. Ordinairement les applications Windows XP utilisent la police Tahoma, taille 8. Les applications (qui voudraient être) correctement intégrée à Windows Vista utilisent toutefois Segoe UI, taille 9. Cela signifie qu'il faut d'une part modifier nos polices (si l'application est lancée sous Windows Vista), et d'autre part vérifier chaque écran pour s'assurer que tous les textes sont toujours visibles, en ajustant çà et là le cas échéant.
Commençons par créer une nouvelle unité nommée uVistaFuncs.pas. Nous y ajouterons nos méthodes spécifiques à Vista. A cette unité, nous allons ajouter des fonctions pour vérifier que notre application est lancée ou non sous Windows Vista et pour appliquer la police et taille de caractères adéquates à une fiche donnée. Ainsi, dans le FormCreate de notre fiche principale et de la fenêtre "A propos", nous n'aurons qu'à appeler SetVistaFonts(Self), en prenant garde que uVistaFuncs est présente dans la clause uses.
Vous remarquerez que le titre de la boîte de dialogue "A propos" utilise la police par défaut, Tahoma. Pour cause, nous avons changé la police en mode design et la propriété ParentFont est maintenant à False. Vous trouverez cela dans beaucoup d'applications. Le meilleur moyen de vérifier que ce détail a été géré est d'éditer uVistaFuncs et de changer "Segoe UI" en "Segoe Script". Cela rendra plus évident, dans l'application, les endroits où des modifications doivent être apportées. Dans notre cas, il nous suffit d'assigner TitleLabel.Font.Name après avoir appelé SetVistaFonts.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 696x434 et 19Ko |

Nouvelle police
Téléchargez l'application exemple - Etape 2 (
miroir)
IV. Où est mon intuition ? (ou, Pourquoi la fenêtre secrète ?)
Windows Vista présente plusieurs éléments qui améliorent l'intuition dans l'expérience de l'utilisateur. Vous pouvez lire un article (bien qu'un peu dépassé) sur les interfaces utilisateur intuitives
ici. Basiquement, une interface intuitive assiste activement l'utilisateur dans la compréhension de ce qui est affiché à l'écran.
Quelques exemples d'une telle interface dans Windows Vista incluent les nouvelles animations de réduction et d'agrandissement d'une application. Comme le mentionne Ales Holecek dans son interview
ici, nombreux sont les utilisateurs qui ne comprennent pas le concept d'une fenêtre et de son bouton correspondant dans la barre des tâches. Ils ne saisissent pas où la fenêtre se trouve lorsqu'elle est minimisée et qu'elle est associée au bouton de la barre des tâches. Pour aider à remédier à ce problème (et pour exhiber l'interface 3D,
Aero) dans Vista, les fenêtres sont doucement animées en 3D vers, et à partir de leur bouton de la barre des tâches lorsqu'elles sont réduites ou agrandies.
Allez-y, testez vous-même... pas avec une application Delphi ! Si vous réduisez ou agrandissez une application Delphi sous Vista, les animations sont celles de la fermeture ou de l'affichage d'une fenêtre : celle-ci s'efface petit à petit en se fermant ou apparaît en fondu lorsqu'elle est affichée. La raison de ce comportement est que toutes les applications Delphi ont une "fiche d'application", cachée et secrète. Cette fiche cachée est la propriétaire du bouton de la barre des tâches que vous pouvez voir dans vos applications Delphi (et c'est aussi pourquoi les menus systèmes de vos applications Delphi sont différents des autres applications). Lorsque vous réduisez ou restaurez vos fiches, la magie interne de Delphi outrepasse les messages et, à la place, affiche ou cache simplement les fiches actives. Vos fiches ne sont jamais réduites ou agrandies au sens propre du terme.
Autorisons-nous un moment de digression sur une autre nouveauté de Windows Vista. Nous reviendrons à notre fenêtre secrète bientôt. Cette nouvelle fonctionnalité dans Vista, qui prend également part à l'interface utilisateur intuitive, nous permet de voir un aperçu de nos fenêtre en passant simplement le curseur de la souris sur le bouton de notre application dans la barre des tâches. Cela facilite la recherche du bon bouton à cliquer dans la barre des tâches et est -chose discutable- plutôt sympathique. La même technique est également utilisée pour afficher le tout nouveau
Flip 3D, une représentation en trois dimensions du classique écran Alt+Tab. Si vous essayez cela avec une application Delphi normale et non réduite, tout fonctionne correctement. Cependant si vous essayez avec une application Delphi minimisée, vous ne verrez qu'une fenêtre vide avec l'icône de votre application plutôt que l'aperçu en temps réel.

Aperçu - fenêtre restaurée

Aperçu - fenêtre minimisée
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 568x361 et 27Ko |

Flip3D avec l'application minimisée

Ces impressions d'écran ne correspondent pas tout à fait à celles de l'article original. En effet, sur la version RC1 de Windows Vista que j'ai utilisée pour mes propres tests, la fenêtre une fois réduite n'apparaît pas du tout en aperçu ou dans Flip3D.
Pourquoi nos applications Delphi se comportent-elles différemment ? Serait-ce l'oeuvre d'une force diabolique ? Non ! C'est la fenêtre secrète ! Lorsque vous survolez le bouton de la barre de tâches, et qu'une fiche est agrandie, la fiche active de l'application est affichée dans l'aperçu. Toutefois, quand toutes les fiches sont réduites, la "fiche d'application" cachée est montrée dans l'aperçu.
Voyons maintenant comment régler le problème ! Nous y arriverons en utilisant les méthodes décrites par Peter Below
ici. La première chose que nous avons besoin de faire est de nous débarasser du bouton dans la barre de tâches pour notre "fiche d'application" cachée. Dans le FormCreate de notre application, nous devons ajuster quelques indicateurs sur notre "fiche d'application" :
ShowWindow(Application.Handle, SW_HIDE);SetWindowLong(Application.Handle, GWL_EXSTYLE, GetWindowLong(Application.Handle, GWL_EXSTYLE) and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW);ShowWindow(Application.Handle, SW_SHOW);
Ensuite, pour chaque fiche pour laquelle nous voulons un bouton dans la barre de tâches (au moins la fiche principale), nous devons surcharger CreateParams :
procedure TMainForm.CreateParams(var Params: TCreateParams);begin inherited CreateParams(Params); Params.ExStyle := Params.ExStyle and not WS_EX_TOOLWINDOW or WS_EX_APPWINDOW;end;
Enfin, pour chaque forme possédant son bouton et la possibilité d'être réduite, nous devons gérer le message Windows WM_SYSCOMMAND :
interface...protectedprocedure WMSyscommand(var Message: TWmSysCommand); message WM_SYSCOMMAND;...implementation...procedure TMainForm.WMSyscommand(var Message: TWmSysCommand);begin case (Message.CmdType and $FFF0) of SC_MINIMIZE: begin ShowWindow(Handle, SW_MINIMIZE); Message.Result := 0; end; SC_RESTORE: begin ShowWindow(Handle, SW_RESTORE); Message.Result := 0; end; else inherited; end;end;
Ce code a pour effet d'empêcher aux processus internes de Delphi de surcharger la réduction et l'agrandissement de nos fiches, causant à la place un simple masquage ou affichage. Maintenant, nos fiches devraient se comporter correctement. Elles s'animent comme il se doit lorsqu'elles sont réduites et restaurées, et elles affichent correctement leur aperçu quand elles sont réduites dans la barre de tâches.
Cependant, il nous reste un problème majeur. Si nous lançions notre application exemple et sélectionnions "Aide" puis "A propos", et que finalement nous cliquions sur le bouton de la barre des tâches de la fenêtre, la fiche serait mise en avant-plan, par-dessus notre dialogue "A propos" ! Cela est dû au fait que, par défaut, Delphi définit la "fiche d'application" cachée comme parente des fiches modales. Pour rectifier cela, vous devez changer Form.PopupParent avant chaque appel à Form.ShowModal :
procedure TMainForm.AboutActionExecute(Sender: TObject);begin AboutForm := TAboutForm.Create(Self); try AboutForm.PopupParent := Self; AboutForm.ShowModal; finally FreeAndNil(AboutForm); end;end;
Téléchargez l'application exemple - Etape 3 (
miroir)
V. Faites briller vos applications
A moins que vous n'ayez vécu dans une grotte, vous savez que les applications tournant sous Windows Vista (sur une machine avec une carte accélératrice 3D) sont dessinées avec une zone non-cliente et une bordure translucides, à la manière de verre. Cet effet est appelé, en toute logique,
Glass (1). Le Gestionnaire de Fenêtres (
DWM - Desktop Windows Manager) fournit des API qui vous permettent d'étendre cette surface
Glass sur la zone cliente de vos applications. Bien que ce ne soit pas nécessaire, cela peut aider votre application à s'intégrer en tant "qu'application Vista".
Nous allons commencer par ajouter quelques nouvelles fonctions à notre fichier
uVistaFuncs.pas : CompositingEnabled et ExtendGlass. La source pour ces méthodes est basée sur le code trouvé
ici. CompositingEnabled indique si les fonctionnalités 3D sont disponibles ou non, et ExtendGlass permet d'étendre la surface
Glass sur la zone cliente de nos fiches. Ajoutons maintenant un Panel au bas de notre fiche. La raison de sa présence est que les API du Gestionnaire de Fenêtres pour étendre la zone
Glass requiert une surface noire sur laquelle effectuer son rendu. Enfin, nous ajoutons une nouvelle méthode à notre fiche principale qui vérifie si les fonctionnalités d'Aero sont activées et, si c'est le cas, qui initialise le Panel noir et appelle ExtendGlass pour dessiner l'effet
Glass sur notre fiche principale :
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 690x429 et 19Ko |
Téléchargez l'application exemple - Etape 4 (
miroir)
VI. Quand une invite n'en est pas une (ou, Dialogues de Tâche)
Windows Vista introduit un nouvel élément dans l'expérience de l'utilisateur, appelé le
Dialogue de Tâche. Il apporte de nombreuses invites sous l'égide d'une unique API afin de renforcer la consistence du système. Bien qu'il est possible de créer des invites extrêmement complexes avec l'API des Dialogues de Tâche, nous commencerons par un simple remplacement de MessageDlg. Notre méthode utilisera les Dialogues de Tâche de Vista s'ils sont disponibes, et retournera à des appels de MessageDlg si nécessaire. Il existe un parfait exemple de cette méthode
ici.
Une fois que nous avons ajouté la nouvelle méthode pour utiliser les Dialogues de Tâche à notre unité uVistaFuncs.pas, une simple modification de notre méthode SaveHandled permet à notre message à la fois de mieux s'intégrer dans l'expérience utilisateur de Vista, tout en fournissant un meilleur retour d'information à l'utilisateur final.

Dialogue classique

Nouveau Dialogue de Tâche de Windows Vista
Téléchargez l'application exemple - Etape 5 (
miroir)

Le code fourni dans l'article original ne fonctionnait pas sur mon système, aucun dialogue ne s'affichait. Afin de le faire fonctionner correctement, j'ai du modifier certaines constantes dans
uVistaFuncs.pas J'ai laissé en commentaire les valeurs initiales.
Les valeurs que j'utilise ont été trouvées
ici.
VI. Tout est dans l'image
Nous approchons de la fin. Ce changement aurait vraisemblablement dû être fait en portant notre application de Windows 2000 à Windows XP mais, dans cet exemple, notre application est bien loin derrière tout cela. Ce que nous devons faire maintenant, c'est remplacer ces images plates et pixellisées par de belles images, lisses, si possible ombrées, et transparentes. J'ai trouvé que la meilleur manière de faire cela était d'utiliser PngComponents et le composant TPngImageList, que l'on peut trouver
ici. Cette simple alternative aux TImageList nous permet de charger des images PNG transparentes dans une liste d'images et de les utiliser au sein de notre application.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 692x454 et 20Ko |
Téléchargez l'application exemple - Etape 5 (
miroir)
VIII. Nouvelles boîtes de dialogues
Windows Vista introduit de nouvelles boîtes de dialogue, dont des fenêtres d'ouverture et d'enregistrement d'un tout nouveau design. Ces nouvelles boîtes de dialogue incluent plusieurs fonctionnalités nouvelles à Windows Vista, comme la zone des Liens Favoris (ndt: cette traduction ne correspond pas nécessairement à la version française de Windows Vista, ne l'ayant pas à disposition pour vérifier)(personnalisable via le menu contextuel), de nouvelles vues et une fenêtre de sauvegarde repliable.
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 610x454 et 20Ko |

Dialogue de sauvegarde classique
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 645x222 et 13Ko |

Nouveau style de dialogue - replié
 |
cette photo est redimensionée, clique ici pour l'afficher à sa taille d'origine. la taille d'origine est 644x518 et 23Ko |

Nouveau style de dialogue - Déplié
Malheureusement, les applications écrites en Delphi n'utiliseront pas automatiquement ces nouveaux styles pour les dialogues d'ouverture et d'enregistrement de fichier. Cela est dû à des
flags utilisés dans le code de l'unité Dialogs.pas, OGN_ENABLEHOOK et OFN_ENABLETEMPLATE. Pour plus d'information, lisez le document du Quality Central ici.
Pour régler ce problème :
- Premièrement, faites une copie de Dialogs.pas et placez cette copie soit dans le répertoire des sources de votre applications, ou dans un répertoire inclus dans vos chemins de librairies.
- Deuxièmement, dans cette copie de Dialogs.pas, recherchez "OFN_ENABLEHOOK". Vous devriez voir le code "Flags := OFN_ENABLEHOOK;". Commentez cette ligne.
- Ajoutez la ligne "Flags := 0" après la ligne que vous venez de commenter
- Recherchez "OFN_ENABLETEMPLATE". Commentez le code en commençant deux lignes au dessus de la première occurrence de OFN_ENABLETEMPLATE, "if Template <> nil then", et en terminant à "hWndOwner := Application.Handle;". Cela doit représenter une vingtaine de lignes de code.
- Ajoutez la ligne "hWndOwner := ParentWnd;" après le code que vous venez de commenter
- Rendez-vous à la méthode "TCommonDialog.Execute". Commentez le "if", "begin", "else" et "ParentWnd := Application.Handle". Cela devrait vous laisser un code qui essaie toujours d'affecter à ParentWnd le Handle de la fenêtre active, retombant sur Application.Handle.
- Sauvegardez vos changements sur votre copie de Dialogs.pas
Notre application d'exemple devrait maintenant utiliser les nouveaux dialogues d'ouverture et de sauvegarde. La première étape ci-dessus permet d'utiliser une copie modifiée de la VCL sans affecter le fichier original. La seconde étape retire le premier
flag incriminé, mais signifie aussi que nos dialogues ne seront pas centré. La troisième étape est juste dans le prolongement de la seconde étape, initialisant le
flag à zéro. La quatrième étape retire le second
flag en cause de notre problème, ainsi que du code qui ne fonctionne plus pour centrer les dialogues. Les cinquième et sixième étapes corrigent le problème du centrage des dialogues, donnant en parent à nos dialogues la fiche active.
Etant donné que cette partie implique de modifier les sources de la VCL, je ne peux pas poster la source. Cependant, en suivant les instructions ci-dessus vous devriez pouvoir donner à toutes vos applications la possibilité d'utiliser le nouveau style de dialogues sans avoir à modifier vos sources. Si vous préférez ne pas modifier Dialogs.pas, lisez
cette page pour des instructions utilisant les nouveaux dialogues d'ouverture et de sauvegarde avec des appels à l'API.

Pour ma part, en plus des étapes ci-dessus, j'ai du renommer ma copie de Dialogs.pas en VistaDialogs.pas (ou tout autre nom différent de l'original) et ajouter
VistaDialogs dans ma clause uses,
après Dialogs.
Téléchargez l'EXE final (
miroir)
IX. Conclusion
Les changements de l'expérience de l'utilisateur dans Windows Vista, bien qu'importantes, n'apporte pas une rupture aussi capitale que celles apportées par Windows XP. Avec ce dernier, pour intégrer nos applications, il nous fallait soit utiliser un
gestionnaire de thème tiers, soit attendre Delphi 7. Avec Windows Vista, tant que nos applications étaient développées pour fonctionner avec Windows XP, elles cadreront automatiquement avec le nouveau style visuel de Windows Vista.
Cependant, depuis Windows Vista, Microsoft se concentre beaucoup plus sur tous les petits détails qui englobent l'expérience utilisateur fournie par un ordinateur personnel. Cela signifie que si nous, développeurs, voulons que nos applications s'intègrent dans cette expérience utilisateur consistante, nous devons également nous concentrer sur les détails de l'expérience de nos clients.
Bien que la prochaine version de Delphi abordera sans aucun doute plusieurs des sujets discutés ici, personnellement, je ne souhaite pas être obligé de mettre à jour mon environnement de développement uniquement pour que mes applications s'intègrent à un nouveau système d'exploitation. Les codes ci-dessus nous permettent d'offir à nos utilisateurs une expérience riche, consistante, fonctionnant sous Windows Vista, et ce dès aujourd'hui.
Ci-dessous vous trouverez quelques articles et tutoriels au sujet de Windows Vista et des interfaces utilisateur :