Improve Foudations Improve Technologies Improve Community Improve Institute

JFace Field Assistance

A partir d’Eclipse 3.2M4, JFace inclut un package consacré à l’aide à la saisie (org.eclipse.jface.fieldassist). Il apporte des fonctionnalités qui améliorent l’ergonomie des formulaires en offrant la possibilité d’ajouter pour chaque champ :

  • des icônes,
  • des bulles d’aides sur ces icônes,
  • des propositions pour la valeur à saisir,
  • des commentaires sur ces propositions.

Rien que la possibilité d’ajouter une icône nous permet enfin de mettre en oeuvre convenablement une particularité bien connue des formulaires : la fameuse étoile pour indiquer qu’un champ est obligatoire. Mais l’API est suffisamment ouverte pour imaginer d’autres usages.

DecoratedField

Les possibilités

DecoratedField, comme son nom l’indique, correspond à un champ décoré. En l’occurrence, la décoration peut se concrétiser par la présence d’icônes, placées autour du champ. L’exemple ci-dessous comporte 2 icônes :

fieldassist1.jpg

Le choix des icônes est libre, il faut simplement fournir à l’API des instances d’Image SWT.

Chacune des icônes peut porter une bulle d’aide qui apparaîtra naturellement en passant la souris dessus. Ceci peut s’avérer utile pour préciser à l’utilisateur la signification de l’icône.

fieldassist2.jpg

Création d'un champ texte décoré

Voyons maintenant comment utiliser DecoratedField pour mettre en oeuvre cette fonctionnalité.

Pour un simple champ texte (Text), le code est relativement simple :

DecoratedField dField = new DecoratedField(parent, SWT.BORDER, new TextControlCreator());
Image img = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_ELEMENT);
FieldDecoration decoration = new FieldDecoration(img, "description");
dField.addFieldDecoration(decoration, SWT.LEFT | SWT.TOP, false);
  1. On crée une instance de DecoratedField en passant le composant parent et le style, comme c’est l’usage pour tous les composants SWT. Mais un 3ème argument réclame une implémentation d’IControlCreator. Ce n’est pas un problème pour l’instant car TextControlCreator apporte l’implémentation qui convient pour la création d’un simple champ texte.
  2. Il va falloir fournir une instance d’Image pour déclarer l’icône. Ici, nous nous contentons d’exploiter une icône déjà disponible dans la plate-forme Eclipse, ce qui nous évite de recourir à un fichier externe. N’importe quelle image pourrait bien sûr faire l’affaire.
  3. On crée une instance de FieldDecoration en passant l’image et un texte qui servira pour la bulle d’aide.
  4. On ajoute la décoration au champs. Celle-ci peut être positionnée par rapport au champ en utilisant les constantes de SWT. Enfin, un booléen permet d’indiquer si l’icône doit être visible en permanence ou seulement lorsque le champ a le focus.

Le résultat est le suivant :

fieldassist3.jpg

Décoration d'autres composants

Décorer d’autres composants est possible et repose sur la même API. Il suffit de passer une implémentation particulière d’IControlDecorator à la création du DecoratedField. En effet, le rôle d’IControlCreator est de créer le composant à décorer (Control SWT).

Par exemple, nous pouvons remplacer le champ text (Text) précédent par un bouton (Button) de la façon suivante :

DecoratedField dField = new DecoratedField(parent, SWT.BORDER, new IControlCreator() {
   public Control createControl(Composite parent, int style) {
      Button b = new Button(parent, SWT.PUSH);
      b.setText("Button");
      return b;
   }
});

fieldassist4.jpg

Utilisation avec le plugin Forms

Le plugin Forms apporte des facilités pour créer des formulaires de saisie. Il apparaît donc naturel de vouloir utiliser DecoratedField avec ce dernier.

FormToolkit apporte des facilités pour ajouter des champs à un formulaire (createText()) mais n’est pas prévu pour ajouter des instances de DecoratedField. Heureusement, le plugin est suffisamment ouvert pour y parvenir :

