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.  * Cette classe comprend une unique methode statique qui permet de creer une  
  4.  * liste d'objets PersonBean. 
  5.  * @author A. Bernard 
  6.  */ 
  7. public class PersonList { 
  8.  
  9.     public static List<PersonBean> getPersonList() { 
  10.     List<PersonBean> result = new ArrayList<PersonBean>(); 
  11.     result.add(new PersonBean("Bernard", "Alain", 24, true)); 
  12.     result.add(new PersonBean("Dupont", "Michel", 49, true)); 
  13.     result.add(new PersonBean("Jacques", "Jocelyne", 51, false)); 
  14.     result.add(new PersonBean("Martin", "Francois", 31, true)); 
  15.     return result; 
  16.  
  17.     } 
  18. } 

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.  * Cette vue affiche une liste de personnes et permet de modifier cette liste.  
  4.  * @author A. Bernard 
  5.  */ 
  6. public class FirstView extends ViewPart { 
  7.     public FirstView() { 
  8.     } 
  9.  
  10.     /** 
  11.      * the table viewer 
  12.      */ 
  13.     private TableViewer viewer; 
  14.     /** 
  15.      * the input for the viewer 
  16.      */ 
  17.     private WritableList input; 
  18.  
  19.     @Override 
  20.     public void createPartControl(Composite parent) { 
  21.     parent.setLayout(new GridLayout(2, false)); 
  22.  
  23.     // Construction du tableau 
  24.     viewer = new TableViewer(parent, SWT.SINGLE | SWT.FULL_SELECTION); 
  25.     Table table = viewer.getTable(); 
  26.     table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true, 2, 
  27.       1)); 
  28.  
  29.     // Creation des colonnes 
  30.     TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); 
  31.     column.getColumn().setWidth(100); 
  32.     column.getColumn().setText("First Name"); 
  33.     column = new TableViewerColumn(viewer, SWT.NONE); 
  34.     column.getColumn().setWidth(100); 
  35.     column.getColumn().setText("Last Name"); 
  36.     column = new TableViewerColumn(viewer, SWT.NONE); 
  37.     column.getColumn().setWidth(100); 
  38.     column.getColumn().setText("Male ?"); 
  39.     column = new TableViewerColumn(viewer, SWT.NONE); 
  40.     column.getColumn().setWidth(100); 
  41.     column.getColumn().setText("Age"); 
  42.     viewer.getTable().setHeaderVisible(true); 
  43.  
  44.     // Creation des donnees d'entree du tableau  
  45.     input = new WritableList(PersonList.getPersonList(), PersonBean.class); 
  46.     // Mise en place du databinding 
  47.     ViewerSupport.bind(viewer, 
  48. 	input, 
  49. 	BeanProperties.values(new String[] {"firstname", "name", 
  50. 	  "male", "age"})); 
  51.  
  52.  
  53.     // Creation des 4 boutons de test du databinding 
  54.     // Bouton de suppression de la selection 
  55.     Button buttonDelete = new Button(parent, SWT.PUSH); 
  56.     buttonDelete.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, true, 
  57.       false, 1, 1)); 
  58.     buttonDelete.setText("Delete selection"); 
  59.     buttonDelete.addSelectionListener(new SelectionAdapter() { 
  60. 	@Override 
  61. 	public void widgetSelected(SelectionEvent e) { 
  62. 	if (!viewer.getSelection().isEmpty()) { 
  63. 	    IStructuredSelection selection = (IStructuredSelection) 
  64. 	      viewer 
  65. 		.getSelection(); 
  66. 	    PersonBean p = (PersonBean) selection.getFirstElement(); 
  67. 	    input.remove(p); 
  68. 	} 
  69. 	} 
  70.     }); 
  71.  
  72.     // Bouton d'ajout d'un element 
  73.     Button buttonAdd = new Button(parent, SWT.PUSH); 
  74.     buttonAdd.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 
  75.       1, 1)); 
  76.     buttonAdd.setText("Add"); 
  77.     buttonAdd.addSelectionListener(new SelectionAdapter() { 
  78. 	@Override 
  79. 	public void widgetSelected(SelectionEvent e) { 
  80. 	PersonBean p = new PersonBean(); 
  81. 	p.setFirstname("Test1"); 
  82. 	p.setName("Test2"); 
  83. 	input.add(p); 
  84. 	} 
  85.     }); 
  86.  
  87.     // Bouton de reinitialisation de la selection 
  88.     Button buttonReset = new Button(parent, SWT.NONE); 
  89.     buttonReset.setText("Reset selection"); 
  90.     buttonReset.addSelectionListener(new SelectionAdapter() { 
  91. 	@Override 
  92. 	public void widgetSelected(SelectionEvent e) { 
  93. 	if (!viewer.getSelection().isEmpty()) { 
  94. 	    IStructuredSelection selection = (IStructuredSelection) 
  95. 	      viewer 
  96. 		.getSelection(); 
  97. 	    PersonBean p = (PersonBean) selection.getFirstElement(); 
  98. 	    p.setName("Unknown"); 
  99. 	    p.setFirstname("Unknown"); 
  100. 	    p.setAge(0); 
  101. 	    p.setMale(true); 
  102. 	} 
  103. 	} 
  104.     }); 
  105.  
  106.     // Bouton d'affichage du modele 
  107.     Button buttonPrint = new Button(parent, SWT.NONE); 
  108.     buttonPrint.setText("Print model"); 
  109.     buttonPrint.addSelectionListener(new SelectionAdapter() { 
  110. 	@Override 
  111. 	public void widgetSelected(SelectionEvent e) { 
  112. 	System.out.println(viewer.getInput()); 
  113. 	} 
  114.     }); 
  115.  
  116.     } 
  117.  
  118.     @Override 
  119.     public void setFocus() { 
  120.     viewer.getControl().setFocus(); 
  121.     } 
  122. } 

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.