Ce support de cours s'intéresse à présenter les composants additionnels JFace suivants : les boîtes de dialogue, les Wizards, les Preferences et la gestion des ressources.
3 À propos de l’auteur … Mickaël BARON Ingénieur de Recherche au LIAS https://www.lias-lab.fr Equipe : Ingénierie des Données et des Modèles Responsable des plateformes logicielles, « coach » technique Ancien responsable Java de Developpez.com (2011-2021) Communauté Francophone dédiée au développement informatique https://java.developpez.com 4 millions de visiteurs uniques et 12 millions de pages vues par mois 750 00 membres, 2000 forums et jusqu'à 5000 messages par jour mickael-baron.fr mickaelbaron
Organisation du cours sur JFace : composants additionnels Dialog : gérer les messages utilisateur Wizard : création d’assistants Preference : création de pages de préférences Resource : gestion des ressources Nous décrirons lors de la partie « plugin » qu’un Wizard ou une boîte de préférence peut être conçu par extension Tous les exemples du cours sont disponibles directement à l’adresse mickael-baron.fr/eclipse/intro-jface2
JFace / Boîtes de dialogue Dans le cours SWT, nous avons présenté les boîtes de dialo- gue qui couvrent la majorité des besoins des IHM Toutefois, JFace fournit une abstraction permettant Afficher des messages de validation Valider des entrées Faciliter la spécialisation selon les besoins de la plateforme Dans cette partie nous étudions les points suivants Présentation des principales boîtes de dialogue et différences avec celles de SWT Personnalisation de boîtes de dialogue Nous verrons dans les prochaines parties du cours que l’API boîtes de dialogue de JFace est chapeau aux Wizards et aux boîtes de préférences
JFace / Boîtes de dialogue Les API de boîtes de dialogue sont fournies dans le package org.eclipse.jface.dialogs Toutes les boîtes de dialogue dérivent directement ou indirectement de la classe Dialog Nous présentons principalement trois types de dialogue IconAndMessageDialog : boîtes de dialogues qui affichent une icône et un texte comme principaux composants accompagnés de boutons de contrôle InputDialog : boîtes de dialogues pour la saisie d’une chaîne de caractères accompagnées de boutons de contrôle TitleAreaDialog : boîtes de dialogues décomposées en zones. Une zone de contenus (un titre), une zone de dialogue, une zone de progression et une zone de contrôle
JFace / Boîtes de dialogue Hiérarchie des principales classes de dialogue Dialog InputDialog IconAndMessageDialog TrayDialog PreferenceDialog TitleAreaDialog WizardDialog ErrorDialog MessageDialog ProgressMonitorDialog Classes étudiées Classes génériques Etudiées dans les parties Preference et Wizard
JFace / Boîtes de dialogue Boîtes de dialogue de type IconAndMessageDialog ErrorDialog MessageDialog ProgressMonitorDialog Une IconAndMessageDialog se caractérise par un message d’information … et une icône
JFace / Boîtes de dialogue : MessageDialog Les boîtes de dialogue de type MessageDialog sont utilisées pour afficher un message suivant une sévérité donnée MessageDialog hérite de IconAndMessageDialog L’icône affiché est fonction de la sévérité (MessageDialog) ERROR INFORMATION NONE QUESTION WARNING La classe MessageDialog fournit une méthode statique pour chaque sévérité (factory de MessageDialog) boolean openConfirm(Shell parent, String title, String message) : ouvre une boîte de confirmation (OK/Cancel) autant qu’il y a de sévérité (sauf NONE)
JFace / Boîtes de dialogue : ErrorDialog Les boîtes de dialogue de type ErrorDialog sont utilisées pour gérer une ou plusieurs erreur(s) ErrorDialog hérite de IconAndMessageDialog A la différence de la boîte MessageDialog (sévérité ERROR), une ErrorDialog contient un objet IStatus qui décrit le status des erreurs La classe ErrorDialog fournit une méthode statique pour construire une boîte de dialogue erreur static int openError(Shell parent, String dialogTitle, String message, IStatus status)
JFace / Boîtes de dialogue : ErrorDialog Un objet IStatus est défini par les attributs suivants id du plugin : id du plugin associé au status (voir chapitre suivant) une sévérité (CANCEL, ERROR, INFO, OK, MESSAGE) code : code donné par le plugin un message : un message qui accompagne le status une exception : une exception liée au problème (peut être null) La classe Status est une implémentation par défaut Status(int severity, String pluginId, int code, String message, Throwable exception) La classe MultiStatus est une sous classe de Status permet- tant de gérer plusieurs status MultiStatus(String plugId, int code, IStatus[] status, String message, Throwable exception)
JFace / Boîtes de dialogue : ErrorDialog Exemple : afficher des boîtes ErrorDialog public class ErrorDialogExample { public static void errorDialog(Shell pShell, int severity) { String statusMessage = "[Placer ici le status de l'erreur]\n\n"; switch (severity) { case IStatus.ERROR: statusMessage += "Status Error Message"; break; case IStatus.INFO: statusMessage += "Status Info Message"; break; case IStatus.WARNING: statusMessage += "Status Warning Message"; break; default: statusMessage += "Status Error Message"; break; } IStatus errorStatus = new Status(severity, "My Plug-In ID", 0, statusMessage, null); ErrorDialog.openError(pShell, "JFace Error Dialog", "[Placer ici le message d'erreur] \n\n Un problème est survenu au niveau ...", errorStatus); } } ErrorDialogExample.java du projet DialogExamples
JFace / Boîtes de dialogue : ProgressMonitorDialog Les boîtes de dialogue de type ProgressMonitorDialog permettent d’afficher l’état progressif d’une opération de longue durée ProgressMonitorDialog(Shell parent) : construction d’une dialogue ProgressMonitorDialog hérite de IconAndMessageDialog L’usage typique est de passer par la méthode run run(boolean fork, boolean canceable, IRunnableWithProgress runnable) throws InvocationTargetException, InterruptedException boolean fork : l’opération dans un thread séparé ou pas boolean canceable : pour activer l’annulation IRunnableWithProgress runnable : l’opération à démarrer InvocationTargetException : si problème lors de l’opération InterruptedException : si action annulation
JFace / Boîtes de dialogue : ProgressMonitorDialog Un objet de type IRunnableWithProgress permet de décrire une opération de longue durée Une classe de type IRunnableWithProgress doit implémenter run(IProgressMonitor monitor) : traitement de l’opération dont le rapport en cours de traitement est envoyé à monitor Un IProgressMonitor renseigne l’état d’avancement d’une opération (concepts de tâches et sous-tâches) beginTask(String name, int tw) : notifie que l’opération a débuté name message à afficher, tw nombre d’unités à réaliser (UNKNOWN si pas déterminable) worked(int work) : nombre d’unités déjà réalisée subTask(String name) : notifie qu’une sous tâche a débuté done() : notifie que l’opération est terminée (tâche complétée ou l’utilisateur a affectué une annulation) boolean isCanceled() : savoir si l’utilisateur a annulé l’opération
JFace / Boîtes de dialogue : ProgressMonitorDialog Exemple (suite) : afficher des boîtes ProgressMonitorDialog public class WorkLessOperation implements IRunnableWithProgress { private static final int TOTAL_TIME = 10000; private static final int INCREMENT = 500; private boolean indeterminate; public WorkLessOperation(boolean indeterminate) { this.indeterminate = indeterminate; } public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask("Operation \"WorkLess\" en cours", indeterminate ? IProgressMonitor.UNKNOWN : TOTAL_TIME); for (int total = 0; total < TOTAL_TIME && !monitor.isCanceled(); total += INCREMENT){ Thread.sleep(INCREMENT); monitor.worked(INCREMENT); if (total == TOTAL_TIME / 2) monitor.subTask("La moitiée de l'opération \"WorkLess\" est réelisée"); } monitor.done(); if (monitor.isCanceled()) throw new InterruptedException("Opération \"WorkLess\" annulée"); } } WorkLessOperation.java du projet DialogExamples Démarrage de l’opération Evolution de l’opération Opération terminée
JFace / Boîtes de dialogue Boîtes de dialogue de type InputDialog Une InputDialog se caractérise par un message d’information … une zone de saisie de texte Et une zone de messages pour la validation des données Une zone de boutons de contrôle généralement cancel et ok pour valider ou pas la saisie
JFace / Boîtes de dialogue : InputDialog Les boîtes InputDialog permettent la saisie d’un texte InputDialog hérite directement de la classe Dialog Possibilité d’effectuer une validation sur la saisie effectuée au travers d’un objet IInputValidator InputDialog(Shell pshell, String dialogTitle, String dialogMessage, String initialValue, IInputValidator validator) : constructeur String getValue() : accesseur sur la valeur saisie dans le dialogue L’interface IInputValidator fournit une méthode de validation String isValid(String newText) : doit retourner un message d’erreur si la validation échoue, sinon retourne null
JFace / Boîtes de dialogue : InputDialog Exemple : afficher des boîtes InputDialog public class InputDialogExample { public static void validationIntegerInputDialog(Shell pShell) { InputDialog myInputDialog = new InputDialog(pShell, "JFace Input Dialog", "[Placer Message pour la saisie] Saisir votre texte", "Ma Valeur", new IntegerInputValidator()); if (myInputDialog.open() == Window.OK) { System.out.println("Vous avez sélectionné la validation en saisissant : " + myInputDialog.getValue()); } else { System.out.println("Vous avez sélectionné l'annulation."); } } ... } InputDialogExample.java du projet DialogExamples
JFace / Boîtes de dialogue : InputDialog Exemple (suite) : afficher des boîtes InputDialog public class IntegerInputValidator implements IInputValidator { public String isValid(String newText) { try { int value = new Integer(newText).intValue(); if (value < 0) return "Doit être positif"; if (value > 1000) return "Ne doit pas être supérieur à 1000"; } catch (NumberFormatException e) { return "Ne correspond pas à un entier"; } return null; } } public class StringInputValidator implements IInputValidator { public String isValid(String newText) { int length = newText.length(); if (length < 4) return "Chaîne trop courte (doit être supérieure à 4)"; if (length > 10) return "Chaîne trop longue (doit être inférieure à 10)"; return null; } } StringInputValidator.java du projet DialogExamples IntegerInputValidator.java du projet DialogExamples
JFace / Boîtes de dialogue Zone de contenu Zone de dialogue Zone de progression (optionnelle) Zone de contrôle Boîtes de dialogue de type TitleAreaDialog SaveAsDialog WizarDialog …
JFace / Boîtes de dialogue : custom TitleAreaDialog Nous avons étudié pour l’instant les dialogues prédéfinis par la bibliothèque JFace Intéressons-nous à montrer comment fournir une boîte de dialogue personnalisée basée sur une TitleAreaDialog Quelques méthodes à redéfinir … Control createContents(Composite parent) : construit la partie contenu (partie haute) Control createDialogArea(Composite parent) : construit la partie dialogue (partie médiane) void createButtonsForButtonBar(Composite parent) : ajoute des boutons à la zone de boutons (partie basse) Nous reviendrons sur les aspects liés à TitleAreaDialog au moment des Wizards
JFace / Boîtes de dialogue : custom TitleAreaDialog Exemple (suite) : afficher une TitleAreaDialog personnalisée public class CustomTitleAreaDialog extends TitleAreaDialog { ... // Suite protected Control createContents(Composite parent) { Control contents = super.createContents(parent); this.setTitle(title); this.setMessage(message); this.getShell().setText(title); return contents; } protected Control createDialogArea(Composite parent) { Composite composite = (Composite)super.createDialogArea(parent); Composite inComposite = new Composite(composite, SWT.NONE); inComposite.setLayout(new GridLayout(1, false)); GridData gd = new GridData(); gd.horizontalIndent = 5; gd.verticalIndent = 5; inComposite.setLayoutData(gd); Label label = new Label(inComposite, SWT.NONE); label.setText(dialogMessage); Button button = new Button(inComposite, SWT.FLAT); button.setText("Ceci est un Bouton"); return composite; } } CustomTitleAreaDialog.java du projet DialogExamples Création de la partie dialogue
JFace/Wizard Un Wizard est un assistant qui permet de guider l’utilisateur à réaliser une tâche structurée Un Wizard se présente sous la forme d’une IHM avec plusieurs traces opérationnelles possibles En sortie de l’assistant un post-traitement est réalisé Génération de code à partir d’un squelette prédéfini Configuration d’une installation Cette partie est guidée par un exemple file rouge « Assistant de réservation d’un transport pour les vacances » L’exemple est inspiré de celui proposé par l’article suivant Tutorial Wizard : www.eclipse.org/articles/Article-JFace Wizards/wizardArticle.html Le projet Akrogen permet de décrire les Wizards en XML/XUL et génère automatiquement le Wizard Eclipse akrogen.sourceforge.net
JFace/Wizard : exemple « fil rouge » Le bouton « Next » est disponible à la condition que certaines informations soient saisies et valides Le bouton « Cancel » permet de sortir à tout moment de l’assistant Le bouton « Finish » permet de réaliser le post traitement selon les informations saisies Contrainte 1 : la date de départ doit être inférieure à la date de retour Contrainte 2 : le lieu de départ doit être différent du lieu d’arrivée Une page définie un squelette d’IHM (une zone de contenu, une zone de dialogue et des boutons de contrôle Selon le choix du transport la page suivante est différente
JFace/Wizard : exemple « fil rouge » Une page qui gère un transport par voiture Une page qui gère un transport par avion Le bouton « Finish » est maintenant disponible. Le traitement relatif à la page sera alors réalisé
JFace/Wizard : caractéristiques Page Squelette d’IHM : boutons prédéfinis, conteneur disponible pour personnaliser l’interface et champs paramétrables (description et titre) Contraintes de validation : indiquent si certains boutons de contrôles doivent être activés ou pas (finish, next, …) Règles de navigation : condition qui permet de savoir quelle page sera affichée lors de l’action « next » ou « back » Wizard Conteneur des pages : toutes les pages sont stockées dans le Wizard Gestion du modèle : création de l’instance du modèle Traitement terminal : retourne l’état du bouton « finish » (selon l’état du modèle) et effectue le traitement associé
JFace/Wizard : schéma de fonctionnement Wizard IWizard Page 1 IWizardPage Page 2 IWizardPage Page n IWizardPage WizardModel - propriété 1 - propriété 2 - … + getPropriété1() + setPropriété1() + getPropriété2() + setPropriété2() + ... WizardMain WizardDialog 1..n 1 1 2 4 1 1 1 3 2 3 4 1 2 3 4 Cancel Next Previous Finish Le modèle du Wizard, instancié par Wizard Le Wizard qui gère les pages et les traitements terminaux Les pages qui gèrent les différentes IHMs du Wizard Les différents états de l’automate Une classe conteneur qui permet de manipuler un Wizard dans une boite de dialogue
JFace/Wizard : IWizard/Wizard L’interface IWizard définit le contrôleur de l’assistant en gérant les différentes pages Affiche en premier plan, selon le contexte, la page via l’utilisation d’un CardLayout Décide quand l’assistant peut être terminé et comment il doit le faire La classe Wizard fournit une implémentation par défaut en ajoutant des méthodes spécifiques De nombreuses méthodes … void addPage(IWizardPage) : ajoute une page à l’assistant void addPages() : endroit où sont ajoutées toutes les pages boolean canFinish() : utilisée pour indiquer l’état du bouton « finish » abstract boolean performFinish() : implémentation du traitement final Et pleins d’autres méthodes … Utilisez de préférence la classe Wizard
JFace/Wizard : IWizard/Wizard Exemple : contrôleur du Wizard public class HolidayWizard extends Wizard { ... public void addPages() { holidayPage = new HolidayMainPage(); addPage(holidayPage); planePage = new PlanePage(""); addPage(planePage); carPage = new CarPage(""); addPage(carPage); } public boolean canFinish() { if (this.getContainer().getCurrentPage() == holidayPage) return false; if (model.usePlane) return planeCompleted; return carCompleted; } public boolean performFinish() { String summary = model.toString(); System.out.println(summary); return true; } ... } Ne peut terminer l’assistant sur la première page Selon le type de transport, retourne l’état de terminaison Le traitement terminal est simpliste, il consiste à afficher un résumé des données du modèle sur la console HolidayWizard.java du projet WizardExamples
JFace/Wizard : modèle du Wizard L’objectif du modèle est de persister les différentes données saisies dans les pages de l’assistant Il n’existe pas d’API pour le modèle puisque les données de l’assistant sont fonctionnelles Les données sont utilisées lors du traitement final et doivent donc être accessibles Plusieurs approches sont à envisager pour la mise en place d’un modèle dans un Wizard Chaque page abrite les données qui lui sont propres Un modèle transverse contenant les données utiles pour le traitement
JFace/Wizard : modèle du Wizard Exemple : modèle du Wizard public class HolidayModel { protected String departureDate; protected String returnDate; protected String destination; protected String departure; protected boolean usePlane; protected boolean resetFlights; protected String selectedFlights; float price; protected String seatChoice; protected String rentalCompany; protected String carPrice; protected boolean buyInsurance; boolean discounted = false; public String toString() { // Construction de la chaîne suivant les données saisies ... } } public class HolidayWizard extends Wizard { ... HolidayModel model; public HolidayWizard() { model = new HolidayModel(); } public boolean performFinish() { String summary = model.toString(); System.out.println(summary); return true; } ... } HolidayModel.java du projet WizardExamples HolidayWizard.java du projet WizardExamples Liste des attributs utilisés par l’assistant
JFace/Wizard : IWizardPage Un objet IWizardPage fournit une description concernant une page d’un assistant La classe WizardPage fournit une implémentation par défaut et des méthodes spécifiques De nombreuses méthodes … boolean canFlipToNextPage() : page suivante est-elle affichable ? IWizardPage getNextPage() : référence sur la page suivante IWizardPage getPreviousPage() : référence sur la page précédente boolean isPageComplete() : page finalisée ou pas ? void setPageComplete(boolean v) : modifie l’état de finalisation IWizard getWizard() : référence de l’assistant (important pour accéder à la référence du modèle)
JFace/Wizard : IWizardPage Exemple : interface utilisateur d’une page public class CarPage extends WizardPage implements Listener { ... public CarPage(String pTitle) { super(pTitle); setTitle("Travel by car"); setDescription("Specify car choices"); ... } public void createControl(Composite parent) { // Construction de l’IHM } public void handleEvent(Event e) { if (e.widget == companyCombo) { // Traitement relatif à la sélection sur le Combo } setPageComplete(isPageComplete()); getWizard().getContainer().updateButtons(); } public boolean canFlipToNextPage() { return false; } ... // Suite dans le prochain transparent } CarPage.java du projet WizardExamples Lors d’une modification sur l’IHM, une demande de validation est réalisée Construction classique d’une IHM via SWT/JFace
JFace/Wizard : IWizardPage Exemple (suite) : valider les données d’une page public class CarPage extends WizardPage implements Listener { ... public boolean isPageComplete() { HolidayWizard wizard = (HolidayWizard)getWizard(); if (companyCombo.getText().length() == 0) { wizard.carCompleted = false; return false; } saveDataToModel(); return true; } private void saveDataToModel() { HolidayWizard wizard = (HolidayWizard) getWizard(); wizard.model.rentalCompany = companyCombo.getText(); wizard.model.carPrice = priceText.getText(); wizard.model.buyInsurance = insuranceButton.getSelection(); wizard.carCompleted = true; } } CarPage.java du projet WizardExamples Extraction de la référence du Wizard Sauvegarde des données dans le modèle quand la page peut être complétée
JFace/Wizard Pour afficher le Wizard deux solutions sont disponibles Utiliser un WizardDialog qui fournit directement une boîte de dialogue Utiliser la plateforme UI d’Eclipse (voir chapitre plugin) en se connectant au point d’extension fourni par org.eclipse.ui.newWizards La classe WizardDialog fournit une encapsulation d’une boîte de dialogue (TitleAreaDialog de JFace) WizardDialog permet de modifier et d’accéder à certaines propriétés graphiques de l’assistant (taille d’une page, page en cours, …) De nombreuses méthodes … WizardDialog(Shell parentShell, IWizard newWizard) : constructeur open() : ouverture de la boîte de dialogue TitleAreaDialog : voir partie précédente
JFace/Wizard Exemple : afficher l’assistant public class HolidayWizard extends Wizard { ... public static void main(String[] args) { Display display = new Display(); final Shell shell = new Shell(display); shell.setText("Wizard Example"); shell.setLayout(new FillLayout()); Button myButton = new Button(shell, SWT.FLAT); myButton.setText("Ouvrir Holiday Wizard"); myButton.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent e) { HolidayWizard wizard = new HolidayWizard(); wizard.setWindowTitle("Holiday Wizard"); WizardDialog refWizardDialog = new WizardDialog(shell, wizard); refWizardDialog.open(); } }); shell.pack(); shell.open(); while (!shell.isDisposed()) { if (!display.readAndDispatch()) display.sleep(); } display.dispose(); } } HolidayWizard.java du projet WizardExamples Ouverture de l’assistant encapsulé dans une boîte de dialogue
JFace/Preference Les préférences permettent de gérer le paramétrage d’une application La plateforme Eclipse fournit une gestion homogène et extensible des préférences Caractéristiques principales de l’API Preference fournies par la plateforme Eclipse Gestion du chargement et de la sauvegarde des paramètres Squelette d’IHM : boutons prédéfinis (apply, default, cancel et ok) conteneur disponible pour personnaliser l’interface et champs paramétrables (description et titre) Conteneur de pages de préférences
JFace/Preference : caractéristiques Pour un nœud est associée, une page de préférence Des boutons actions pour la boîte de dialogue de préférences Des boutons actions pour la page de préférences Des interfaces personnalisées par page Pour un autre nœud est associée une autre page de préférences
JFace/Preference : schéma de fonctionnement PreferenceMain PreferenceDialog PreferenceStore IPreferenceStore PreferenceManager PreferenceManager PreferenceNode IPreferenceNode PreferencePage IPreferencePage FieldEditorPreferencePage FieldEditorPreferencePage FieldEditor FieldEditor Relation hiérarchique entre les nœuds et les pages 1 Définition d’une page (associée à un nœud) Gestion implicite du chargement/sauvegarde des préférences Fichier de properties 1..n 1 1 Contient un ensemble de Composite pour l’IHM 1 1..n Le gestionnaire de persistance des préférences Boîte de dialogue des préférences Définition d’un nœud Définition d’une page de préférences qui gère exclusivement les FieldEditors Composants prédéfinis qui gère implicitement la persistance
JFace/Preference : IPreferencePage/PreferencePage Une page fournit un regroupement de composants et de boutons de contrôle (defaults, apply, cancel et ok) Une page est définie par l’interface IPreferencePage et son implémentation par défaut PreferencePage La classe PreferencePage demande l’implémentation de Control createContents(Composite parent) : retourne le contrôle en charge de définir l’IHM Différentes méthodes sont également utilisables performApply(), performOk, … : réalise le traitement associé à l’action sur un bouton de contrôle setValid(boolean v) : si false, les boutons ok et valid sont désactivés setMessage(String message, int newType) : affiche un message de sévérité donné par newType (ERROR, INFORMATION, NONE, WARNING)
JFace/Preference : IPreferencePage/PreferencePage Exemple : page de préférences public class PreferencePageOne extends PreferencePage { private Text textFieldOne; private Text textFieldTwo; public PreferencePageOne() { super("Page Une"); setDescription("Préféence des options 'Page Une'"); } protected Control createContents(Composite comp) { Composite myComposite = new Composite(comp, SWT.NONE); myComposite.setLayout(new GridLayout(2, false)); Label label = new Label(myComposite,SWT.LEFT); label.setText("Champs 1:"); textFieldOne = new Text(myComposite, SWT.BORDER); textFieldOne.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); label = new Label(myComposite,SWT.LEFT); label.setText("Champs 2:"); textFieldTwo = new Text(myComposite, SWT.BORDER); textFieldTwo.setLayoutData(new GridData(GridData.FILL_HORIZONTAL)); return myComposite; } } Construction de l’IHM de cette page Définition du titre et de la description qui seront affichés en position haute de la page PreferencePageOne.java du projet PreferenceExamples
JFace/Preference : IPreferencePage/PreferencePage Exemple (suite) : page de préférences public class PreferencePageOne extends PreferencePage { ... protected Control createContents(Composite comp) { ... textFieldOne = new Text(myComposite, SWT.BORDER); textFieldOne.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { validFieldOneValue(); } } return myComposite; } private void validFieldOneValue() { String value = textFieldOne.getText(); if (value == null || value.equals("")) { PreferencePageOne.this.setValid(false); } if (this.isValid()) { this.setMessage(null); } else { this.setMessage("Champs 1 : ne peut être vide", ERROR); } } } Vérifie à chaque saisie que le champ n’est pas vide PreferencePageOne.java du projet PreferenceExamples
JFace/Preference : IPreferenceNode/PreferenceNode Le gestionnaire de préférences s’occupe de gérer les pages par l’intermédiaire de nœuds Un nœud est associé à une seule page (IPreferencePage) Un nœud est décrit par l’interface IPreferenceNode et son implémentation par défaut PreferenceNode Plusieurs constructeurs définis PreferenceNode(String id, IPreferencePage page) : construit un nœud id associé un objet PreferencePage page PreferenceNode(String id, String label, ImageDescriptor image, String className) : un nœud id avec un nom, une icône et la page Le nom et l’image donnent une représentation au nœud Plusieurs méthodes de gestion de nœuds add(IPreferenceNode node) : ajouter un sous nœud IPreferenceNode[] getSubNodes() : retourne les sous noeuds
JFace/Preference : IPreferenceNode/PreferenceNode Exemple : associer un nœud avec une page public class PreferenceExample { public PreferenceExample() { Display myDisplay = new Display(); ... PreferenceNode one = new PreferenceNode("one", new PreferencePageOne()); PreferenceNode two = new PreferenceNode("two", new PreferencePageTwo()); PreferenceNode three = new PreferenceNode("three", "three", null, PreferencePageTwo.class.getName()); one.add(two); one.add(three); System.out.println(one.getSubNodes().length); // Doit retourner 2 ... ... } public static void main(String[] args) { new PreferenceExample(); } } PreferenceExample.java du projet PreferenceExamples Construit un nœud trois en se basant sur la définition du nœud 2 Ajout du nœud two et three à la racine de one
JFace/Preference : PreferenceDialog La gestion de la hiérarchie entre les nœuds et les pages est obtenue par un objet de type PreferenceManager Différentes méthodes à utiliser boolean addTo(String path, IPreferenceNode node) : ajoute un sous nœud au nœud identifié par path void addToRoot(IPreferenceNode node) : ajoute le nœud node à la racine du gestionnaire de préférences L’objet PreferenceManager est utilisé comme point de départ à l’affichage de la boîte de préférences via PreferenceDialog PreferenceDialog(Shell parent, PreferenceManager pm) : construit une boîte de préférence sous le contrôle d’un PreferenceManager
JFace/Preference : PreferenceDialog Exemple : afficher une boîte de dialogue de préférences public class PreferenceExample { public PreferenceExample() { ... PreferenceNode one = new PreferenceNode("one", new PreferencePageOne()); PreferenceNode two = new PreferenceNode("two", new PreferencePageTwo()); PreferenceNode three = new PreferenceNode("three", "Page Three", null, PreferencePageTwo.class.getName()); one.add(two); one.add(three); PreferenceManager mgr = new PreferenceManager(); mgr.addToRoot(one); // Autre écriture pour ajouter two et three à one // mgr.addTo("one", two); // mgr.addTo("one", three); PreferenceDialog myPreferenceDialog = new PreferenceDialog(null, mgr); myPreferenceDialog.open(); myDisplay.dispose(); } public static void main(String[] args) { new PreferenceExample(); } } PreferenceExample.java du projet PreferenceExamples Construction d’un gestionnaire de préférences Ajout du nœud one à la racine Construction d’une boîte de dialogue de préférences
JFace/Preference : PreferenceStore L’API JFace fournit un mécanisme qui permet de persister les informations de préférences L’API de persistance s’appuie sur la classe PreferenceStore qui permet la sauvegarde et le chargement à partir d’un flux Principales méthodes … boolean getBoolean(String name) … : des accesseurs sur des valeurs de préférence par l’intermédiaire d’un nom boolean getDefaultBoolean(String name) … : des accesseurs sur les valeurs par défaut au travers d’un nom void setValue(String name, boolean value) : des modifieurs sur les valeurs au travers d’un nom et d’une valeur void setDefaultValue(String name, boolean value) : des modifieurs sur les valeurs par défaut par l’intermédiaire d’un nom et d’une valeur save() et save(OutputStream, String header) : sauvegarde load() et load(InputStream) : chargement
JFace/Preference : PreferenceStore Exemple : chargement et sauvegarde de préférences public class PreferenceStoreExample { public PreferenceStoreExample() { ... PreferenceNode one = new PreferenceNode("one", new PreferenceStorePageOne()); PreferenceNode two = new PreferenceNode("two", new PreferenceStorePageTwo()); PreferenceManager mgr = new PreferenceManager(); mgr.addToRoot(one); mgr.addTo("one", two); PreferenceDialog myPreferenceDialog = new PreferenceDialog(null, mgr); PreferenceStore ps = new PreferenceStore("prefs.properties"); try { ps.load(); } catch (IOException e) { e.printStackTrace(); } myPreferenceDialog.setPreferenceStore(ps); myPreferenceDialog.open(); try { ps.save(); } catch (IOException e) { e.printStackTrace(); } ... } } PreferenceStoreExample.java du projet PreferenceExamples Création d’un PreferenceStore à partir d’un fichier « plat » prefs.properties Chargement des préférences Associe l’objet PreferenceStore à l’objet PreferenceDialog qui le dispatchera à toutes les pages
JFace/Preference : PreferenceStore Exemple (suite) : chargement et sauvegarde de préférences public class PreferenceStorePageOne extends PreferencePage { private static final String FIELD1_PROPERTIES = "pageOne.field1"; private static final String FIELD2_PROPERTIES = "pageOne.field2"; protected Control createContents(Composite comp) { ... textFieldOne.setText(currentPreferenceStore.getString(FIELD1_PROPERTIES)); textFieldTwo.setText(currentPreferenceStore.getString(FIELD2_PROPERTIES)); ... } public boolean performApply() { this.performOk(); } public boolean performOk() { IPreferenceStore currentPreferenceStore = getPreferenceStore(); // Modification du contenu de la sauvegarde à partir des champs de texte if (this.textFieldOne != null) currentPreferenceStore.setValue(FIELD1_PROPERTIES, textFieldOne.getText()); if (this.textFieldTwo != null) currentPreferenceStore.setValue(FIELD2_PROPERTIES, textFieldTwo.getText()); return t rue; } } PreferenceStorePageOne.java du projet PreferenceExamples A la construction besoin d’initialiser le contenu des composants Appeler lors de l’action Apply Récupération de gestionnaire de persistance Sauvegarde des préférences Appeler lors de l’action Ok
JFace/Preference : FieldEditor La construction d’une page de préférences impose de devoir gérer explicitement la persistance des valeurs Les composants FieldEditor fournissent une encapsulation de la gestion de la persistance de manière à masquer cette étape répétitive Un composant FieldEditor encapsule également un composant graphique Selon le composant utilisé, des contrôles de cohérence implicites peuvent être réalisés (Integer correctement formé) JFace fournit une bibliothèque prédéfinie de composants graphiques Construction commune d’un composant FieldEditor FieldEditor(String name, String label, Composite parent) : FieldEditor identifié par name, une valeur pour un label et le composant parent
JFace/Preference : FieldEditorPreferencePage Pour intégrer des objets FieldEditors dans une page de préférences, la page doit étendre FieldEditorPreferencePage Une page FieldEditorPreferencePage positionne verticalement les objects FieldEditors Styles des FieldEditors lors de la construction d’une page GRID : l’espace entre le label et le composant est uniforme FLAT : l’espace entre le champ de texte et le composant varie FieldEditorPreferencePage nécessite l’implémentation de Control createContents(Composite parent) : retourne le contrôle en charge de définir l’IHM Différentes méthodes sont également utilisables addField(FieldEditor fe) : ajoute l’objet FieldEditor à la page getFieldEditorParent() : permet de retourne le parent de la page (à utiliser lors de la construction des composants FieldEditors)
JFace/Preference : FieldEditorPreferencePage Exemple : page de préférences pour FieldEditors public class PreferenceFieldEditorPageOne extends FieldEditorPreferencePage { private static final String FILE_PROPERTIES = "pageOne.file"; ... public PreferenceFieldEditorPageOne() { super("Page Une", GRID); } protected void createFieldEditors() { BooleanFieldEditor bfe = new BooleanFieldEditor(CHECK_PROPERTIES, "Booléen", getFieldEditorParent()); addField(bfe); StringFieldEditor sfe = new StringFieldEditor(STRING_PROPERTIES, "Chaîne de caractères", getFieldEditorParent()); addField(sfe); IntegerFieldEditor ife = new IntegerFieldEditor(INTEGER_PROPERTIES,"Entier", getFieldEditorParent()); addField(ife); ColorFieldEditor cfe = new ColorFieldEditor(COLOR_PROPERTIES,"Couleur", getFieldEditorParent()); addField(cfe); DirectoryFieldEditor dfe = new DirectoryFieldEditor(DIRECTORY_PROPERTIES, "Répertoire", getFieldEditorParent()); addField(dfe); FileFieldEditor ffe = new FileFieldEditor(FILE_PROPERTIES, "Fichier", getFieldEditorParent()); addField(ffe); } } PreferenceFieldEditorPageOne.java du projet PreferenceExamples Style GRID définit comment sont affichés les FieldEditors L’identifiant du FieldEditor est utilisé pour persister la préférence
JFace/Preference : FieldEditorPreferencePage Exemple (suite) : page de préférences pour FieldEditors public class PreferenceFieldEditorExample { public PreferenceFieldEditorExample() { ... PreferenceNode one = new PreferenceNode("one", new PreferenceFieldEditorPageOne()); PreferenceNode two = new PreferenceNode("two", new PreferenceFieldEditorPageTwo()); PreferenceNode three = new PreferenceNode("three", new PreferenceFieldEditorPageThree()); PreferenceManager mgr = new PreferenceManager(); mgr.addToRoot(one); mgr.addToRoot(two); mgr.addToRoot(three); PreferenceDialog myPreferenceDialog = new PreferenceDialog(null, mgr); PreferenceStore ps = new PreferenceStore("fieldprefs.properties"); try { ps.load(); } catch (IOException e) { e.printStackTrace(); } myPreferenceDialog.setPreferenceStore(ps); myPreferenceDialog.open(); try { ps.save(); } catch (IOException e) { e.printStackTrace(); } ... } } PreferenceFieldEditorExample.java du projet PreferenceExamples Construction identique aux objets de type PreferencePage Fichier contenant les valeurs des préférences
JFace/Preference : FieldEditorPreferencePage Exemple (suite) : page de préférences pour FieldEditors PreferenceFieldEditorPageOne.java du projet PreferenceExamples fieldprefs.properties du projet PreferenceExamples pageOne.string
JFace/Preference : FieldEditor personnalisé Il se peut que les composants FieldEditor ne correspondent pas directement aux besoins Possibilité de créer facilement de nouveaux FieldEditor Principales méthodes int getNumberOfControls() : retourne le nombre de composants contenu dans le FieldEditor adjustForNumColumns(int nC) : ajuste la valeur « horizontal span » par le nombre de colonne nC doFillIntoGrid(Composite p, int nC) : construction des composants via le parent p et le nombre de colonne nC doLoad() : chargement des préférences doLoadDefault() : chargement par défaut doStore() : sauvegarde des préférences
JFace/Preference : FieldEditor personnalisé Exemple : composant FieldEditor min/max PreferenceFieldEditorPageFour.java du projet PreferenceExamples La valeur min est supérieure à la valeur max => génération erreur La valeur min ou la valeur max contient une valeur non cohérente => génération erreur Le but de ce FieldEditor est de fournir deux zones de saisies d’entier dont la valeur min est strictement inférieure à la valeur max Un composite contient un label et un champ de texte
JFace/Preference : FieldEditor personnalisé Exemple (suite) : composant FieldEditor min/max public class MinMaxFieldEditor { ... protected void doFillIntoGrid(Composite parent, int numColumns) { top = parent; ... Label label = this.getLabelControl(parent); GridData labelData = new GridData(); labelData.horizontalSpan = numColumns; label.setLayoutData(labelData); ... minField = new Text(minComposite, SWT.BORDER); minField.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { clearErrorMessage(); } }); minField.addFocusListener(new FocusAdapter()) { public void focusGained(FocusEvent e) { refreshValidateState(); } public void focusLost(FocusEvent e) { valueChanged(); clearErrorMessage(); } } } } La validation est réalisée lors de la perte du focus MinMaxFieldEditor.java du projet PreferenceExamples Le label est positionné sur la partie haute
public class PreferencePersoFieldEditorExample { public PreferencePersoFieldEditorExample() { PreferenceManager mgr = new PreferenceManager(); PreferenceNode three = new PreferenceNode("three", new PreferenceFieldEditorPageThree()); PreferenceNode four = new PreferenceNode("four", new PreferenceFieldEditorPageFour()); mgr.addToRoot(three); mgr.addToRoot(four); PreferenceDialog myPreferenceDialog = new PreferenceDialog(null, mgr); PreferenceStore ps = new PreferenceStore("persofieldprefs.properties"); ... } } JFace/Preference : FieldEditor personnalisé Exemple (suite) : composant FieldEditor min/max public class PreferenceFieldEditorPageFour extends FieldEditorPreferencePage { public PreferenceFieldEditorPageFour() { super("Page Quatre", GRID); } protected void createFieldEditors() { MinMaxFieldEditor bfe = new MinMaxFieldEditor("tolerance", "Tolérence", 3, getFieldEditorParent()); addField(bfe); } } PreferencePersoFieldEditorExample.java du projet PreferenceExamples PreferencePersoFieldEditorPageFour.java du projet PreferenceExamples
JFace/Preference : FieldEditor personnalisé Exemple : espaces entre FieldEditors PreferencePersoFieldEditorPageTwo.java du projet PreferenceExamples PreferencePersoFieldEditorPageFour.java du projet PreferenceExamples Construction d’objets SpacerFieldEditor pour définir explicitement des espaces entre les FieldEditors Exemple basé sur celui-ci … www.eclipse.org/articles/Article-Field-Editors/field_editors.html
JFace/Preference : FieldEditor personnalisé Exemple (suite) : espaces entre FieldEditors public class SpacerFieldEditor extends LabelFieldEditor { public SpacerFieldEditor(Composite parent, int pVGap) { super("", parent, pVGap); } } public class PreferenceFieldEditorPageThree extends FieldEditorPreferencePage { protected void createFieldEditors() { FontFieldEditor ffe = new FontFieldEditor(FONT_PROPERTIES, "Police", getFieldEditorParent()); addField(ffe); SpacerFieldEditor spacer = new SpacerFieldEditor(getFieldEditorParent(), V_GAP); addField(spacer); RadioGroupFieldEditor rfe = new RadioGroupFieldEditor(RADIO_PROPERTIES, "Radio Group", 2, strings, getFieldEditorParent(), true); addField(rfe); spacer = new SpacerFieldEditor(getFieldEditorParent(), V_GAP); addField(spacer); ... } } SpacerFieldEditor.java du projet PreferenceExamples PreferenceFieldEditorPageThree.java du projet PreferenceExamples Entre chaque FieldEditor un SpacerFieldEditor pour ajouter un espace horizontal Un SpacerFieldEditor est un LabelFieldEditor sans valeur de texte
JFace/Resource : ImageDescriptor Nous avons vu dans la partie SWT que la création d’objets Image nécessitait l’utilisation d’un Display Un descripteur d’image définit par ImageDescriptor est une sorte de factory d’images sans utilisation explicite d’un Display A partir d’un ImageDescriptor, il est possible de Image createImage() : créer un objet Image ImageData getImageData() : créer un objet ImageData Création à partir d’un fichier static ImageDescriptor createFromFile(Class location, String filename) location chemin de la classe qui définit le répertoire de la ressource filename nom du fichier Création à partir d’une URL static ImageDescriptor createFromURL(URL url)
JFace/Resource : ImageDescriptor Exemple : afficher une image via un ImageDescriptor package eclipse.jface.resourceexamples; public class ImageDescriptorExample { public ImageDescriptorExample() { Display display = new Display(); final Shell shell = new Shell(display); shell.setText("ImageDescriptor : File and URL"); shell.setLayout(new GridLayout(1, false)); ImageDescriptor imageDescr = ImageDescriptor.createFromFile( eclipse.jface.resourceexamples.ImageDescriptorExample.class, "image/superstar.jpg"); Label myLabelImage = new Label(shell, SWT.NONE); myLabelImage.setImage(imageDescr.createImage()); shell.pack(); shell.open(); ... } public static void main(String[] args) { new ImageDescriptorExample(); } } ImageDescriptorExample.java du projet ResourceExamples L’image est localisée dans un sous répertoire image qui est à la racine du sous package resourceexamples Création d’un Image à partir d’un ImageDescriptor
JFace/Resource : Resource Lors de la conception d’interfaces utilisateurs, il est souvent nécessaire de réutiliser des mêmes ressources : couleurs, fontes, images, ressources bundles JFaceResources fournit un mécanisme pour la déclaration de ressources dans des registres et de les appeler par un nom Chaque type de ressource est défini dans un registre dont les accès sont définis dans JFaceResources ColorRegistry pour les couleurs via getColorRegistry() FontRegistry pour les fontes via getFontRegistry() ImageRegistry pour les images via getImageRegistry() Les registres fonctionnent sur un principe commun : cas du registre ColorRegistry put(String id, RGB colorData) : ajouter une couleur au registre Color get(String id) : accesseur sur la couleur identifiée par id
JFace/Resource : Resource Exemple : gestion des ressources via les registres public class ResourcesExample { public static final String SUPERSTAR_KEULKEUL_IMAGE = "superstar"; public static final String ARIAL_KEULKEUL_FONT = "Arial"; public static final String RED_KEULKEUL_COLOR = "red"; public static final String BLACK_KEULKEUL_COLOR = "black"; public void ResourcesExample() { // Color resources definition ColorRegistry colorRegistry = JFaceResources.getColorRegistry(); colorRegistry.put(BLACK_KEULKEUL_COLOR, new RGB(0,0,0)); colorRegistry.put(RED_KEULKEUL_COLOR, new RGB(255, 0, 0)); // Font resources definition FontRegistry fontRegistry = JFaceResources.getFontRegistry(); FontData fontData = new FontData(ARIAL_KEULKEUL_FONT, 40, SWT.BOLD); fontRegistry.put(ARIAL_KEULKEUL_FONT, new FontData[]{fontData}); // Image resources definition ImageRegistry imageRegistry = JFaceResources.getImageRegistry(); ImageDescriptor myImageDescriptor = ImageDescriptor.createFromFile(eclipse.jface.resourceexamples.ResourcesExample. class, "image/superstar.jpg"); imageRegistry.put(SUPERSTAR_KEULKEUL_IMAGE, myImageDescriptor); } ... } ResourcesExample.java du projet ResourceExamples
JFace/Resource : Resource Exemple (suite) : gestion des ressources via les registres public class ResourcesExample { public ResourcesExample() { Display display = new Display(); initResources(); final Shell shell = new Shell(display); shell.setText("JFaceResources : Registry"); shell.setLayout(new GridLayout()); // The first parameter gives the peer to localize the image file. Label myLabelImage = new Label(shell, SWT.NONE); myLabelImage.setImage(JFaceResources.getImageRegistry() .get(SUPERSTAR_KEULKEUL_IMAGE)); Label myLabelColor = new Label(shell, SWT.NONE); myLabelColor.setBackground(JFaceResources.getColorRegistry() .get(BLACK_KEULKEUL_COLOR)); myLabelColor.setForeground(JFaceResources.getColorRegistry() .get(RED_KEULKEUL_COLOR)); myLabelColor.setFont(JFaceResources.getFontRegistry() .get(ARIAL_KEULKEUL_FONT)); myLabelColor.setText("He is a super star baby"); ... } } ResourcesExample.java du projet ResourceExamples Récupération des ressources via les registres associés
Bilan … Premières impressions … De nombreuses APIs pour gérer des fonctionnalités indispensables à des applications classiques Des composants homogènes via l’héritage de la classe Dialog Les choses non étudiées, prochainement Gestion de texte via le composant TextViewer Intégration des Wizards et des Preferences dans la plateforme Eclipse via la notion d’extension (voir chapitre Conception de plug-ins) Prochaine étape : Conception de plug-ins Pour l’instant nous avons étudié les APIs qui se limitent à la couche graphique Le prochain chapitre s’intéressera à la conception de plug-ins