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 :
- développement de plugins avec EclipseTutoriels sur Eclipse RCP : création de vues, gestion des dépendances ;
- utilisation de SWTTutoriels sur SWT, notamment sur les différents composants et leurs propriétés.
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 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
/**
* 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 :
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.
/**
* 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 :
input =
new
WritableList
(
PersonList.getPersonList
(
),
PersonBean.class
);
D'autre part, on lie le contenu du viewer directement à cette liste grâce à l'instruction :
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 :
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 :
/**
* 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 :
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 :
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 :
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 :
// 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 :
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) :
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 :
Le binding est créé. On retrouve dans le code source généré les éléments que nous avons mentionnés dans cet article :
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 :
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.