FormToolkit toolkit = new FormToolkit(parent.getDisplay());
DecoratedField df = new DecoratedField(form.getBody(), SWT.BORDER, new TextControlCreator());
toolkit.adapt(df.getLayoutControl(), false, false);

Les particularités qui apparaissent sont les suivantes :

  • Puisqu’il n’y a pas de méthode prévue sur le toolkit, on crée le DecoratedField en utilisant son constructeur.
  • Le parent du DecoratedField est le corps du formulaire (classique quand on utilise Forms).
  • Il faut appeler adapt() pour que le toolkit adapte ce composant particulier au formulaire. Cette méthode va notamment positionner la bonne couleur de fond aux zones de décoration (ces zones sont grises par défaut alors que le fond du formulaire est généralement blanc).

Malheureusement un problème subsiste malgré l’adaptation du champ décoré au formulaire : si les icônes ont des zones transparentes, celles-ci apparaîtront en gris et non en blanc. Ce n’est qu’un détail mais il peut être embarrassant du point de vue visuel. En attendant une amélioration sur ce point, il vaut mieux éviter la transparence dans les images utilisées.

J’ai moi-même posté une demande pour améliorer la compatibilité entre Forms et JFace Field Assistance.

Proposals

La fonctionnalité appelée “Proposals” est l’autre aspect de l’API Field Assistance. Elle permet de faire apparaître une liste de propositions (liste déroulante) pour la valeur à saisir. Elle correspond à la fonction disponible dans l’éditeur de code Java d’Eclipse IDE avec la combinaison de touches Ctrl+Espace.

fieldassist5.jpg

Voici ses possibilités :

  • Le texte affiché dans la liste peut être différent de la valeur qui sera insérée dans le champ
  • Des commentaires peuvent apparaître sur chacune des propositions.
  • L’apparition de la liste des propositions peut être automatique ou déclenchée selon des combinaisons de touches paramétrables.
  • La liste des valeurs peut être filtrée en fonction de ce qui a déjà été saisi dans le champ.
  • Lorsqu’une valeur est choisie dans la liste, elle peut être insérée dans le champ en complément ou en remplacement de ce qui a été saisi ; il est également possible de désactiver cette insertion.

ContentProposalAdapter

Pour mettre en oeuvre les “Proposals”, il suffit de créer une instance de ContentProposalAdapter. Mais le constructeur attend de nombreux arguments :

  1. Le composant de saisie (Control) auquel s’appliquent les propositions ; le plus souvent, il s’agira d’un Text. Si l’on veut récupérer celui que nous avons créé précédemment dans le DecoratedField, on pourra l’obtenir par dField.getControl();
  2. Une implémentation d’IControlContentAdapter dont le rôle est de mettre à jour le composant de saisie suite au choix d’une proposition ; pour un champ Text, on pourra utiliser TextContentAdapter
  3. Une implémentation d’IContentProposalProvider qui définit la liste des propositions (voir ci-après)
  4. Une implémentation (facultative) d’ILabelProvider pour définir l’affichage des propositions sous forme de textes et/ou d’images
  5. Une instance de KeyStroke pour préciser le raccourci clavier éventuel
  6. Un tableau de caractères qui vont déclencher l’affichage des propositions dès qu’il seront saisis dans le champ (indépendamment du KeyStroke précédent)
  7. Un booléen qui indique si ce qui est saisi doit être à la fois transmis à la liste des propositions (pour filtrage par exemple) et au champ de saisie
  8. Un booléen qui active le filtrage des propositions en fonction des caractères saisis dans le champs
  9. Le mode d’exploitation du résultat choisi parmi les 3 qui sont disponibles (PROPOSAL_INSERT pour insérer la valeur, PROPOSAL_REPLACE pour remplacer ce qui a été saisi par la valeur, PROPOSAL_IGNORE pour que le choix reste sans action)

IContentProposalProvider

