JFace Databinding avec des composants JFace

Ce tutoriel se propose de détailler l'utilisation du framework JFace Databinding sous Eclipse afin de permettre la liaison entre un modèle de données et des composants de visualisation JFace. Cet article se situe donc dans la droite lignée de l'article JFace Databinding avec SWTJFace Databinding avec SWT. Cet article est basé sur Eclipse 3.7. Les sources de l'exemple sont disponibles à l'adresse suivante : FTPftp-sources ou HTTPhttp-sources.

Pour toute remarque ou question sur ce tutoriel, profitez de cette discussion : Commentez Donner une note à l'article (5)

Article lu   fois.

L'auteur

Site personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

L'API JFace Databinding d'Eclipse permet de lier facilement les données du modèle objet aux informations affichées par l'interface graphique. Il est en effet intéressant que les données entrées par l'utilisateur soient répercutées directement dans le modèle, et vice versa. Dans le précédent article, nous avons étudié les différents mécanismes de l'API au travers d'un exemple simple qui utilisait uniquement des composants graphiques SWT. On se propose ici de mettre en œuvre ce framework sur des composants graphiques JFace. Pour cet article, il est nécessaire d'avoir des connaissances de base dans les domaines suivants :

II. Un premier exemple

Dans ce premier exemple, nous allons lier de manière la plus simple possible une liste d'éléments de notre modèle à un viewer JFace. Commençons par modifier notre modèle de manière à travailler sur une liste d'objets. Pour cela, nous nous basons sur la classe PersonBean.java que nous avons créée dans le premier article. Nous créons ensuite la classe PersonList qui peut nous retourner une liste d'objets PersonBean grâce à une méthode statique :

PersonBean.java
CacherSélectionnez
PersonList.java
CacherSélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.

/**
 * Cette classe comprend une unique methode statique qui permet de creer une 
 * liste d'objets PersonBean.
 * @author A. Bernard
 */
public class PersonList {

    public static List<PersonBean> getPersonList() {
    List<PersonBean> result = new ArrayList<PersonBean>();
    result.add(new PersonBean("Bernard", "Alain", 24, true));
    result.add(new PersonBean("Dupont", "Michel", 49, true));
    result.add(new PersonBean("Jacques", "Jocelyne", 51, false));
    result.add(new PersonBean("Martin", "Francois", 31, true));
    return result;

    }
}

Nous allons maintenant afficher notre liste de personnes dans un viewer JFace de type tableau. Créons la vue « FirstView » au sein de notre application de la manière suivante :

FirstView.java
CacherSélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.

/**
 * Cette vue affiche une liste de personnes et permet de modifier cette liste. 
 * @author A. Bernard
 */
public class FirstView extends ViewPart {
    public FirstView() {
    }

    /**
     * the table viewer
     */
    private TableViewer viewer;
    /**
     * the input for the viewer
     */
    private WritableList input;

    @Override
    public void createPartControl(Composite parent) {
    parent.setLayout(new GridLayout(2, false));

    // Construction du tableau
    viewer = new TableViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION);
    Table table = viewer.getTable();
    table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true, 2,
      1));

    // Creation des colonnes
    TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("First Name");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Last Name");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Male ?");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Age");
    viewer.getTable().setHeaderVisible(true);

    // Creation des donnees d'entree du tableau 
    input = new WritableList(PersonList.getPersonList(), PersonBean.class);
    // Mise en place du databinding
    ViewerSupport.bind(viewer,
	input,
	BeanProperties.values(new String[] {"firstname", "name",
	  "male", "age"}));


    // Creation des 4 boutons de test du databinding
    // Bouton de suppression de la selection
    Button buttonDelete = new Button(parent, SWT.PUSH);
    buttonDelete.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true,
      false, 1, 1));
    buttonDelete.setText("Delete selection");
    buttonDelete.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	if (!viewer.getSelection().isEmpty()) {
	    IStructuredSelection selection = (IStructuredSelection)
	      viewer
		.getSelection();
	    PersonBean p = (PersonBean) selection.getFirstElement();
	    input.remove(p);
	}
	}
    });

    // Bouton d'ajout d'un element
    Button buttonAdd = new Button(parent, SWT.PUSH);
    buttonAdd.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false,
      1, 1));
    buttonAdd.setText("Add");
    buttonAdd.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	PersonBean p = new PersonBean();
	p.setFirstname("Test1");
	p.setName("Test2");
	input.add(p);
	}
    });

    // Bouton de reinitialisation de la selection
    Button buttonReset = new Button(parent, SWT.NONE);
    buttonReset.setText("Reset selection");
    buttonReset.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	if (!viewer.getSelection().isEmpty()) {
	    IStructuredSelection selection = (IStructuredSelection)
	      viewer
		.getSelection();
	    PersonBean p = (PersonBean) selection.getFirstElement();
	    p.setName("Unknown");
	    p.setFirstname("Unknown");
	    p.setAge(0);
	    p.setMale(true);
	}
	}
    });

    // Bouton d'affichage du modele
    Button buttonPrint = new Button(parent, SWT.NONE);
    buttonPrint.setText("Print model");
    buttonPrint.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	System.out.println(viewer.getInput());
	}
    });

    }

    @Override
    public void setFocus() {
    viewer.getControl().setFocus();
    }
}

Si l'on observe ce code, on s'aperçoit que le viewer ne possède ni ContentProvider, ni LabelProvider. Il n'y a pas non plus d'appel effectué à la méthode setInput. Au lieu de cela, on utilise un objet de type WritableList que l'on initialise grâce à la ligne :

 
Sélectionnez

	input = new WritableList(PersonList.getPersonList(),
	  PersonBean.class);

D'autre part, on lie le contenu du viewer directement à cette liste grâce à l'instruction :

 
Sélectionnez

    ViewerSupport.bind(viewer,
	input,
	BeanProperties.values(new String[] {"firstname", "name",
	  "male", "age"}));

On indique que l'on lie les valeurs « firstname », « name », « male » et « age » aux différentes colonnes du tableau. La classe ViewerSupport permet de simplifier la mise en place du databinding pour les viewers JFace. Il permet d'enregistrer les changements effectués au sein du modèle dans sa globalité, mais aussi ceux effectués sur les éléments individuels. C'est cette classe qui se charge de créer automatiquement le ContentProvider et le LabelProvider. Lançons l'exemple pour constater le fonctionnement du mécanisme :

Image non disponible
Application et modèle après réinitialisation et suppression d'un élément

III. Observer les changements plus précisément

La première vue que nous avons créée affiche tous les éléments, mais nous laisse peu de latitude quant à la personnalisation de l'affichage en lui-même : le LabelProvider est générique et on ne peut pas le modifier. Grâce aux éléments que nous allons mettre en œuvre, nous allons pouvoir remédier à ce problème. Créons la vue « SecondView » de la manière suivante :

 
Sélectionnez

/**
 * Cette vue presente les mecanismes pour observer de maniere plus precise les 
 * elements affiches dans un viewer JFace.
 * @author A. Bernard
 */
public class SecondView extends ViewPart {

    /**
     * the viewer
     */
    private TableViewer viewer;
    /**
     * the input for the table
     */
    private IObservableList input;