Comme nous l’avons vu, il est nécessaire de passer une implémentation d’IContentProposalProvider. Celle-ci a pour rôle de fournir les propositions en fonction du texte saisi (méthode getProposals()). Chacune de ses propositions doit elle-même implémenter IContentProposal dont les méthodes sont les suivantes :

  • getContent() : valeur de la proposition qui sera transmise au champ de saisie
  • getLabel() : chaîne de caractères qui sera affichée dans la liste (si on renvoie null ici, c’est getContent() qui sera affiché)
  • getDescription() : message affiché en commentaire de la proposition (peut valoir null)
  • getCursorPosition() : retourne quelle devra être la position (int) du curseur dans le champs de saisie après le choix de la proposition ; typiquement, on retournera la taille de la valeur pour que le curseur soit placé en fin de texte.

Exemple

Compte tenu des possibilités, on imagine que l’implémentation des propositions peut être assez complexe. Voici un exemple qui reste relativement trivial puisqu’il présente comme propositions des valeurs qui sont simplement prises dans un tableau de Strings sous l’action du raccourci clavier Ctrl+Espace :

IContentProposalProvider proposalProvider = new IContentProposalProvider() {
   private String[] values= {"abc", "toto", "titi", "tata", "tutu"};
   public IContentProposal[] getProposals(String contents, int position) {
        IContentProposal[] proposals = new IContentProposal[values.length];
        for (int i = 0; i < values.length; i++) {
          final String val = values[i];
          proposals[i] = new IContentProposal() {
            public String getContent() {
              return val;
            }
 
            public String getLabel() {
              return null;
            }
 
            public String getDescription() {
                return MessageFormat.format("{0} est un excellent choix.",
                        new String[] { val });
            }
            public int getCursorPosition() {
              return val.length();
            }
          };
        }
        return proposals;
      }
    };
    
try {
   KeyStroke keyStroke = KeyStroke.getInstance("Ctrl+Space");
   new ContentProposalAdapter(text, new TextContentAdapter(), proposalProvider, null, 
         keyStroke, null, true, true, ContentProposalAdapter.PROPOSAL_REPLACE);
} catch (ParseException e) {
   e.printStackTrace();
}

Conclusion

Cette API offre des possibilités qui ne demandent qu’à être exploitées. Elle permet d’apporter des informations avant, pendant et/ou après la saisie. On peut par exemple envisager de s’en servir pour signaler des erreurs. Il va de soi que la mise en oeuvre de ces fonctionnalités alourdit le code source et qu’il serait judicieux de disposer de composants pré-décorés, prêts à l’emploi, notamment pour signaler les champs obligatoires.
A ce propos, un débat est en cours sur la bonne manière de signaler les champs obligatoires et aboutira peut-être à un standard dans Eclipse. Quoi qu’il en soit, cette nouveauté est vraiment la bienvenue pour créer des applications de gestion avec Eclipse RCP.

Frédéric ESNAULT 2006/02/27 09:45

Liens

Discussion

Didier Girard Didier Girard, 2006/03/14 15:25:

Le système a évolué en Eclipse 3.2M5. Le code de l’article ne fonctionne plus. Voici un exemple de code qui fonctionne :

public class Main {


/**
 * @param args
 */
public static void main(String[] args) {
	DeviceData data = new DeviceData();
    data.tracking = true;
    Display display = new Display(data);
	final Shell shell = new Shell(display);
	shell.setBackgroundMode(SWT.INHERIT_DEFAULT);
	GridLayout layoutShell = new GridLayout();
	
	shell.setLayout(layoutShell);
	DecoratedField dField = new DecoratedField(shell, SWT.BORDER, new TextControlCreator());
	FieldDecoration decoration = getWarningDecoration(); 
	dField.addFieldDecoration(decoration, SWT.LEFT | SWT.TOP, false);
	
	
	shell.pack();
	shell.open();
	while (!shell.isDisposed()) {
		if (!display.readAndDispatch())
			display.sleep();
	}
	display.dispose();
}
private static FieldDecoration getWarningDecoration() {
	return FieldDecorationRegistry.getDefault().getFieldDecoration(
			FieldDecorationRegistry.DEC_WARNING);
}

}


 
moni/articles/jface_field_assistance.txt · Last modified: 2006/02/27 10:02 by fesnault