    @Override
    public void createPartControl(Composite parent) {
    parent.setLayout(new GridLayout(2, false));
    viewer = new TableViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION);
    Table table = viewer.getTable();
    table.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2,
      1));
    table.setHeaderVisible(true);
    table.setLinesVisible(true);

    // Creation des colonnes
    TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Name");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Details");

    // Definition du ContentProvider
    ObservableListContentProvider contentProvider = new
      ObservableListContentProvider();
    viewer.setContentProvider(contentProvider);

    IObservableSet knownElements = contentProvider.getKnownElements();

    // Creation des maps contenant les differents attributs de la classe
      PersonBean
    final IObservableMap firstNames =
      BeanProperties.value(PersonBean.class,
	"firstname").observeDetail(knownElements);
    final IObservableMap names = BeanProperties.value(PersonBean.class,
	"name").observeDetail(knownElements);
    final IObservableMap ages = BeanProperties.value(PersonBean.class,
	"age").observeDetail(knownElements);
    final IObservableMap genders = BeanProperties.value(PersonBean.class,
	"male").observeDetail(knownElements);
    IObservableMap[] labelMaps = { firstNames, names, ages, genders };

    // Creation du LabelProvider pour le tableau
    ILabelProvider labelProvider = new
      ObservableMapLabelProvider(labelMaps) {
	@Override
	public String getColumnText(Object element, int columnIndex) {
	if (columnIndex == 0) {
	    return names.get(element) + " (" + firstNames.get(element)
	      + ")";
	} else {
	    return genders.get(element) + " (" + ages.get(element) +
	      ")";
	}
	}
    };

    viewer.setLabelProvider(labelProvider);


    // Initialisation des donnees
    List<PersonBean> persons = PersonList.getPersonList();
    input = Properties.selfList(PersonBean.class).observe(persons);
    viewer.setInput(input);


    // Creation des 4 boutons de test du databinding
    // Bouton de suppression de la selection
    Button buttonDelete = new Button(parent, SWT.PUSH);
    buttonDelete.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true,
      false, 1, 1));
    buttonDelete.setText("Delete selection");
    buttonDelete.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	if (!viewer.getSelection().isEmpty()) {
	    IStructuredSelection selection = (IStructuredSelection)
	      viewer
		.getSelection();
	    PersonBean p = (PersonBean) selection.getFirstElement();
	    input.remove(p);
	}
	}
    });

    // Bouton d'ajout d'un element
    Button buttonAdd = new Button(parent, SWT.PUSH);
    buttonAdd.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false,
      1, 1));
    buttonAdd.setText("Add");
    buttonAdd.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	PersonBean p = new PersonBean();
	p.setFirstname("Test1");
	p.setName("Test2");
	input.add(p);
	}
    });

    // Bouton de reinitialisation de la selection
    Button buttonReset = new Button(parent, SWT.NONE);
    buttonReset.setText("Reset selection");
    buttonReset.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	if (!viewer.getSelection().isEmpty()) {
	    IStructuredSelection selection = (IStructuredSelection)
	      viewer
		.getSelection();
	    PersonBean p = (PersonBean) selection.getFirstElement();
	    p.setName("Unknown");
	    p.setFirstname("Unknown");
	    p.setAge(0);
	    p.setMale(true);
	}
	}
    });

    // Bouton d'affichage du modele
    Button buttonPrint = new Button(parent, SWT.NONE);
    buttonPrint.setText("Print model");
    buttonPrint.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	System.out.println(viewer.getInput());
	}
    });
    }

    @Override
    public void setFocus() {
    //
    }

}

Nous pouvons visualiser le résultat et vérifier le bon fonctionnement de l'application :

Utilisation des ObservableMap
Utilisation des ObservableMap

Il nous faut cette fois-ci définir le ContentProvider. La classe ObservableListContentProvider nécessite que les données d'entrée du viewer implémentent l'interface IObservableList. Notons qu'une liste classique peut être transformée en IObservableList grâce à la classe Properties, comme le montre l'exemple ci-dessous :

 
Sélectionnez

List<PersonBean> persons = PersonList.getPersonList();
IObservableList input =
  Properties.selfList(PersonBean.class).observe(persons);

Afin d'observer les modifications dans les listes d'éléments, on utilise des objets IObservableMap. Ces objets permettent d'observer les modifications d'un attribut au sein des différents éléments d'une collection de type IObservableSet. Cette collection est obtenue directement à partir du ContentProvider grâce à l'instruction :

 
Sélectionnez

IObservableSet knownElements = contentProvider.getKnownElements();

On peut ensuite créer un objet IObservableMap pour chaque attribut du modèle pour lequel on souhaite observer les changements. Enfin, on peut créer un LabelProvider pour notre viewer, en passant en paramètre un tableau d'IObservableMap :

 
Sélectionnez

// Creation du LabelProvider pour le tableau
    ILabelProvider labelProvider = new
      ObservableMapLabelProvider(labelMaps) {
	@Override
	public String getColumnText(Object element, int columnIndex) {
	if (columnIndex == 0) {
	    return names.get(element) + " (" + firstNames.get(element)
	      + ")";
	} else {
	    return genders.get(element) + " (" + ages.get(element) +
	      ")";
	}
	}
    };

Comme nous avons pu le constater lorsque nous avons exécuté notre exemple, les données sont bien mises à jour en temps réel.

IV. Utilisation de WindowBuilder

Une fois encore, WindowBuilder nous permet de réaliser nos bindings facilement. Créons la vue « ThirdView » en l'initialisant de la manière suivante :

 
Sélectionnez

public class ThirdView extends ViewPart {

    /**
     * the viewer
     */
    private TableViewer viewer;
    /**
     * the input for the table
     */
    private IObservableList input;

    public ThirdView() {
    //
    }

    @Override
    public void createPartControl(Composite parent) {
    parent.setLayout(new GridLayout(2, false));
    viewer = new TableViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION);
    Table table = viewer.getTable();
    table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2,
      1));
    table.setHeaderVisible(true);
    table.setLinesVisible(true);

    // Creation des colonnes
    TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Name");
    column = new TableViewerColumn(viewer, SWT.NONE);
    column.getColumn().setWidth(100);
    column.getColumn().setText("Details");

    // Initialisation des donnees
    List<PersonBean> persons = PersonList.getPersonList();
    input = Properties.selfList(PersonBean.class).observe(persons);



    // Creation des 4 boutons de test du databinding
    // Bouton de suppression de la selection
    Button buttonDelete = new Button(parent, SWT.PUSH);
    buttonDelete.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true,
      false, 1, 1));
    buttonDelete.setText("Delete selection");
    buttonDelete.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	if (!viewer.getSelection().isEmpty()) {
	    IStructuredSelection selection = (IStructuredSelection)
	      viewer
		.getSelection();
	    PersonBean p = (PersonBean) selection.getFirstElement();
	    input.remove(p);
	}
	}
    });

    // Bouton d'ajout d'un element
    Button buttonAdd = new Button(parent, SWT.PUSH);
    buttonAdd.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false,
      1, 1));
    buttonAdd.setText("Add");
    buttonAdd.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	PersonBean p = new PersonBean();
	p.setFirstname("Test1");
	p.setName("Test2");
	input.add(p);
	}
    });

    // Bouton de reinitialisation de la selection
    Button buttonReset = new Button(parent, SWT.NONE);
    buttonReset.setText("Reset selection");
    buttonReset.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	if (!viewer.getSelection().isEmpty()) {
	    IStructuredSelection selection = (IStructuredSelection)
	      viewer
		.getSelection();
	    PersonBean p = (PersonBean) selection.getFirstElement();
	    p.setName("Unknown");
	    p.setFirstname("Unknown");
	    p.setAge(0);
	    p.setMale(true);
	}
	}
    });

    // Bouton d'affichage du modele
    Button buttonPrint = new Button(parent, SWT.NONE);
    buttonPrint.setText("Print model");
    buttonPrint.addSelectionListener(new SelectionAdapter() {
	@Override
	public void widgetSelected(SelectionEvent e) {
	System.out.println(viewer.getInput());
	}
    });

    }

    @Override
    public void setFocus() {
    // 

    }
}

Nous avons initialisé nos données d'entrée au travers de la variable « input », mais nous n'avons pas encore défini le ContentProvider ni le LabelProvider. Ouvrons la vue avec WindowBuilder, et rendons-nous dans l'onglet « Bindings ». Dans la partie gauche, sélectionnons l'objet graphique « viewer » et sa caractéristique « input » (carrés rouges). Dans la partie droite, sélectionnons l'objet « input » et la caractéristique « Object as IObservableList » (carrés oranges). Enfin, créons le binding en cliquant sur le bouton idoine (carré vert) :

Image non disponible
Première étape avec WindowBuilder

Une fenêtre s'ouvre et nous permet de sélectionner les propriétés à afficher dans les colonnes de notre viewer. Elle nous permet aussi de définir des éditeurs pour les cellules :

Deuxième étape avec WindowBuilder
Deuxième étape avec WindowBuilder

Le binding est créé. On retrouve dans le code source généré les éléments que nous avons mentionnés dans cet article :

 
Sélectionnez

    protected DataBindingContext initDataBindings() {
    DataBindingContext bindingContext = new DataBindingContext();
    //
    ObservableListContentProvider listContentProvider = new
      ObservableListContentProvider();
    viewer.setContentProvider(listContentProvider);
    //
    IObservableMap[] observeMaps =
      BeansObservables.observeMaps(listContentProvider.getKnownElements(),
      PersonBean.class, new String[]{"firstname", "age"});
    viewer.setLabelProvider(new ObservableMapLabelProvider(observeMaps));
    //
    viewer.setInput(input);
    //
    return bindingContext;
    }

En lançant l'application, nous pouvons constater le fonctionnement correct du databinding :

Résultat avec WindowBuilder
Résultat avec WindowBuilder

V. Liens utiles

VI. Conclusion

Dans ce second article, nous avons vu comment mettre en œuvre le framework JFace Databinding sur des composants JFace. Cela nous permet d'observer les changements effectués sur une collection d'objets, soit de manière très simple et rapide, soit de manière plus précise.

VII. Remerciements

Je tiens à remercier les membres de la communauté Java qui m'ont aidé et conseillé pour la rédaction de cet article, ainsi que Fabien (f-leb) et Maxime (_Max_) pour leur relecture orthographique attentive.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2012 Alain Bernard. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.