Tutoriel : libérez le pouvoir de la NatTable !

Alain BERNARD

Vos utilisateurs ne vous ont jamais dit que telle ou telle fonctionnalité d'Excel manquait dans vos tableaux ? Avez-vous déjà eu des soucis de performance sur des tableaux devant afficher de très grandes quantités de données ? Souhaitez-vous pouvoir mettre en place à moindre coût un tri des colonnes, un système de persistance, des filtres, la gestion du presse-papiers, et bien d'autres encore ? Alors dans ce cas la NatTable est faite pour vous et au travers de cet article, vous découvrirez les bases pour en tirer le maximum. Les sources de cet exemple sont disponibles à l'adresse suivante : FTP ou HTTP.

Pour toute remarque ou question sur ce tutoriel, profitez de cette discussion : Commentez

- Titre : Tutoriel : libérez le pouvoir de la NatTable !

- Auteur : Alain BERNARD

- Parution : 23 novembre 2013

- Public visé :

- Licence : 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 ® 2013 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.

Sommaire

- I. Introduction

- II. Charger les données

- II-A. Source de données

- II-B. Modèle de données

- II-C. Chargement des données

- II-D. Architecture des exemples

- III. Un premier exemple simple

- III-A. Le fonctionnement de la NatTable

- III-B. La première NatTable

- IV. La gestion des colonnes

- IV-A. Figer les lignes et colonnes

- IV-B. Afficher/cacher les colonnes

- IV-C. Grouper des colonnes

- IV-D. Gérer les colonnes affichées

- IV-E. Trier les données dans les colonnes

- V. Gérer les données

- V-A. Les données d'entrée

- V-B. Dériver des données

- V-B-1. Créer une colonne dérivée

- V-B-2. Créer une ligne dérivée

- V-C. Filtrer les données

- V-D. Grouper les données par valeur

- V-E. Récupérer la sélection

- V-F. Grouper des lignes

- VI. Configurer la NatTable

- VI-A. Comment fonctionne la configuration

- VI-B. Appliquer un style aux cellules

- VI-C. Éditer les cellules

- VII. Persistez !

- VIII. Liens utiles

- IX. Conclusion

- X. Remerciements

- XI. Annexe : lancer les exemples

I - Introduction

Qu'est-ce que la NatTable ? Il s'agit d'un composant graphique basé sur SWT destiné à afficher de manière performante des grandes quantités de données, dans un format de type tableau. Par dessus ce mécanisme de base sont disponibles, à moindre coût, une quantité d'autres fonctionnalités : filtres et tris, groupement de colonnes et de lignes, gestion du presse-papiers, persistance de l'affichage, mise à jour des données en temps réel et composants personnalisés de visualisation et modification des données affichées… Les données sont chargées au fur et à mesure de leur affichage et non pas d'un bloc, un peu comme le ferait une table SWT avec le « LazyContentProvider » (voir ici).
En bref, le but est de proposer des fonctionnalités proches des tableurs tels qu'Excel ou OpenOffice dans un composant graphique SWT. La NatTable peut ainsi afficher des jeux de données contenant jusqu'à un million de lignes et de colonnes. Ce composant, qui fait partie du projet Nebula d'Eclipse a été initialement créé par Andy TSOI qui lui a donné le prénom de son épouse Natalie. Dans cet article, nous nous concentrerons sur quelques fonctionnalités de la NatTable et sur quelques exemples. Il serait en effet bien trop long d'en faire le tour complet.
Pour suivre cet article, il est nécessaire d'avoir des connaissances en SWT et de bien maîtriser les concepts qui gravitent autour des composants JFace tels que les TableViewer ou les TreeViewer.

II - Charger les données

II-A - Source de données

La première étape pour explorer les possibilités de la NatTable est d'avoir un jeu de données à afficher. Pour cela nous avons besoin d'un jeu de données assez grand, puisque le propre de la NatTable est d'afficher des grandes quantités de données. Pour cela, nous pouvons utiliser la base statistiques de l'aéroport de San Francisco sur les atterrissages dans cet aéroport depuis juillet 2005. Ces données sont disponibles librement ici : http://www.flysfo.com/media/facts-statistics/air-traffic-statistics. En ce qui nous concerne, nous travaillons sur le « Aircraft Landing Dataset », que nous modifions pour augmenter le nombre de colonnes :

- colonne « Year » déduite des quatre premiers chiffres de la colonne « Period » ;

- colonne « Month » déduite des deux derniers chiffres de la colonne « Period » ;

- colonne « Average Landing Weight (lb) » déduite de la colonne « Total Landed Weight (lbs) » divisée par le nombre d'atterrissages « Landing count » ;

- ces deux dernières colonnes avec des Kg au lieu des lb.

Le fichier CSV ainsi modifié sera chargé dans notre application puis affiché dans la NatTable. Pour plus de facilité, le fichier créé avec les colonnes supplémentaires est disponible dans les sources de cet article.

Notez qu'au lieu d'un fichier, nous aurions très bien pu extraire des éléments d'une base de données.

II-B - Modèle de données

Pour stocker nos données, nous utilisons une simple classe conteneur appelée SanFranciscoData. Elle contient une liste d'éléments représentant chacun une ligne du fichier CSV, SingleData. SingleData est un bean Java très simple, composé simplement des éléments permettant de stocker la valeur de chaque colonne et les accesseurs associés, directement générés via Eclipse. Il est aussi nécessaire, pour certains exemples, qu'elle implémente Comparable. Le code de cette classe est donné ci-dessous :

SingleData.java

1. package com.abernard.nattable.model;

3. /**

4. * Donnees contenues sur une ligne de fichier d'entree

5. * @author A. BERNARD

6. *

7. */

8. public class SingleData extends Comparable<SingleData> {

10. private Integer period;

11. private Integer year;

12. private Integer month;

13. private String airline;

14. private String airlineIATA;

15. private String publishedAirline;

16. private String publishedAirlineIATA;

17. private String geoSummary;

18. private String geoRegion;

19. private String landingAircraftType;

20. private String aircraftBodyType;

21. private String manufacturer;

22. private String aircraftModel;

23. private String aircraftVersion;

24. private String aircraftName;

25. private Integer landingCount;

26. private Integer totalLandedWeightLbs;

27. private Integer avgLandingWeightLbs;

28. private Integer totalLandedWeightKgs;

29. private Integer avgLandingWeightKgs;

31. public SingleData() {

33. }

35. @Override

36. public int compareTo(SingleData o) {

37. return period.compareTo(o.getPeriod());

38. }

40. // + ACCESSEURS...

42. }

Le code de la classe SanFranciscoData est donné dans le paragraphe ci-après, car elle contient les méthodes de chargement des données.

II-C - Chargement des données

Pour charger les données à partir du fichier, nous utilisons la bibliothèque SuperCSV. L'utilisation de cette bibliothèque n'étant pas le sujet de cet article, il est laissé au lecteur le soin de se documenter si besoin sur son utilisation. Les données sont chargées à partir du fichier d'entrée au sein de la classe SanFranciscoData dont le code est donné ci-dessous :

SanFranciscoData.java

1. package com.abernard.nattable.model;

3. import java.io.IOException;

4. import java.io.Reader;

5. import java.util.ArrayList;

6. import java.util.HashMap;

7. import java.util.List;

9. import org.supercsv.cellprocessor.Optional;

10. import org.supercsv.cellprocessor.ParseInt;

11. import org.supercsv.cellprocessor.constraint.NotNull;

12. import org.supercsv.cellprocessor.ift.CellProcessor;

13. import org.supercsv.io.CsvBeanReader;

14. import org.supercsv.io.ICsvBeanReader;

15. import org.supercsv.prefs.CsvPreference;

17. /**

18. * Conteneur pour les donnees

19. * @author A. BERNARD

20. *

21. */

22. public class SanFranciscoData {

24. private List<SingleData> data;

25. private HashMap<String, String> propertyNames;

27. /**

28. * Liste des attributs de la classe SingleData.java

29. */

30. private static final String[] BEAN_HEADERS = new String[] {"period", "year",

31. "month", "airline", "airlineIATA", "publishedAirline", "publishedAirlineIATA",

32. "geoSummary", "geoRegion", "landingAircraftType", "aircraftBodyType",

33. "manufacturer", "aircraftModel", vaircraftVersion", "aircraftName",

34. "landingCount", "totalLandedWeightLbs", "avgLandingWeightLbs",

35. "totalLandedWeightKgs", "avgLandingWeightKgs"};

37. /** Constructeur */

38. public SanFranciscoData() {

39. data = new ArrayList<>();

40. }

42. /**

43. * Retourne la liste des donnees chargees

44. * @return

45. */

46. public List<SingleData> getData() {

47. return data;

48. }

50. /**

51. * Construit la correspondance entre attributs de la classe SingleData.java

52. * et en-tetes des colonnes

53. * @param beanHeaders noms des attributs

54. * @param csvHeaders en-tetes des colonnes

55. */

56. private void updatePropertyNames(String[] beanHeaders, String[] csvHeaders) {

57. propertyNames = new HashMap<>();

58. for (int i = 0; i < beanHeaders.length; i++) {

59. propertyNames.put(beanHeaders[i], csvHeaders[i]);

60. }

61. }

63. /**

64. * Retourne la correspondance entre attributs de la classe SingleData.java

65. * et en-tetes du fichier CSV

66. * @return

67. */

68. public HashMap<String, String> getPropertyNames() {

69. return propertyNames;

70. }

72. // Methodes de lecture

73. /**

74. * Lit l'ensemble des donnees a partir du fichier d'entree

75. * @param input

76. * @return

77. * @throws IOException

78. */

79. public static SanFranciscoData read(Reader input) throws IOException {

80. SanFranciscoData model = new SanFranciscoData();

81. ICsvBeanReader beanReader = null;

82. try {

83. beanReader = new CsvBeanReader(input, CsvPreference.EXCEL_NORTH_EUROPE_PREFERENCE);

85. // Recuperation des en-tetes du fichier CSV

86. String[] csvHeaders = beanReader.getHeader(true);

87. final String[] header = getBeanHeaders();

88. model.updatePropertyNames(getBeanHeaders(), csvHeaders);

89. final CellProcessor[] processors = getProcessors();

91. SingleData line;

92. while( (line = beanReader.read(SingleData.class, header, processors)) != null ) {

93. model.getData().add(line);

94. }

95. }

96. finally {

97. if( beanReader != null ) {

98. beanReader.close();

99. }

100. }

101. return model;

102. }

104. /**

105. * Retourne la liste des attributs de la classe SingleData.java

106. * @return

107. */

108. public static String[] getBeanHeaders() {

109. return BEAN_HEADERS.clone();

110. }

112. /**

113. * Retourne les convertisseurs entre les valeurs de cellules vers les types

114. * Java correspondants.

115. * @return

116. */

117. private static CellProcessor[] getProcessors() {

118. CellProcessor[] processors = new CellProcessor[] {

119. new ParseInt(), // period

120. new ParseInt(), // year

121. new ParseInt(), // month

122. new NotNull(), // airline

123. new Optional(), // airline IATA

124. new NotNull(), // pub. airline

125. new Optional(), // pub. airline IATA

126. new NotNull(), // GEO Summary

127. new NotNull(), // GEO region

128. new NotNull(), // A/C type

129. new NotNull(), // A/C body type

130. new Optional(), // manufacturer

131. new NotNull(), // model

132. new Optional(), // version

133. new NotNull(), // name

134. new ParseInt(), // count

135. new ParseInt(), // total weight lbs

136. new ParseInt(), // weight lbs

137. new ParseInt(), // total weight Kgs

138. new ParseInt() // weight Kgs

139. };

140. return processors;

141. }

142. }

II-D - Architecture des exemples

Pour simplifier la mise en place des exemples, nous créerons une vue dans un plugin Eclipse pour chaque exemple que nous créerons. Le chargement des données se fera au démarrage du plugin, dans la méthode « start » de l'Activator :

Activator.java

1. package com.abernard.nattable;

3. import java.io.BufferedReader;

4. import java.io.IOException;

5. import java.io.InputStream;

6. import java.io.InputStreamReader;

7. import java.net.URL;

9. import org.eclipse.ui.plugin.AbstractUIPlugin;

10. import org.osgi.framework.BundleContext;

12. import com.abernard.nattable.model.SanFranciscoData;

14. public class Activator extends AbstractUIPlugin {

16. // The plug-in ID

17. public static final String PLUGIN_ID = "com.abernard.nattable"; //$NON-NLS-1$

19. // The shared instance

20. private static Activator plugin;

22. private SanFranciscoData data;

24. /**

25. * The constructor

26. */

27. public Activator() {

28. }

30. /*

31. * (non-Javadoc)

32. * @see org.eclipse.ui.plugin.AbstractUIPlugin#start(org.osgi.framework.BundleContext)

33. */

34. public void start(BundleContext context) throws Exception {

35. super.start(context);

36. plugin = this;

37. readData();

39. }

41. /**

42. * Charge les donnees a partir du fichier contenu dans le plugin

43. */

44. private void readData() {

45. try {

46. URL url = new URL("platform:/plugin/com.abernard.nattable/files/LandingDataModified.csv");

47. InputStream inputStream = url.openConnection().getInputStream();

48. BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));

49. this.data = SanFranciscoData.read(in);

50. } catch (IOException e1) {

51. e1.printStackTrace();

52. }

54. }

56. /*

57. * (non-Javadoc)

58. * @see org.eclipse.ui.plugin.AbstractUIPlugin#stop(org.osgi.framework.BundleContext)

59. */

60. public void stop(BundleContext context) throws Exception {

61. plugin = null;

62. super.stop(context);

63. }

65. /**

66. * Returns the shared instance

67. * @return the shared instance

68. */

69. public static Activator getDefault() {

70. return plugin;

71. }

73. public SanFranciscoData getData() {

74. return data;

75. }

76. }

Le principe du chargement des données à l'activation du plugin est une méthode efficace pour un prototypage rapide mais est à proscrire absolument dans un cas standard !

Les dépendances de votre plugin doivent être les suivantes :

- org.eclipse.runtime ;

- org.eclipse.ui ;

- org.eclipse.ui.forms ;

- org.eclipse.widgets.nattable.core ;

- org.eclipse.widgets.nattable.extension.glazedlists ;

- ca.odell.glazedlists.

Les dépendances aux GlazedLists sont optionnelles, mais vont être utilisées dans certains exemples comme le tri des colonnes.

Les plugins de la NatTable peuvent être installés dans votre distribution Eclipse via les repositorys de la NatTable, dont la dernière livraison est disponible à cette adresse : http://download.eclipse.org/nattable/releases/1.0.1/repository/
Sélectionnez les éléments « NatTable Core », « NatTable POI Extensions » et « NatTable GlazedLists Extension ».

Installation des plugins de la NatTable

Enfin, pour faciliter la mise en place de nos vues, nous créons une super classe qui sera commune à toutes nos vues, « NatTableView.java ». Elle permet un accès simplifié à notre modèle et met en place les éléments communs à toutes nos vues.

NatTableView.java

1. package com.abernard.nattable;

3. import org.eclipse.nebula.widgets.nattable.NatTable;

4. import org.eclipse.nebula.widgets.nattable.data.IDataProvider;

5. import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;

6. import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;

7. import org.eclipse.swt.layout.FillLayout;

8. import org.eclipse.swt.widgets.Composite;

9. import org.eclipse.ui.part.ViewPart;

11. import com.abernard.nattable.model.SanFranciscoData;

12. import com.abernard.nattable.model.SingleData;

13. import org.eclipse.ui.forms.widgets.FormToolkit;

14. import org.eclipse.swt.widgets.Display;

15. import org.eclipse.ui.forms.widgets.ScrolledForm;

17. /**

18. * Cette classe sert de base pour toutes nos vues d'exemple. Elle possede un

19. * acces direct au modele charge et met en place les composants communs.

20. * @author A. BERNARD

21. *

22. */

23. public abstract class NatTableView extends ViewPart {

24. public NatTableView() {

25. }

27. private NatTable nattable;

28. private final FormToolkit formToolkit = new FormToolkit(Display.getDefault());

30. @Override

31. public void createPartControl(Composite parent) {

32. parent.setLayout(new FillLayout());

35. ScrolledForm container = formToolkit.createScrolledForm(parent);

36. formToolkit.paintBordersFor(container);

37. container.setText(getExampleTitle());

38. formToolkit.decorateFormHeading(container.getForm());

40. container.getBody().setLayout(new FillLayout());

41. nattable = createNatTable(container.getBody());

43. }

45. /**

46. * Retourne le titre de la vue

47. * @return

48. */

49. protected abstract String getExampleTitle();

51. /**

52. * Cree la NatTable associee a cette vue

53. * @param parent

54. * @return

55. */

56. protected abstract NatTable createNatTable(Composite parent);

58. /**

59. * Retourne le modele charge au demarrage du plugin

60. * @return

61. */

62. protected SanFranciscoData getModel() {

63. return Activator.getDefault().getData();

64. }

66. /**

67. * Retourne l'accesseur aux donnees du modele pour la NatTable

68. * @return

69. */

70. protected IRowDataProvider<SingleData> getSanFranciscoDataProvider() {

71. return new ListDataProvider<SingleData>(getModel().getData(),

72. new ReflectiveColumnPropertyAccessor<SingleData>(SanFranciscoData.getBeanHeaders()));

74. }

76. @Override

77. public void setFocus() {

78. nattable.setFocus();

80. }

81. }

Tout est maintenant en place pour aborder le vif du sujet !

III - Un premier exemple simple

III-A - Le fonctionnement de la NatTable

La NatTable ne repose pas sur des composants SWT classiques de type Composite, pas plus que sur un composant Table standard. Celui permet de manipuler de grandes quantités de données « virtuelles » sans perte de performance : les éléments graphiques sont calculés et créés seulement lors de l'affichage à l'utilisateur.
Les différentes fonctionnalités disponibles sont ajoutées à la NatTable via des éléments appelés « layers ». Chaque layer définit une fonctionnalité et ils sont ainsi « empilés » les uns sur les autres afin de créer le rendu final. Par exemple, le diagramme ci-dessous permet de visualiser comment les layers sont utilisés en couche pour créer une NatTable :

Architecture de la NatTable

La pile de layer centrale « Body Layer Stack » définit les fonctionnalités au sein de la table en elle-même, deux autres piles de layers permettent de définir le comportement des en-têtes de colonne et de ligne, tandis qu'une dernière pile définit le comportement du coin supérieur, jonction entre les en-têtes de colonnes et de lignes. Tout en bas de chaque pile de layers se trouve un élément « DataLayer » qui contient simplement l'accès aux données de la NatTable.
Chaque pile de layers est agencée au sein de la NatTable dans une région, l'agencement de ces régions étant lui-même agencé par un layer, dans l'exemple du schéma il s'agit d'un GridLayer, agencement le plus classique.
Chaque layer pourra ensuite être configuré plus précisément grâce à des registres de configuration « UIBindingRegistry » et « ConfigRegistry » qui nous permettront par exemple de mettre en place le style des cellules, l'édition des cellules ou encore les raccourcis clavier.
Lorsqu'une action utilisateur ou une commande est déclenchée au niveau de la NatTable, elle sera propagée au sein des différents layers, de celui de plus haut niveau vers le DataLayer, jusqu'à ce qu'un layer réagisse à la commande. Puis, un événement sera lancé pour informer tous les layers qu'une actualisation est potentiellement nécessaire. Cet événement, celui-ci sera propagé au sein de tous les layers, peu importe qu'un layer réagisse à cet événement ou non. On pourra réagir à cet événement en implémentant l'interface « ILayerListener ».

III-B - La première NatTable

Créons notre première NatTable ! Pour commencer, créons la vue nommée « BasicExample » dans notre plugin. Pour gérer les données, la NatTable a besoin, comme n'importe quel viewer d'un « data provider ». Dans le cas de la NatTable, ces éléments implémentent l'interface « IDataProvider ». Nous pouvons créer dans la classe « NatTableView.java » la méthode « getSanFranciscoDataProvider » qui nous permettra de charger dans toutes nos tables les éléments de notre modèle.

NatTableView.java

1. // ...

3. /**

4. * Retourne l'accesseur aux donnees du modele pour la NatTable

5. * @return

6. */

7. protected IRowDataProvider<SingleData> getSanFranciscoDataProvider() {

8. return new ListDataProvider<SingleData>(getModel().getData(),

9. new ReflectiveColumnPropertyAccessor<SingleData>(SanFranciscoData.getBeanHeaders()));

10. }

12. // ...

Analysons cette courte méthode pourtant puissante. Premièrement, pour toutes les données de type liste, la NatTable propose un provider par défaut, le « ListDataProvider ». Cet élément est un peu le pendant de l'ArrayContentProvider dans les viewers JFace classiques.
Le premier paramètre du constructeur est simplement la liste des données à afficher. Le deuxième est la manière dont la NatTable va accéder aux données du modèle, colonne par colonne.
Ici, nous utilisons l'élément le plus automatique, un ReflectiveColumnPropertyAccessor. Comment fonctionne cet élément ? Il accède aux attributs de chaque élément du modèle par réflexion, ce qui impose que les éléments respectent la convention de nommage des « beans » Java pour les accesseurs. La Map fournie au constructeur de cette classe permet de donner les en-têtes de colonne pour chaque attribut.

Qu'est-ce qu'un bean Java ? Pour faire court, il s'agit d'un objet où, entre autres conventions, les attributs et leurs accesseurs suivent une convention de nommage : le getter est nommé « get » + le nom de l'attribut avec la première lettre en majuscule (ou « is » dans le cas d'un booléen) ; le setter est nommé « set » + le nom de l'attribut avec la première lettre en majuscule. Par exemple, pour un attribut name on aura « getName » et « setName". Plus d'infos ici.

Il faut ensuite créer la NatTable. Dans la classe BasicExample.java, modifiez le code comme suit :

BasicExample.java

1. package com.abernard.nattable.examples;

3. import org.eclipse.nebula.widgets.nattable.NatTable;

4. import org.eclipse.nebula.widgets.nattable.data.IDataProvider;

5. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;

6. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;

7. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;

8. import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;

9. import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;

10. import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;

11. import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;

12. import org.eclipse.nebula.widgets.nattable.layer.AbstractLayerTransform;

13. import org.eclipse.nebula.widgets.nattable.layer.DataLayer;

14. import org.eclipse.nebula.widgets.nattable.selection.SelectionLayer;

15. import org.eclipse.nebula.widgets.nattable.viewport.ViewportLayer;

16. import org.eclipse.swt.widgets.Composite;

18. import com.abernard.nattable.NatTableView;

19. import com.abernard.nattable.model.SanFranciscoData;

21. /**

22. * Ce premier exemple montre la mise en place d'une NatTable pourvue des

23. * fonctionnalites minimales.

24. * @author A. BERNARD

25. *

26. */

27. public class BasicExample extends NatTableView {

29. private BodyLayerStack bodyLayer;

31. @Override

32. protected NatTable createNatTable(Composite parent) {

33. IDataProvider dataProvider = super.getSanFranciscoDataProvider();

34. bodyLayer = new BodyLayerStack(dataProvider);

36. DefaultColumnHeaderDataProvider colHeaderDataProvider = new DefaultColumnHeaderDataProvider(SanFranciscoData

37. .getBeanHeaders(), getModel().getPropertyNames());

38. DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);

39. ColumnHeaderLayerStack columnHeaderLayer = new ColumnHeaderLayerStack(colHeaderDataProvider);

40. RowHeaderLayerStack rowHeaderLayer = new RowHeaderLayerStack(rowHeaderDataProvider);

41. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(colHeaderDataProvider,

42. rowHeaderDataProvider);

43. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer,

44. columnHeaderLayer);

46. GridLayer gridLayer = new GridLayer(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer);

47. NatTable natTable = new NatTable(parent, gridLayer);

49. return natTable;

50. }

52. /**

53. * La pile de layers pour la partie centrale de la NatTable

54. * @author A. BERNARD

55. *

56. */

57. public class BodyLayerStack extends AbstractLayerTransform {

59. private SelectionLayer selectionLayer;

61. public BodyLayerStack(IDataProvider dataProvider) {

62. DataLayer bodyDataLayer = new DataLayer(dataProvider);

63. selectionLayer = new SelectionLayer(bodyDataLayer);

64. ViewportLayer viewportLayer = new ViewportLayer(selectionLayer);

65. setUnderlyingLayer(viewportLayer);

66. }

68. /**

69. * Retourne le layer de selection

70. * @return

71. */

72. public SelectionLayer getSelectionLayer() {

73. return selectionLayer;

74. }

76. }

78. /**

79. * La pile de layers pour les en-tetes de colonne

80. * @author A. BERNARD

81. *

82. */

83. public class ColumnHeaderLayerStack extends AbstractLayerTransform {

85. public ColumnHeaderLayerStack(IDataProvider dataProvider) {

86. DataLayer dataLayer = new DataLayer(dataProvider);

87. ColumnHeaderLayer colHeaderLayer = new ColumnHeaderLayer(dataLayer,

88. bodyLayer, bodyLayer.getSelectionLayer());

89. setUnderlyingLayer(colHeaderLayer);

90. }

91. }

93. /**

94. * La pile de layers pour les en-tetes de lignes

95. * @author A. BERNARD

96. *

97. */

98. public class RowHeaderLayerStack extends AbstractLayerTransform {

100. public RowHeaderLayerStack(IDataProvider dataProvider) {

101. DataLayer dataLayer = new DataLayer(dataProvider, 50, 20);

102. RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(dataLayer,

103. bodyLayer, bodyLayer.getSelectionLayer());

104. setUnderlyingLayer(rowHeaderLayer);

105. }

106. }

108. @Override

109. protected String getExampleTitle() {

110. return "Basic Example";

111. }

113. }

Dans la méthode « createNatTable », les différents éléments de la NatTable sont configurés un à un. En premier, on crée les layers pour la partie centrale de la table, l'affichage des données proprement dit. Cette pile de layers nécessite de connaître le DataProvider de la NatTable, pour construire le DataLayer qui encapsule les données qui sont affichées dans la NatTable (rappelez-vous que c'est toujours le layer de plus bas niveau). Puis nous empilons les layers afin de créer des fonctionnalités basiques : un SelectionLayer, puis un ViewportLayer. Le ViewportLayer permet de mettre en place le défilement dans la NatTable. Le SelectionLayer quant à lui suit la sélection courante dans la NatTable.

On crée ensuite les layers pour les en-têtes de colonnes, puis les en-têtes de lignes. Notez que dans les deux cas, le layer central sert de référence pour le positionnement des éléments et est passé au constructeur des layers. De même, le SelectionLayer est donné en argument pour pouvoir suivre la cellule sélectionnée.

Puis nous construisons le layer du coin supérieur gauche de la NatTable grâce aux éléments par défaut. Enfin, nous rassemblons ces quatre layers dans le GridLayer qui va agencer ces éléments. Ce dernier layer nous permet de finaliser la construction de la NatTable. Nous pouvons lancer notre application pour observer le résultat :

Notre première NatTable

Maintenant que les bases de la NatTable sont posées, nous allons passer en revue quelques fonctionnalités majeures.

IV - La gestion des colonnes

IV-A - Figer les lignes et colonnes

Lorsqu'on manipule une grande quantité de données, il est vite pratique de pouvoir figer l'affichage de certaines lignes et de certaines colonnes, à la manière de l'option « Figer les volets » d'Excel. Créez une nouvelle vue « ColumnsExample » dans notre plugin et insérez-y le code suivant :

ColumnsExample.java

1. package com.abernard.nattable.examples;

3. import org.eclipse.nebula.widgets.nattable.NatTable;

4. import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;

5. import org.eclipse.nebula.widgets.nattable.data.IDataProvider;

6. import org.eclipse.nebula.widgets.nattable.freeze.CompositeFreezeLayer;

7. import org.eclipse.nebula.widgets.nattable.freeze.FreezeLayer;

8. import org.eclipse.nebula.widgets.nattable.freeze.config.DefaultFreezeGridBindings;

9. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;

10. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultCornerDataProvider;

11. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultRowHeaderDataProvider;

12. import org.eclipse.nebula.widgets.nattable.grid.layer.ColumnHeaderLayer;

13. import org.eclipse.nebula.widgets.nattable.grid.layer.CornerLayer;

14. import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultColumnHeaderDataLayer;

15. import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultRowHeaderDataLayer;

16. import org.eclipse.nebula.widgets.nattable.grid.layer.GridLayer;

17. import org.eclipse.nebula.widgets.nattable.grid.layer.RowHeaderLayer;

18. import org.eclipse.nebula.widgets.nattable.layer.DataLayer;

19. import org.eclipse.nebula.widgets.nattable.layer.ILayer;

20. import org.eclipse.nebula.widgets.nattable.layer.stack.DefaultBodyLayerStack;

21. import org.eclipse.swt.widgets.Composite;

23. import com.abernard.nattable.NatTableView;

24. import com.abernard.nattable.model.SanFranciscoData;

26. /**

27. * Cet exemple montre comment manipuler les colonnes : figer les colonnes, les

28. * grouper, les trier, choisir lesquelles afficher.

29. * @author A. BERNARD

30. *

31. */

32. public class ColumnsExample extends NatTableView {

34. public ColumnsExample() {

35. //

36. }

38. @Override

39. protected String getExampleTitle() {

40. return "Columns Example";

41. }

43. @Override

44. protected NatTable createNatTable(Composite parent) {

46. // Creation de la pile de layers centrale

47. IDataProvider dataProvider = super.getSanFranciscoDataProvider();

48. DataLayer dataLayer = new DataLayer(dataProvider);

49. DefaultBodyLayerStack bodyLayer = new DefaultBodyLayerStack(dataLayer);

50. // Creation de la capacite a figer les lignes et colonnes

51. FreezeLayer freezeLayer = new FreezeLayer(bodyLayer.getSelectionLayer());

52. CompositeFreezeLayer compositeFreezeLayer = new CompositeFreezeLayer(freezeLayer,

53. bodyLayer.getViewportLayer(), bodyLayer.getSelectionLayer());

55. // Creation des en-tetes de colonnes

56. DefaultColumnHeaderDataProvider colHeaderDataProvider = new DefaultColumnHeaderDataProvider(SanFranciscoData

57. .getBeanHeaders(), getModel().getPropertyNames());

58. ILayer columnHeaderLayer = new ColumnHeaderLayer(new DefaultColumnHeaderDataLayer(colHeaderDataProvider),

59. compositeFreezeLayer, bodyLayer.getSelectionLayer());

61. // Creation des en-tetes de lignes

62. DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(dataProvider);

63. ILayer rowHeaderLayer = new RowHeaderLayer(new DefaultRowHeaderDataLayer(rowHeaderDataProvider),

64. compositeFreezeLayer, bodyLayer.getSelectionLayer());

66. // Creation du coin superieur

67. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(colHeaderDataProvider,

68. rowHeaderDataProvider);

69. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer,

70. columnHeaderLayer);

72. // Creation du layer global

73. GridLayer gridLayer = new GridLayer(compositeFreezeLayer, columnHeaderLayer, rowHeaderLayer,

74. cornerLayer);

76. // Constructeur avec "false" pour eviter l'autoconfiguration de la NatTable

77. NatTable natTable = new NatTable(parent, gridLayer, false);

79. // Configuration de la NatTable

80. natTable.addConfiguration(new DefaultNatTableStyleConfiguration());

81. natTable.addConfiguration(new DefaultFreezeGridBindings());

82. natTable.configure();

84. return natTable;

85. }

86. }

Dans ce code, nous utilisons, pour gagner du temps, un « DefaultBodyLayerStack ». Cet élément met en place un certain nombre de layers qui proposent des fonctionnalités générales : réordonner les colonnes par glisser-déplacer ou les cacher et la couche de sélection et de défilement (le « SelectionLayer » et « ViewportLayer » dont nous avons déjà parlé).
Nous mettons ensuite en place le mécanisme pour figer les colonnes, à travers un élément « FreezeLayer » et un « CompositeFreezeLayer » qui va gérer l'agencement des différents layers et l'affichage des éléments une fois que les lignes/colonnes auront été figées.
Après cela, nous créons les en-têtes de lignes et colonnes, puis le GridLayer global. Enfin, dans le constructeur de la NatTable, nous mettons l'option d'autoconfiguration à faux afin de créer notre propre configuration. Nous ajoutons donc à notre NatTable une configuration par défaut et les raccourcis clavier associés à l'ancrage des lignes et des colonnes. En dernier lieu, il faut absolument penser à appeler la méthode « natTable.configure() » afin d'appliquer nos paramètres.
Nous pouvons ensuite observer le résultat en lançant notre application : appuyer sur Ctrl+Maj+F afin de figer les volets, et sur Ctrl+Maj+U pour les libérer.

Figer les volets dans une NatTable

Vous pouvez aussi tester le réagencement des colonnes en les faisant glisser comme dans une table SWT classique.

IV-B - Afficher/cacher les colonnes

Dans le paragraphe précédent, nous avons mentionné qu'il était possible grâce au « DefaultBodyLayerStack » de cacher et afficher des colonnes. Pourtant, rien ne semble l'indiquer dans notre exemple précédent. Cependant, ajoutez la ligne suivante juste avant l'instruction « natTable.configure() » :

// ...
natTable.addConfiguration(new HeaderMenuConfiguration(natTable));
natTable.configure();
// ....

Cet élément permet de créer un menu contextuel par défaut sur les en-têtes de colonnes. Relancez notre exemple et observez le résultat :

Menu contextuel sur lesen-têtes de colonnes

Vous pouvez ainsi cacher des colonnes à votre guise, les renommer, ou encore les redimensionner. Mais l'instruction pour les grouper n'a aucun effet ! Ceci tout simplement parce que nous n'avons pas configuré de layer pour grouper des colonnes. Regardons donc comment faire.

IV-C - Grouper des colonnes

Encore une fois, grouper des colonnes passera par la mise en place des layers adaptés. Pour cela, remplacez simplement le « DefaultBodyLayerStack » par un « ColumnGroupBodyLayerStack ». Le constructeur de cet élément prend en paramètre un objet de type « ColumnGroupModel » qui stocke les groupes de colonnes. Puis, nous modifions le layer des en-têtes de colonnes et lui ajoutons un layer « ColumnGroupHeaderLayer ». De fait, notre classe devient la suivante (seules les modifications sont laissées visibles) :

ColumnsExample.java

1. // ...

2. public class ColumnsExample extends NatTableView {

4. private final ColumnGroupModel columnGroupModel = new ColumnGroupModel();

6. // ...

8. @Override

9. protected NatTable createNatTable(Composite parent) {

11. // Creation de la pile de layers centrale

12. // ...

13. // Creation du layer pour les groupes de colonnes

14. ColumnGroupBodyLayerStack bodyLayer = new ColumnGroupBodyLayerStack(dataLayer, columnGroupModel);

15. // Creation de la capacite a figer les lignes et colonnes

16. // ...

18. // Creation des en-tetes de colonnes

19. // ...

20. // Ajout d'une surcouche sur les en-tetes de colonnes pour les groupes

21. ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer,

22. bodyLayer.getSelectionLayer(), columnGroupModel);

24. // Creation des en-tetes de lignes

25. // ...

27. // Creation du coin superieur

28. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(colHeaderDataProvider,

29. rowHeaderDataProvider);

30. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer,

31. columnGroupHeaderLayer);

33. // Creation du layer global

34. GridLayer gridLayer = new GridLayer(compositeFreezeLayer, columnGroupHeaderLayer, rowHeaderLayer,

35. cornerLayer);

37. // ...

38. }

39. }

Sans plus attendre, regardons le résultat : après sélection de plusieurs colonnes avec la touche Maj, et un clic sur « Group columns » dans le menu contextuel, une fenêtre s'affiche pour nous permettre d'indiquer le nom du groupe et les colonnes sont groupées automatiquement. On peut par ailleurs réduire le groupe en double-cliquant sur son en-tête.

Grouper des colonnes

Par ailleurs, nous pouvons indiquer les groupes de colonnes à établir directement lors de la construction de la NatTable, en modifiant l'objet « ColumnGroupHeaderLayer, » par exemple comme suit :

// ...
// Ajout d'une surcouche sur les en-tetes de colonnes pour les groupes
ColumnGroupHeaderLayer columnGroupHeaderLayer = new ColumnGroupHeaderLayer(columnHeaderLayer,
bodyLayer.getSelectionLayer(), columnGroupModel);
columnGroupHeaderLayer.addColumnsIndexesToGroup("Date", 0,1,2);
columnGroupHeaderLayer.addColumnsIndexesToGroup("UnBreakable Airlines", 3,4,5,6);
columnGroupHeaderLayer.setGroupUnbreakable(3);

// ...

Dès l'affichage de la NatTable, les groupes sont déjà construits et les quatre colonnes se reportant aux compagnies ne peuvent pas être dégroupées.

IV-D - Gérer les colonnes affichées

Une possibilité intéressante est aussi de proposer à l'utilisateur de choisir lui-même les colonnes à afficher dans la NatTable. Pour cela, nous allons ajouter à notre menu contextuel sur les colonnes une action pour sélectionner les colonnes à afficher. Modifiez la partie configuration de notre exemple avec le code suivant :

// ...
// Configuration de la NatTable
natTable.addConfiguration(new DefaultNatTableStyleConfiguration());
natTable.addConfiguration(new DefaultFreezeGridBindings());

// Creation du dialogue de choix des colonnes
DisplayColumnChooserCommandHandler columnChooserCommandHandler = new DisplayColumnChooserCommandHandler(
bodyLayer.getSelectionLayer(),
bodyLayer.getColumnHideShowLayer(),
columnHeaderLayer,
columnHeaderDataLayer,
columnGroupHeaderLayer,
columnGroupModel);
compositeFreezeLayer.registerCommandHandler(columnChooserCommandHandler);

natTable.addConfiguration(new HeaderMenuConfiguration(natTable) {
@Override
protected PopupMenuBuilder createColumnHeaderMenu(NatTable natTable) {
return super.createColumnHeaderMenu(natTable).withColumnChooserMenuItem();
}
});

natTable.configure();
// ...

Lancez ensuite notre exemple : si vous cliquez sur l'élément « Choose columns », vous pouvez sélectionner les colonnes à afficher.

Choisir les colonnes

IV-E - Trier les données dans les colonnes

Pour trier les données dans les colonnes, la NatTable utilise les GlazedLists, un projet qui permet de trier et filtrer des listes très simplement, permettant leur intégration rapide au sein d'une table par exemple. Pour plus d'informations, consultez le site officiel du projet.
La première étape est donc de transformer notre liste de données en une SortedList qui sera utilisée par la NatTable pour manipuler les données. Pour ce faire, nous modifions l'implémentation par défaut de la méthode « getSanFranciscoDataProvider() ». Puis, comme pour les autres fonctionnalités, la mise en place du tri se fait via un nouveau layer positionné sur les en-têtes de colonnes. Le code ci-dessous donne les modifications à apporter à la classe « ColumnsExample.java ».

ColumnsExample.java

1. // ...

2. public class ColumnsExample extends NatTableView {

4. private SortedList<SingleData> sortedList;

5. private ReflectiveColumnPropertyAccessor<SingleData> columnPropertyAccessor;

7. // ...

9. @Override

10. protected NatTable createNatTable(Composite parent) {

12. // Creation des en-tetes de colonnes

13. // ...

14. // Ajout de la fonctionnalite de tri

15. ConfigRegistry configRegistry = new ConfigRegistry();

16. SortHeaderLayer<SingleData> sortHeaderLayer = new SortHeaderLayer<SingleData>(

17. columnGroupHeaderLayer, new GlazedListsSortModel<SingleData>(

18. sortedList, columnPropertyAccessor, configRegistry, columnHeaderDataLayer),

19. true);

21. // Creation du coin superieur

22. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(colHeaderDataProvider,

23. rowHeaderDataProvider);

24. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer,

25. sortHeaderLayer);

27. // Creation du layer global

28. GridLayer gridLayer = new GridLayer(compositeFreezeLayer, sortHeaderLayer, rowHeaderLayer,

29. cornerLayer);

31. // Constructeur avec "false" pour eviter l'autoconfiguration de la NatTable

32. NatTable natTable = new NatTable(parent, gridLayer, false);

33. natTable.setConfigRegistry(configRegistry);

35. // Configuration de la NatTable

36. natTable.addConfiguration(new DefaultNatTableStyleConfiguration());

37. natTable.addConfiguration(new DefaultFreezeGridBindings());

38. natTable.addConfiguration(new SingleClickSortConfiguration());

40. // ...

42. return natTable;

43. }

45. @Override

46. protected IRowDataProvider<SingleData> getSanFranciscoDataProvider() {

47. EventList<SingleData> eventsList = GlazedLists.eventList(getModel().getData());

48. sortedList = new SortedList<SingleData>(eventsList);

49. columnPropertyAccessor = new ReflectiveColumnPropertyAccessor<SingleData>(SanFranciscoData

50. .getBeanHeaders());

51. return new ListDataProvider<SingleData>(sortedList, columnPropertyAccessor);

52. }

54. }

Cette configuration permet d'activer le tri « par défaut » sur les colonnes qui va effectuer le tri des données avec des comparateurs par défaut (ordre lexicographique pour les chaînes de caractères par exemple). On peut observer le résultat directement en lançant l'application et en cliquant sur les en-têtes de colonnes.

Tri sur une colonne

Notez le raccourci clavier pour établir le tri sur plusieurs colonnes : Alt+Maj+clic gauche. Si vous souhaitez modifier les raccourcis clavier, utilisez une autre implémentation de la classe « DefaultSortConfiguration » au lieu de la « SingleClickSortConfiguration » que nous avons utilisée.
Si vous souhaitez mettre en place un comparateur personnalisé, il vous faudra enregistrer celui-ci dans l'objet ConfigRegistry et l'enregistrer en remplacement du comparateur par défaut. Pour désactiver le tri sur une colonne, utilisez un NullComparator en lieu et place d'un comparateur personnalisé. Par exemple, l'extrait ci-dessous montre comment désactiver le tri sur la première colonne « Period » et effectuer le tri sur « Airline » en fonction de la taille de son nom au lieu de l'ordre lexicographique.

ColumnsExample.java

1. // ...

3. // Ajout de la fonctionnalite de tri

4. ConfigRegistry configRegistry = new ConfigRegistry();

5. SortHeaderLayer<SingleData> sortHeaderLayer = new SortHeaderLayer<SingleData>(

6. columnGroupHeaderLayer, new GlazedListsSortModel<SingleData>(

7. sortedList, columnPropertyAccessor, configRegistry, columnHeaderDataLayer),

8. false); // Notez le positionnement a "false" !

10. // Ajout du comparateur personnalise

11. ColumnOverrideLabelAccumulator labelAccumulator = new ColumnOverrideLabelAccumulator(columnHeaderDataLayer);

12. columnHeaderDataLayer.setConfigLabelAccumulator(labelAccumulator);

14. labelAccumulator.registerColumnOverrides(

15. 0, // Modification du comparateur de la colonne Period

16. "noComparePeriod");

17. labelAccumulator.registerColumnOverrides(

18. 3, // Modification du comparateur de la colonne Airline

19. "myCustomLabel");

21. configRegistry.registerConfigAttribute(SortConfigAttributes.SORT_COMPARATOR,

22. new NullComparator(),

23. DisplayMode.NORMAL, "noComparePeriod");

25. configRegistry.registerConfigAttribute(SortConfigAttributes.SORT_COMPARATOR,

26. // La NatTable transmet les elements de la cellule directement

27. //   d'ou un comparateur sur des Strings (les Airline sont des Strings dans SingleData)

28. new Comparator<String>() {

29. @Override

30. public int compare(String o1, String o2) {

31. return new Integer(o1.length()).compareTo(o2.length());

32. }},

33. DisplayMode.NORMAL, "myCustomLabel");

35. // Creation des en-tetes de lignes

36. // ...

Dernier point, au cas où vous ne souhaitez pas passer par les GlazedLists, vous pouvez définir votre propre implémentation de « ISortModel » et l'utiliser dans votre layer « SortHeaderLayer » au lieu d'utiliser le « GlazedListsSortModel ». Gardez cependant à l'esprit que l'utilisation des GlazedLists dans votre NatTable vous garantit des performances accrues (sans compter le gain - substantiel - en termes de nombre de lignes de code).

V - Gérer les données

V-A - Les données d'entrée

La NatTable comme nous l'avons vu repose entièrement sur un layer de plus bas niveau, le DataLayer. Ce layer est toujours construit à l'aide d'un objet qui implémente l'interface « IDataProvider ». Dans les exemples précédents, nous avons toujours utilisé un provider par défaut, le « ListDataProvider ». Ce provider permet de construire très simplement un provider à partir d'une liste d'objets et d'un autre objet de type « ReflectivePropertyColumnAccessor » construit à partir de la liste des attributs des objets contenus dans la liste. Si cette méthode est très adaptée pour des objets de type « beans », elle peut être inadéquate si vos objets ne respectent pas cette convention. Dans ce cas, la solution est de créer un nouveau « data provider » qui implémente directement l'interface « IDataProvider ». Cette interface, très simple, requiert les méthodes suivantes :

- int getColumnCount() : retourne le nombre de colonnes ;

- getRowCount() : retourne le nombre de lignes, i.e. le nombre total d'objets ;

- Object getDataValue(int columnIndex, int rowIndex) : retourne la valeur à la ligne et la colonne indiquée ;

- void setDataValue(int columnIndex, int rowIndex, Object newValue) : modifie la valeur à la ligne et la colonne indiquée.

Si vous souhaitez, par la suite, accéder directement aux lignes depuis votre DataProvider (et donc à vos objets), vous devez implémenter en outre l'interface « IRowDataProvider<T> ». Par ailleurs, si vos objets sont contenus dans une liste, sans être des « beans », vous pouvez utiliser le « ListDataProvider », en redéfinissant votre propre implémentation de « IColumnAccessor », comme le montre en partie cet exemple sur nos objets SingleData :

protected IRowDataProvider<SingleData>
getSanFranciscoDataProvider() {
return new ListDataProvider<SingleData>(getModel().getData(),
new IColumnAccessor<SingleData>() {
@Override
public Object getDataValue(SingleData rowObject,
int columnIndex) {
switch (columnIndex) {
case 0:
return rowObject.getPeriod();
// ...
}
}

@Override
public void setDataValue(SingleData rowObject,
int columnIndex, Object newValue) {
switch (columnIndex) {
case 0:
rowObject.setPeriod(// ...
break;
// ...
}
}

@Override
public int getColumnCount() {
return 20;
}
});
}

V-B - Dériver des données

V-B-1 - Créer une colonne dérivée

Il est parfois utile d'afficher dans une colonne des informations supplémentaires établies à partir d'autres colonnes. La NatTable permet de créer efficacement ce genre de colonnes en ne modifiant pas le modèle, permettant ainsi de ne pas surcharger les données chargées en mémoire. La création de ce genre de colonne passe par la définition ou le raffinement d'un objet de type « IColumnPropertyAcessor » qui va simplement créer la donnée au fur et à mesure de l'affichage.
Créez la vue DataExample.java, qui hérite de notre classe squelette NatTableView.java avec le contenu suivant :

DataExample.java

1. package com.abernard.nattable.examples;

3. import java.util.Calendar;

4. import java.util.GregorianCalendar;

5. import java.util.Locale;

7. import org.eclipse.nebula.widgets.nattable.NatTable;

8. import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;

9. import org.eclipse.nebula.widgets.nattable.data.IDataProvider;

10. import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;

11. import org.eclipse.nebula.widgets.nattable.data.ReflectiveColumnPropertyAccessor;

12. import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultGridLayer;

13. import org.eclipse.nebula.widgets.nattable.layer.ILayer;

14. import org.eclipse.swt.widgets.Composite;

16. import com.abernard.nattable.NatTableView;

17. import com.abernard.nattable.model.SanFranciscoData;

18. import com.abernard.nattable.model.SingleData;

20. /**

21. * Cet exemple montre comment manipuler les donnees contenues dans la NatTable.

22. * @author A. BERNARD

23. *

24. */

25. public class DataExample extends NatTableView {

27. private Calendar calendar = new GregorianCalendar();

28. private IColumnPropertyAccessor<SingleData> derivedColumnPropertyAccessor;

30. public DataExample() {

31. //

32. }

34. @Override

35. protected String getExampleTitle() {

36. return "Data Example";

37. }

39. @Override

40. protected NatTable createNatTable(Composite parent) {

42. IDataProvider provider = getSanFranciscoDataProvider();

44. // Pour les colonnes, le data provider des en-tetes doit prendre en compte notre colonne derivee

45. IDataProvider columnHeaderDataProvider = new IDataProvider() {

46. @Override

47. public Object getDataValue(int columnIndex, int rowIndex) {

48. if (columnIndex == 0) {

49. return "Derived date";

50. } else {

51. String property = derivedColumnPropertyAccessor.getColumnProperty(columnIndex);

52. return getModel().getPropertyNames().get(property);

53. }

54. }

56. @Override

57. public void setDataValue(int columnIndex, int rowIndex, Object newValue) {}

59. @Override

60. public int getColumnCount() {

61. return derivedColumnPropertyAccessor.getColumnCount();

62. }

64. @Override

65. public int getRowCount() {

66. return 1;

67. }

69. };

71. // Creation d'une layer "grid" par defaut

72. ILayer layer = new DefaultGridLayer(provider, columnHeaderDataProvider);

74. return new NatTable(parent, layer);

76. }

78. @Override

79. protected IRowDataProvider<SingleData> getSanFranciscoDataProvider() {

80. // Creation de l'accesseur aux attributs de nos beans

81. final IColumnPropertyAccessor<SingleData> columnPropertyAccessor =

82. new ReflectiveColumnPropertyAccessor<SingleData>(SanFranciscoData.getBeanHeaders());

84. // Ajout d'une colonne derivee

85. derivedColumnPropertyAccessor = new IColumnPropertyAccessor<SingleData>() {

87. @Override

88. public Object getDataValue(SingleData rowObject, int columnIndex) {

89. if (columnIndex == 0) {

90. // La premiere colonne est notre colonne derivee

91. int month = (int)columnPropertyAccessor.getDataValue(rowObject, 2) - 1;

92. calendar.set(Calendar.MONTH, month);

93. return calendar.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.FRENCH)

94. + " " + columnPropertyAccessor.getDataValue(rowObject, 1);

95. } else if (columnIndex < columnPropertyAccessor.getColumnCount()) {

96. // Les autres colonnes ne changent pas

97. return columnPropertyAccessor.getDataValue(rowObject, columnIndex);

98. } else {

99. return null;

100. }

101. }

103. @Override

104. public void setDataValue(SingleData rowObject, int columnIndex, Object newValue) {

105. if (columnIndex != 0) {

106. // Ne rien faire a partir de la colonne derivee

107. columnPropertyAccessor.setDataValue(rowObject, columnIndex, newValue);

108. }

109. }

111. @Override

112. public int getColumnCount() {

113. return columnPropertyAccessor.getColumnCount() + 1;

114. }

116. @Override

117. public String getColumnProperty(int columnIndex) {

118. if (columnIndex == 0) {

119. // La premiere colonne est notre colonne derivee

120. return "humanDate";

121. } else if (columnIndex < columnPropertyAccessor.getColumnCount()) {

122. return columnPropertyAccessor.getColumnProperty(columnIndex);

123. } else {

124. return null;

125. }

126. }

128. @Override

129. public int getColumnIndex(String propertyName) {

130. if ("humanDate".equals(propertyName)) {

131. // Notre colonne derivee est en premiere position

132. return 0;

133. } else {

134. return columnPropertyAccessor.getColumnIndex(propertyName);

135. }

136. }

137. };

139. IDataProvider listDataProvider = new ListDataProvider<SingleData>(getModel().getData(),

140. derivedColumnPropertyAccessor);

141. return listDataProvider;

142. }

144. }

La première étape est de définir un objet de type « IColumnPropertyAccessor », construit dans notre exemple à partir d'une implémentation par défaut (« ReflectiveColumnPropertyAccessor »). Notez que nous aurions très bien pu, comme dans le paragraphe précédent utiliser un simple « IColumnAccessor ». Cet accesseur nous permet de créer « artificiellement » une colonne en première position, créée à partir des colonnes du mois et de l'année. L'introduction de cette colonne passe par la création d'un identifiant pour cette colonne qui ne correspond évidemment pas à un attribut du modèle, que nous nommons arbitrairement « humanDate ».
Il faut ensuite créer un data provider particulier pour les en-têtes de colonnes, qui va afficher le texte idoine en en-tête de notre colonne dérivée. Pour les autres colonnes, on se sert simplement de la transposition attribut-texte de notre modèle. On peut observer le résultat dans notre vue nouvellement créée :

Colonne dérivée

V-B-2 - Créer une ligne dérivée

La NatTable permet de mettre en place, en dernière position, une ligne qui va donner un résumé des lignes précédentes, charge au développeur de choisir ce qu'il veut afficher : la somme des éléments, leur moyenne, le nombre moyen de lettres par mot, etc. Encore une fois, cela passera par un layer, nommé « SummaryRowLayer ». Pour notre exemple, nous allons reprendre le code de l'exemple précédent en reconstruisant un GridLayer personnalisé qui nous permettra d'intégrer la ligne de sommaire. Celle-ci affichera trois informations : le nombre total d'atterrissages, et le poids total moyen en lb et en Kg. Modifiez le code de l'exemple « DataExample » comme suit :

DataExample.java

1. // ...

2. public class DataExample extends NatTableView {

4. // ...

5. private IRowDataProvider<SingleData> provider;

7. // ...

9. @Override

10. protected NatTable createNatTable(Composite parent) {

11. provider = getSanFranciscoDataProvider();

12. // Pour les colonnes, le data provider des en-tetes doit prendre en compte notre colonne derivee

13. IDataProvider columnHeaderDataProvider = // ...

15. // Creation de la pile de layers centrale

16. IConfigRegistry configRegistry = new ConfigRegistry();

17. IUniqueIndexLayer dataLayer = new DataLayer(provider);

18. // Creation de la ligne "sommaire" au travers d'un layer

19. IUniqueIndexLayer summaryRowLayer = new SummaryRowLayer(dataLayer, configRegistry, false);

20. SelectionLayer selectionLayer = new SelectionLayer(summaryRowLayer);

21. ViewportLayer bodyLayer = new ViewportLayer(selectionLayer);

23. // Creation des colonnes

24. DefaultColumnHeaderDataLayer columnHeaderDataLayer = new DefaultColumnHeaderDataLayer(columnHeaderDataProvider);

25. ColumnHeaderLayer columnHeaderLayer = new ColumnHeaderLayer(columnHeaderDataLayer,

26. bodyLayer, selectionLayer);

28. // Creation des lignes

29. DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(provider);

30. ILayer rowHeaderLayer = new RowHeaderLayer(new DefaultRowHeaderDataLayer(rowHeaderDataProvider),

31. bodyLayer, selectionLayer);

33. // Creation du coin superieur

34. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider,

35. rowHeaderDataProvider);

36. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer,

37. columnHeaderLayer);

39. // Creation du layer global

40. GridLayer gridLayer = new GridLayer(bodyLayer, columnHeaderLayer, rowHeaderLayer, cornerLayer);

42. NatTable natTable = new NatTable(parent, gridLayer, false);

44. // Configuration de la ligne "sommaire"

45. natTable.addConfiguration(new LandingsSummaryRowConfig(provider));

46. natTable.addConfiguration(new DefaultNatTableStyleConfiguration());

47. natTable.setConfigRegistry(configRegistry);

48. natTable.configure();

50. return natTable;

52. }

54. /**

55. * Configuration particuliere pour notre NatTable de la ligne "sommaire"

56. * @author A. BERNARD

57. *

58. */

59. private class LandingsSummaryRowConfig extends DefaultSummaryRowConfiguration  {

61. private IDataProvider dataProvider;

63. public LandingsSummaryRowConfig(IDataProvider provider) {

64. this.dataProvider = provider;

66. }

68. @Override

69. public void addSummaryProviderConfig(IConfigRegistry configRegistry) {

71. // Calcul de la somme sur la colonne 16 (nombre d'atterrissages)

72. configRegistry.registerConfigAttribute(

73. SummaryRowConfigAttributes.SUMMARY_PROVIDER,

74. new SummationSummaryProvider(dataProvider), // Somme par defaut

75. DisplayMode.NORMAL,

76. SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + 15);

78. // Calcul de la moyenne sur la colonne 17 (poids moyen en lb)

79. configRegistry.registerConfigAttribute(

80. SummaryRowConfigAttributes.SUMMARY_PROVIDER,

81. new LandingsAverageProvider(17),

82. DisplayMode.NORMAL,

83. SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + 17);

85. // Calcul de la moyenne sur la colonne 19 (poids moyen en Kq)

86. configRegistry.registerConfigAttribute(

87. SummaryRowConfigAttributes.SUMMARY_PROVIDER,

88. new LandingsAverageProvider(19),

89. DisplayMode.NORMAL,

90. SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + 19);

91. }

95. }

97. /**

98. * Calcul de la moyenne des elements d'une colonne.

99. * @author A. BERNARD

100. *

101. */

102. private class LandingsAverageProvider implements ISummaryProvider {

104. private int index;

106. /**

107. * Constructeur

108. * @param colIndex l'index de la colonne a moyenner

109. */

110. public LandingsAverageProvider(int colIndex) {

111. this.index = colIndex;

112. }

114. @Override

115. public Object summarize(int arg0) {

116. long total = 0;

117. int rowCount = provider.getRowCount();

118. for (int rowIndex = 0; rowIndex < rowCount; rowIndex++) {

119. Object dataValue = provider.getDataValue(index, rowIndex);

120. total += Integer.parseInt(dataValue.toString());

121. }

122. return "Average: " + (total / rowCount);

123. }

124. }

126. @Override

127. protected IRowDataProvider<SingleData> getSanFranciscoDataProvider() {

128. // ...

129. }

130. }

Plusieurs choses sont à noter, outre l'introduction du layer « SummaryRowLayer ». Premièrement, nous créons une configuration dérivée de la « DefaultSummaryRowConfiguration ». Cette configuration crée le style par défaut, ainsi que la valeur par défaut pour les colonnes non impactées, à savoir « … ». Puis nous enregistrons pour chaque colonne souhaitée le type d'opération à produire sur les données. Par exemple pour les atterrissages, nous utilisons un objet « SummationSummaryProvider » par défaut, qui est configuré pour effectuer la somme des éléments sur toutes les lignes. Pour la moyenne nous créons notre propre objet « LandingsAverageProvider ». Notez que l'indication de la colonne impactée se fait via l'instruction « SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX + 19 », où 19 est l'index de la colonne. Si vous souhaitez appliquer une configuration sur toutes les colonnes, il suffit de supprimer l'index pour ne garder que « SummaryRowLayer.DEFAULT_SUMMARY_COLUMN_CONFIG_LABEL_PREFIX ». On peut visualiser le résultat en dernière position de la NatTable :

Ligne de sommaire sur des colonnes

V-C - Filtrer les données

Autre fonctionnalité classique, le filtre sur les données. Encore une fois, la NatTable va tirer toute sa puissance des GlazedLists et proposer à l'utilisateur (ainsi qu'au développeur !) une manière puissante de filtrer les données. Pour cela, nous allons dans un premier temps utiliser une FilterList du projet GlazedLists, tout comme précédemment nous avons utilisé une SortedList. Il faut donc modifier en conséquence notre méthode getSanFranciscoDataProvider. Puis il faut mettre en place les outils de filtre. Cela se fait en deux étapes, en mettant en place tout d'abord un layer sur les en-têtes de colonnes qui va activer le filtrage de type « FilterRowHeaderComposite ». C'est cet élément qui va tirer parti des fonctionnalités de la FilterList (notez que vous pouvez aussi passer par une implémentation « maison » de l'interface « IFilterStrategy »).
Enfin, il faut configurer la NatTable avec nos fonctions de filtre personnalisées, si besoin. Dans notre cas, cela va en deux parties. La première configuration permet de forcer l'affichage de l'icône de filtre même si aucun filtre n'est actif. La deuxième permet de mettre en place une liste déroulante de choix sur le type de vol (« Domestic » ou « International ») ce qui permet de faciliter le choix de l'utilisateur.
Le fragment de code ci-dessous montre les éléments à modifier dans notre classe « DataExample » afin d'activer ces fonctionnalités :

DataExample.java

1. // ...

2. public class DataExample extends NatTableView {

4. // ...

6. @Override

7. protected NatTable createNatTable(Composite parent) {

8. // ...

10. // Creation des colonnes

11. // ...

12. // Ajout des filtres

13. FilterRowHeaderComposite<SingleData> filterHeaderLayer = new FilterRowHeaderComposite<>(

14. new DefaultGlazedListsFilterStrategy<SingleData>(filterList,

15. derivedColumnPropertyAccessor, configRegistry),

16. columnHeaderLayer, columnHeaderDataProvider, configRegistry);

18. // Creation des lignes

19. // ...

21. // Creation du coin superieur

22. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider,

23. rowHeaderDataProvider);

24. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider), rowHeaderLayer,

25. filterHeaderLayer);

27. // Creation du layer global

28. GridLayer gridLayer = new GridLayer(bodyLayer, filterHeaderLayer, rowHeaderLayer, cornerLayer);

30. // ...

31. // Configuration des filtres

32. natTable.addConfiguration(new LandingsFilterConfiguration());

33. natTable.setConfigRegistry(configRegistry);

34. natTable.configure();

36. return natTable;

38. }

40. /**

41. * Configuration des filtres sur les lignes specifiques a nos donnees.

42. * @author A. BERNARD

43. *

44. */

45. public class LandingsFilterConfiguration extends AbstractRegistryConfiguration  {

47. @Override

48. public void configureRegistry(IConfigRegistry configRegistry) {

49. // Affichage de l'icone de tri meme si inactif

50. configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER,

51. new FilterRowPainter(new FilterIconPainter(GUIHelper.getImage("filter"))),

52. DisplayMode.NORMAL,

53. GridRegion.FILTER_ROW);

55. // Filtre par liste deroulante sur le type de vol

56. configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,

57. new ComboBoxCellEditor(Arrays.asList("Domestic", "International")),

58. DisplayMode.NORMAL,

59. FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 7);

61. // Application du style sur le background de la ligne de filtre

62. final Style rowStyle = new Style();

63. rowStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper

64. .getColor(197, 212, 231));

65. configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, rowStyle,

66. DisplayMode.NORMAL, GridRegion.FILTER_ROW);

67. }

68. }

72. @Override

73. protected IRowDataProvider<SingleData> getSanFranciscoDataProvider() {

74. // Creation de l'accesseur aux attributs de nos beans

75. final IColumnPropertyAccessor<SingleData> columnPropertyAccessor =

76. new ReflectiveColumnPropertyAccessor<SingleData>(SanFranciscoData.getBeanHeaders());

78. // Ajout d'une colonne derivee

79. derivedColumnPropertyAccessor = new IColumnPropertyAccessor<SingleData>() {

80. //...

81. };

83. EventList<SingleData> model = GlazedLists.eventList(getModel().getData());

84. filterList = new FilterList<>(model);

85. IRowDataProvider<SingleData> listDataProvider = new ListDataProvider<>(filterList,

86. derivedColumnPropertyAccessor);

87. return listDataProvider;

88. }

89. }

Lorsqu'on lance l'application, on observe bien la mise en place des filtres sur nos colonnes. Dans la capture d'écran, on a filtré les données correspondant aux atterrissages ayant eu lieu au mois de décembre.

Filtres sur les colonnes

V-D - Grouper les données par valeur

La NatTable permet aussi de grouper les données par valeur de manière à les représenter sous forme d'arbre dans la NatTable. Ce comportement fait penser à la fonctionnalité de tableau croisé dynamique dans Excel. Elle peut être utile sur les jeux volumineux de données où certaines peuvent être regroupées entre elles. Pour cela, il suffit d'utiliser un layer de données de type « GroupyByDataLayer », couplé à un modèle de données « GroupByModel ». Il faut ensuite configurer le « GridLayer » final en y ajoutant un composite d'affichage des groupements effectués. Enfin, il faut ajouter un menu contextuel sur le composite précédent afin de pouvoir désactiver les groupements. L'extrait suivant montre les modifications à effectuer sur notre classe « DataExample » :

DataExample.java

1. // ...

2. public class DataExample extends NatTableView {

4. // ...

6. @Override

7. protected NatTable createNatTable(Composite parent) {

8. // ...

10. // Creation de la pile de layers centrale

11. IConfigRegistry configRegistry = new ConfigRegistry();

12. // Modele de groupement des donnees

13. GroupByModel groupByModel = new GroupByModel();

14. GroupByDataLayer<SingleData> bodyDataLayer = new GroupByDataLayer<>(groupByModel, filterList,

15. derivedColumnPropertyAccessor);

17. // Creation de la ligne "sommaire" au travers d'un layer

18. IUniqueIndexLayer summaryRowLayer = new SummaryRowLayer(bodyDataLayer, configRegistry, false);

19. SelectionLayer selectionLayer = new SelectionLayer(summaryRowLayer);

20. TreeLayer treeLayer = new TreeLayer(selectionLayer, bodyDataLayer.getTreeRowModel());

21. ViewportLayer bodyLayer = new ViewportLayer(treeLayer);

23. // Creation des colonnes

24. // ...

26. // Creation du layer global

27. GridLayer gridLayer = new GridLayer(bodyLayer, filterHeaderLayer, rowHeaderLayer, cornerLayer,

28. false);

29. CompositeLayer compositeGridLayer = new CompositeLayer(1, 2);

30. final GroupByHeaderLayer groupByHeaderLayer = new GroupByHeaderLayer(groupByModel, gridLayer,

31. columnHeaderDataProvider);

32. compositeGridLayer.setChildLayer(GroupByHeaderLayer.GROUP_BY_REGION, groupByHeaderLayer, 0, 0);

33. compositeGridLayer.setChildLayer("Grid", gridLayer, 0, 1);

36. NatTable natTable = new NatTable(parent, compositeGridLayer, false);

38. // ...

39. // Configuration du groupement de donnees

40. natTable.addConfiguration(new GroupByHeaderMenuConfiguration(natTable, groupByHeaderLayer));

41. natTable.setConfigRegistry(configRegistry);

42. natTable.configure();

44. return natTable;

46. }

48. // ...

49. }

Vous pouvez sans plus attendre visualiser le résultat. Faites glisser les en-têtes des colonnes vers le bandeau juste au-dessus de la NatTable pour activer le groupement. Par un clic droit sur ce même bandeau, vous pouvez désactiver le groupement. Dans l'exemple ci-dessous, nous avons groupé les données successivement par année, par type de vol (domestique ou international), puis par mois.

Groupement des données

V-E - Récupérer la sélection

La NatTable est un provider de sélection, au sens « JFace » du terme c'est-à-dire qu'elle est capable de renvoyer les éléments sélectionnés sous la forme d'une « ISelection », permettant ainsi d'utiliser les mécanismes classiques et donc de l'enregistrer en tant que provider pour la plateforme Eclipse dans le cas d'une intégration dans une application RCP par exemple. Ainsi si vous intégrez le code ci-dessous à notre exemple, les éléments sélectionnés seront affichés dans la console :

DataExample.java

1. // ...

2. natTable.configure();

4. // Creation des evenements de selection

5. ISelectionProvider selectionProvider = new RowSelectionProvider<>(selectionLayer, provider, false);

6. selectionProvider.addSelectionChangedListener(new ISelectionChangedListener() {

7. @Override

8. public void selectionChanged(SelectionChangedEvent event) {

9. IStructuredSelection selection = (IStructuredSelection) event.getSelection();

10. Iterator<?> it = selection.iterator();

11. while (it.hasNext()) {

12. SingleData data = (SingleData)it.next();

13. System.out.println("Selection of data: " + data.getManufacturer()

14. + data.getAircraftModel());

15. }

16. }

18. });

20. return natTable;

21. // ...

Le dernier argument du constructeur, booléen, indique - s'il est à vrai - que l'on souhaite uniquement être informé des sélections sur des lignes complètes.

V-F - Grouper des lignes

De même que nous avons groupé des colonnes, il est aussi possible de grouper des lignes. Malheureusement, le fonctionnement est moins souple et impose des groupements de lignes fixés à la création de la NatTable. De plus, si une ligne n'est pas ajoutée à un groupe elle n'apparaît pas dans la table. L'exemple ci-dessous crée des groupements de 50 lignes dans notre exemple « DataExample » :

DataExample.java

1. // ...

3. @Override

4. protected NatTable createNatTable(Composite parent) {

5. // ...

7. // Creation de la ligne "sommaire" au travers d'un layer

8. IUniqueIndexLayer summaryRowLayer = new SummaryRowLayer(bodyDataLayer, configRegistry, false);

10. RowHideShowLayer rowHideShowLayer = new RowHideShowLayer(summaryRowLayer);

11. RowGroupModel<SingleData> rowGroupModel = new RowGroupModel<>();

12. rowGroupModel.setDataProvider(provider);

13. RowGroupExpandCollapseLayer<SingleData> rowExpandCollapseLayer =

14. new RowGroupExpandCollapseLayer<>(rowHideShowLayer, rowGroupModel);

16. SelectionLayer selectionLayer = new SelectionLayer(rowExpandCollapseLayer);

17. // ...

19. // Creation des lignes

20. DefaultRowHeaderDataProvider rowHeaderDataProvider = new DefaultRowHeaderDataProvider(provider);

21. RowHeaderLayer rowHeaderLayer = new RowHeaderLayer(new DefaultRowHeaderDataLayer(rowHeaderDataProvider),

22. bodyLayer, selectionLayer);

23. rowHeaderLayer.addConfiguration(new DefaultRowGroupHeaderLayerConfiguration<>(rowGroupModel));

25. RowGroupHeaderLayer<SingleData> rowGroupHeaderLayer = new RowGroupHeaderLayer<>(rowHeaderLayer,

26. selectionLayer, rowGroupModel);

27. rowGroupHeaderLayer.setColumnWidth(15);

29. // Creation de groupes de 50 lignes

30. int totalRows = provider.getRowCount();

31. int numberGroups = totalRows / 50;

32. int firstLine = 0;

33. for (int i = 0; i < numberGroups; i++) {

34. firstLine = i*50;

35. RowGroup<SingleData> rowGroup = new RowGroup<>(rowGroupModel, "Group " + i, true);

36. rowGroup.addStaticMemberRow(provider.getRowObject(firstLine));

37. for (int j = 0; j < 50; j++) {

38. rowGroup.addMemberRow(provider.getRowObject(firstLine+j));

39. }

40. rowGroupModel.addRowGroup(rowGroup);

41. }

42. firstLine = numberGroups*50;

43. for (int k = firstLine; k < totalRows; k++) {

44. RowGroup<SingleData> rowGroup = new RowGroup<>(rowGroupModel, "Group " + numberGroups, true);

45. rowGroup.addStaticMemberRow(provider.getRowObject(firstLine));

46. for (int j = 0; j < totalRows-firstLine; j++) {

47. rowGroup.addMemberRow(provider.getRowObject(firstLine+j));

48. }

49. rowGroupModel.addRowGroup(rowGroup);

50. }

54. // Creation du coin superieur

55. DefaultCornerDataProvider cornerDataProvider = new DefaultCornerDataProvider(columnHeaderDataProvider,

56. rowHeaderDataProvider);

57. CornerLayer cornerLayer = new CornerLayer(new DataLayer(cornerDataProvider),

58. rowGroupHeaderLayer, filterHeaderLayer);

60. // Creation du layer global

61. GridLayer gridLayer = new GridLayer(bodyLayer, filterHeaderLayer, rowGroupHeaderLayer,

62. cornerLayer, false);

63. // ...

66. return natTable;

67. }

69. }

On peut observer le résultat en lançant notre application. Notez que l'on ne voit pas la ligne de « sommaire ». D'autre part, le groupement de lignes est d'un intérêt limité dans notre exemple !

Groupement de lignes

VI - Configurer la NatTable

VI-A - Comment fonctionne la configuration

Dans la plupart de nos exemples, nous avons contribué à la configuration de la NatTable par plusieurs méthodes. Pour cela nous avons appelé la méthode « natTable.addConfiguration(IConfiguration) », puis réimplémenté la méthode « configureRegistry(IConfigRegistry configRegistry) ». Au sein de cette méthode, nous avons appelé, chaque fois que c'était nécessaire, la méthode « configRegistry.registerConfigAttribute() ».
Pour rappel, voici un petit exemple de ce qui a été fait pour filtrer les atterrissages par type de vol dans l'exemple « DataExample » :

/**
* Configuration des filtres sur les lignes specifiques a nos donnees.
* @author A. BERNARD
*
*/
public class LandingsFilterConfiguration extends AbstractRegistryConfiguration  {

@Override
public void configureRegistry(IConfigRegistry configRegistry) {
// Affichage de l'icone de tri meme si inactif
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER,
new FilterRowPainter(new FilterIconPainter(GUIHelper.getImage("filter"))),
DisplayMode.NORMAL,
GridRegion.FILTER_ROW);

// Filtre par liste deroulante sur le type de vol
configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,
new ComboBoxCellEditor(Arrays.asList("Domestic", "International")),
DisplayMode.NORMAL,
FilterRowDataLayer.FILTER_ROW_COLUMN_LABEL_PREFIX + 7);

// Application du style sur le background de la ligne de filtre
final Style rowStyle = new Style();
rowStyle.setAttributeValue(CellStyleAttributes.BACKGROUND_COLOR, GUIHelper
.getColor(197, 212, 231));
configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_STYLE, rowStyle,
DisplayMode.NORMAL, GridRegion.FILTER_ROW);
}
}

Vous vous êtes sans doute demandé quels concepts se cachaient derrière ces configurations, il est temps de les détailler ! Chaque configuration implémente l'interface « IConfiguration », qui permet d'accéder à deux objets centraux de la configuration de la NatTable. Tout d'abord, le « ConfigRegistry » est un objet qui contient les informations de configuration suivantes :

- le style des cellules ;

- l'édition des cellules ;

- les comparateurs pour le tri ;

- et toute autre information arbitraire souhaitée.

Cet objet possède un équivalent pour les bindings clavier et souris liés aux configurations, le « UiBindingRegistry ». Ce registre est accessible, comme le précédent, via une méthode idoine de l'interface « IConfiguration » : « configureUiBindings(UiBindingRegistry) ». Chaque layer de la NatTable (et donc la NatTable elle-même) définit une configuration par défaut qui peut être désactivée par un booléen « autoconfigure » dans le constructeur :

final NatTable natTable = new NatTable(parent, compositeGridLayer, false);

Le diagramme UML simplifié ci-dessous montre l'architecture de la configuration de la NatTable :

Diagramme UML de la configuration

Dans ce paragraphe, nous verrons comment appliquer des styles aux cellules et comment activer l'édition.

VI-B - Appliquer un style aux cellules

Le moyen le plus flexible pour appliquer un style aux cellules est de passer par un « CellLabel ». Il s'agit tout simplement d'une chaîne de caractères qui va être utilisée pour identifier une cellule dans la NatTable. Par exemple, appliquer un style particulier à des cellules qui contiennent des erreurs, ou bien les afficher comme des cases à cocher. Pour chaque style que nous définirons, nous devrons indiquer à quel état de la cellule ce style s'applique. Pour cela, nous utiliserons l'interface « DisplayMode », qui contient trois éléments :

- « Normal » : l'état standard d'une cellule ;

- « Select » : lorsqu'une cellule est sélectionnée ;

- « Edit » : lorsqu'une cellule est en cours d'édition.

Pour attacher un « label » à une cellule, il est nécessaire de l'ajouter au layer concerné, par la méthode « ILayer.setConfigLabelAccumulator(ILabelAccumulator) ». L'API de la NatTable nous propose deux implémentations de l'interface « ILabelAccumulator » qui devraient suffire à la plupart des utilisations :

- CellOverrideLabelAccumulator : applique les labels à des cellules contenant une valeur spécifique ;

- ColumnOverrideLabelAccumulator : applique les labels à toutes les cellules d'une colonne.

Bien évidemment, vous pouvez définir votre propre implémentation de cette interface.
Sans plus attendre, voyons cela sur un exemple simple : créez une nouvelle vue pour ces exemples appelée « CellsExample.java » et affectez-lui le code suivant :

CellsExample.java

1. package com.abernard.nattable.examples;

3. import org.eclipse.nebula.widgets.nattable.NatTable;

4. import org.eclipse.nebula.widgets.nattable.config.AbstractRegistryConfiguration;

5. import org.eclipse.nebula.widgets.nattable.config.CellConfigAttributes;

6. import org.eclipse.nebula.widgets.nattable.config.DefaultNatTableStyleConfiguration;

7. import org.eclipse.nebula.widgets.nattable.config.IConfigRegistry;

8. import org.eclipse.nebula.widgets.nattable.data.IRowDataProvider;

9. import org.eclipse.nebula.widgets.nattable.data.convert.DisplayConverter;

10. import org.eclipse.nebula.widgets.nattable.data.convert.IDisplayConverter;

11. import org.eclipse.nebula.widgets.nattable.grid.data.DefaultColumnHeaderDataProvider;

12. import org.eclipse.nebula.widgets.nattable.grid.layer.DefaultGridLayer;

13. import org.eclipse.nebula.widgets.nattable.layer.DataLayer;

14. import org.eclipse.nebula.widgets.nattable.layer.cell.ColumnOverrideLabelAccumulator;

15. import org.eclipse.nebula.widgets.nattable.painter.cell.CheckBoxPainter;

16. import org.eclipse.nebula.widgets.nattable.style.DisplayMode;

17. import org.eclipse.swt.widgets.Composite;

19. import com.abernard.nattable.NatTableView;

20. import com.abernard.nattable.model.SanFranciscoData;

21. import com.abernard.nattable.model.SingleData;

23. /**

24. * Cet exemple montre comment configurer les cellules (style, edition...)

25. * @author A. BERNARD

26. *

27. */

28. public class CellsExample extends NatTableView {

30. private static final String LABEL_AC_TYPE = "ColumnLandingAircraftType";

33. public CellsExample() {

35. }

37. @Override

38. protected String getExampleTitle() {

39. return "Cells Example";

40. }

42. @Override

43. protected NatTable createNatTable(Composite parent) {

44. IRowDataProvider<SingleData> dataProvider = getSanFranciscoDataProvider();

45. DefaultGridLayer gridLayer = new DefaultGridLayer(dataProvider,

46. new DefaultColumnHeaderDataProvider(SanFranciscoData.getBeanHeaders(),

47. getModel().getPropertyNames()));

49. DataLayer bodyDataLayer = (DataLayer) gridLayer.getBodyDataLayer();

51. ColumnOverrideLabelAccumulator columnLabelAccumulator = new ColumnOverrideLabelAccumulator(bodyDataLayer);

52. bodyDataLayer.setConfigLabelAccumulator(columnLabelAccumulator);

53. // Enregistrement d'un nouveau label pour la colonne sur le type de vol ("Landing Aircraft Type")

54. columnLabelAccumulator.registerColumnOverrides(9, LABEL_AC_TYPE);

56. NatTable natTable = new NatTable(parent, gridLayer, false);

57. natTable.addConfiguration(new DefaultNatTableStyleConfiguration());

58. // Creation du nouveau style pour les cellules de la colonne "Landing Aircraft Type"

59. natTable.addConfiguration(new AircraftTypeColumnConfiguration());

60. natTable.configure();

62. return natTable;

64. }

67. /**

68. * Cette configuration met en place la configuration de style sur la colonne

69. * "Landing Aircraft Type".

70. * @author A. BERNARD

71. *

72. */

73. private class AircraftTypeColumnConfiguration extends AbstractRegistryConfiguration {

75. @Override

76. public void configureRegistry(IConfigRegistry configRegistry) {

77. // Le rendu de la cellule doit etre de type "case a cocher"

78. configRegistry.registerConfigAttribute(CellConfigAttributes.CELL_PAINTER,

79. new CheckBoxPainter(), DisplayMode.NORMAL, LABEL_AC_TYPE);

81. // Pour utiliser une case a cocher, il faut assurer la conversion

82. //    entre les donnees du modele et un booleen.

83. configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER,

84. getAircraftTypeBooleanConverter(), DisplayMode.NORMAL, LABEL_AC_TYPE);

85. }

87. /**

88. * Construit un element permettant de convertir la donnee du modele vers

89. * le composant graphique utilise pour le rendu de la cellule.

90. * @return

91. */

92. private IDisplayConverter getAircraftTypeBooleanConverter() {

93. return new DisplayConverter() {

94. @Override

95. public Object canonicalToDisplayValue(Object canonicalValue) {

96. if (canonicalValue instanceof String) {

97. return ((String)canonicalValue).equals("Passenger");

98. }

99. return null;

100. }

102. @Override

103. public Object displayToCanonicalValue(Object displayValue) {

104. Boolean displayBoolean = Boolean.valueOf(displayValue.toString());

105. return displayBoolean ? "Passenger" : "Freighter";

106. }

108. };

109. }

110. }

112. }

Cet exemple affiche le contenu de la colonne « Landing Aircraft Type » sous la forme d'une case à cocher. Pour cela, nous enregistrons tout d'abord un label, « LABEL_AC_TYPE » sur la colonne. Puis nous configurons la NatTable pour prendre en compte cette configuration, qui définit la manière dont la cellule est affichée (une case à cocher) et la manière dont on convertit la donnée de la case à cocher (qui est un booléen) vers la donnée du modèle (dans notre exemple une chaîne de caractères) et vice-versa. On peut visualiser le résultat en démarrant notre exemple :

Style sur une cellule

La méthode « registerConfigAttribute », en plus du label et de l'objet « DisplayMode » dont nous avons parlé dans le paragraphe précédent, requiert deux attributs : un de type « ConfigAttributes<ICellPainter> » et l'autre de type « ICellPainter ».
Il existe différents ConfigAttributes prédéfinis dont voici la liste et une courte description :

- « BACKGROUND_COLOR » : pour la couleur de fond ;

- « FOREGROUND_COLOR » : pour la couleur d'avant-plan ;

- « HORIZONTAL_ALIGNMENT » : pour le type d'alignement horizontal ;

- « VERTICAL_ALIGNMENT » : pour le type d'alignement vertical ;

- « FONT » : pour la police utilisée dans la cellule ;

- « BORDER_STYLE » : pour le type de bordure de la cellule ;

- « IMAGE » : pour créer une image (par exemple une case à cocher !) ;

- « GRADIENT_BACKGROUND_COLOR » : pour créer un gradient de couleur pour l'arrière-plan (utilisé seulement par le « GradientBackgroundPainter ») ;

- « GRADIENT_FOREGROUND_COLOR » : pour créer un gradient de couleur pour l'avant-plan (utilisé seulement par le « GradientBackgroundPainter ») ;

- « PASSWORD_ECHO_CHAR » : pour créer un rendu de type « mot de passe » (utilisé seulement par le « PasswordTextPainter » et le « PasswordCellEditor »).

Ces attributs sont liés à l'objet utilisé pour créer le rendu de la cellule. Pour cela les éléments utilisés implémentent l'interface « ICellPainter » et celui utilisé par défaut est un « TextPainter ». D'autres rendus sont disponibles directement, dont nous donnons une liste ici :

- « TextPainter » : rendu le plus simple, textuel ;

- « VerticalTextPainter » : rendu sous forme de texte vertical ;

- « PasswordTextPainter » : rendu textuel pour mots de passe, avec des caractères spéciaux ;

- « LineBorderDecorator » : crée une bordure sur la cellule ;

- « PaddingDecorator » : crée un « padding » (marge interne) sur la cellule ;

- « BeveledBorderDecorator » : rendu sous la forme d'un bouton ;

- « ImagePainter » : dessine une image dans la cellule ;

- « BackgroundImagePainter » : dessine une image en fond de la cellule ;

- « GradientBackgroundPainter » : dessine un gradient dans l'arrière-plan.

Vous trouverez plus d'informations sur le site officiel du projet ainsi que dans les exemples, rubrique « Styling ». Ci-dessous, un exemple de ce qu'on peut obtenir pour avoir des cellules entièrement personnalisées, dont la mise en place est décrite sur le site officiel.

Un exemple de style personnalisé

VI-C - Éditer les cellules

L'édition des cellules fonctionne sensiblement sur le même principe que la mise en place du style. Par exemple, mettons en place l'édition sur notre colonne « Landing Aircraft Type » où nous avons déjà mis en place la case à cocher. Modifiez la classe « CellsExample.java » comme suit :

CellsExample.java

1. // ...

3. private class AircraftTypeColumnConfiguration extends AbstractRegistryConfiguration {

5. @Override

6. public void configureRegistry(IConfigRegistry configRegistry) {

7. // ...

9. configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE,

10. new IEditableRule() {

11. @Override

12. public boolean isEditable(int columnIndex, int rowIndex) {

13. // Seule la colonne "Landing Aircraft Type" est editable

14. return (columnIndex == 9);

15. }

17. @Override

18. public boolean isEditable(ILayerCell cell, IConfigRegistry configRegistry) {

19. return (cell.getColumnIndex() == 9);

20. }

21. });

23. // Creation de l'editeur sur la cellule

24. configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,

25. new CheckBoxCellEditor(), DisplayMode.EDIT, LABEL_AC_TYPE);

26. }

28. // ...

Nous pouvons ensuite visualiser le résultat et constater que nous pouvons modifier la valeur de la cellule.

Ici le mode d'édition est relativement simple, mais la NatTable permet de mettre en place des mécanismes bien plus complexes que nous allons rapidement passer en revue. Tout d'abord, la première étape pour rendre une cellule éditable est d'enregistrer dans le registre de configuration une « IEditableRule ». Dans notre cas, nous avons autorisé seulement l'édition sur la colonne « Landing Aircraft Type ». L'interface « IEditableRule » propose deux implémentations par défaut : « ALWAYS_EDITABLE » ou « NEVER_EDITABLE ».
Nous définissons ensuite l'éditeur à utiliser dans la cellule. La NatTable propose une série d'éditeurs dont voici la liste avec le rendu associé à utiliser :

- « TextCellEditor » - « TextPainter » : pour éditer le texte tout simplement ;

- « CheckBoxCellEditor » - « CheckBoxPainter » : pour afficher une case à cocher ;

- « ComboBoxCellEditor » - « ComboBoxPainter » : pour une édition via une liste déroulante ;

- « PasswordCellEditor » - « PasswordTextPainter » : pour une édition d'un mot de passe.

Notez que le « TextCellEditor » est l'éditeur par défaut. Par exemple, si vous utilisez la règle « IEditableRule.ALWAYS_EDITABLE » dans notre exemple, vous pourrez éditer le contenu de chacune des cellules sans autre manipulation supplémentaire pour l'affichage.

Si vous le faites, vous aurez par contre des exceptions en cascade à chaque édition !

Exception lors de l'édition

En effet, la NatTable essaye d'affecter une valeur texte à des éléments qui ne le sont pas (des années par exemple qui sont des entiers dans notre modèle) ! Il est nécessaire de convertir les données vers le format voulu. Pour cela la NatTable met à disposition un ensemble de convertisseurs de données dont nous donnons la liste :

- « DefaultDisplayConverter » : String <-> String ;

- « DefaultCharacterDisplayConverter » : caractère <-> String ;

- « DefaultBooleanDisplayConverter » : boolean <-> String ;

- « DefaultByteDisplayConverter » : byte <-> String ;

- « DefaultShortDisplayConverter » : short <-> String ;

- « DefaultIntegerDisplayConverter » : int <-> String ;

- « DefaultLongDisplayConverter » : long <-> String ;

- « DefaultFloatDisplayConverter » : float <-> String ;

- « DefaultDoubleDisplayConverter » : double <-> String ;

- « DefaultBigIntegerDisplayConverter » : BigInteger <-> String ;

- « DefaultBigDecimalDisplayConverter » : BigDecimal <-> String ;

- « DefaultDateDisplayConverter » : date <-> String (utilise le format local de date) ;

- « PercentageDisplayConverter » : double <-> String (par exemple « 23 % » devient 0.23).

Évidemment, rien ne vous empêche de créer votre propre convertisseur, en étendant la classe par défaut « DisplayConverter », comme nous l'avons fait précédemment pour convertir le type de vol (« Passenger » ou « Freighter ») en booléen. Pour pouvoir traiter les erreurs de conversion, il est nécessaire de déclencher une exception de type « ConversionFailedException ».

De la même manière, les données doivent être validées avant d'être enregistrées dans le modèle. Pour cela, la NatTable vous permet d'enregistrer la stratégie de validation en étendant la classe « DataValidator ». Si la validation échoue, il est nécessaire de déclencher une exception de type « ValidationFailedException ».
Si une des deux exceptions citées ci-dessus intervient, il est nécessaire de donner un retour à l'utilisateur. Pour cela, encore une fois, la NatTable propose différentes stratégies, à vous de choisir celle qui convient le mieux :

- « LoggingErrorHandling » : stratégie par défaut, crée simplement un log de l'erreur ;

- « DiscardValueErrorHandling » : ferme l'éditeur de valeur ouvert sans mettre à jour le modèle ;

- « DialogErrorHandling » : permet via une boîte de dialogue d'annuler la valeur entrée ou de la modifier. L'annulation ferme l'éditeur sans mettre à jour le modèle, sinon l'éditeur est laissé ouvert pour permettre la modification ;

- « RenderErrorHandling » : utilisé pour le rendu sur les valeurs entrées directement dans la cellule, sans éditeur. Affiche la valeur en rouge dans le cas d'une erreur de conversion/validation.

Enfin, on peut afficher une décoration sur la cellule qui affiche un tooltip à propos de l'erreur lorsqu'elle intervient. Vous trouverez plus d'informations sur l'édition et les concepts associés sur la page idoine du site officiel.

Sans plus attendre, mettons en œuvre ces concepts sur notre NatTable ! Pour cela, nous allons simplement mettre en place un mécanisme d'édition de l'année. C'est un entier sur quatre chiffres, que nous souhaitons compris entre 2005 et 2013, inclus. La première étape est de créer un « label » sur notre colonne « Year ». Puis nous rendons la colonne « Year » éditable dans notre règle d'édition. Enfin nous créons l'éditeur, définissons les stratégies de validation et de conversion et d'affichage des erreurs, ainsi que la décoration.

CellsExample.java

1. // ...

2. public class CellsExample extends NatTableView {

4. private static final String LABEL_AC_TYPE = "ColumnLandingAircraftType";

5. private static final String LABEL_PERIOD =    "ColumnLabelPeriod";

7. // ...

9. @Override

10. protected NatTable createNatTable(Composite parent) {

11. // ...

12. // Enregistrement d'un label pour la colonne "Year"

13. columnLabelAccumulator.registerColumnOverrides(1, LABEL_PERIOD);

14. // ...

15. }

18. /**

19. * Cette configuration met en place la configuration de style sur la colonne

20. * "Landing Aircraft Type".

21. * @author A. BERNARD

22. *

23. */

24. private class AircraftTypeColumnConfiguration extends AbstractRegistryConfiguration {

26. @Override

27. public void configureRegistry(IConfigRegistry configRegistry) {

28. // ...

30. configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITABLE_RULE,

31. new IEditableRule() {

32. @Override

33. public boolean isEditable(int columnIndex, int rowIndex) {

34. // Seules les colonnes "Landing Aircraft Type" et "Year" sont editables

35. return (columnIndex == 9 || columnIndex == 1);

36. }

38. @Override

39. public boolean isEditable(ILayerCell cell, IConfigRegistry configRegistry) {

40. return (cell.getColumnIndex() == 9 || cell.getColumnIndex() == 1);

41. }

42. });

44. // ...

45. // Mise en place de l'edition de l'annee du vol

46. configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER,

47. new DefaultIntegerDisplayConverter(), DisplayMode.NORMAL, LABEL_PERIOD);

48. configRegistry.registerConfigAttribute(CellConfigAttributes.DISPLAY_CONVERTER,

49. new DefaultIntegerDisplayConverter(), DisplayMode.EDIT, LABEL_PERIOD);

51. configRegistry.registerConfigAttribute(EditConfigAttributes.DATA_VALIDATOR,

52. getYearValidator(), DisplayMode.EDIT, LABEL_PERIOD);

54. // Enregistrement des strategies d'erreur

55. configRegistry.registerConfigAttribute(EditConfigAttributes.CONVERSION_ERROR_HANDLER,

56. new DialogErrorHandling(), DisplayMode.EDIT, LABEL_PERIOD);

57. configRegistry.registerConfigAttribute(EditConfigAttributes.VALIDATION_ERROR_HANDLER,

58. new DialogErrorHandling(), DisplayMode.EDIT, LABEL_PERIOD);

60. // Ajout de la decoration sur la cellule

61. TextCellEditor textCellEditor = new TextCellEditor();

62. textCellEditor.setErrorDecorationEnabled(true);

63. textCellEditor.setErrorDecorationText("Year must be a four-digit integer " +

64. "between 2005 and 2013, inclusive.");

65. textCellEditor.setDecorationPositionOverride(SWT.LEFT | SWT.TOP);

67. configRegistry.registerConfigAttribute(EditConfigAttributes.CELL_EDITOR,

68. textCellEditor, DisplayMode.NORMAL, LABEL_PERIOD);

71. }

73. /**

74. * Creation de la strategie de validation sur l'annee du vol.

75. * @return

76. */

77. private IDataValidator getYearValidator() {

78. return new DataValidator() {

79. @Override

80. public boolean validate(int columnIndex, int rowIndex, Object newValue) {

81. if (newValue instanceof Integer && ((Integer)newValue).intValue() > 2004

82. && ((Integer)newValue).intValue() < 2014) {

83. return true;

84. }

85. else {

86. throw new ValidationFailedException("Value must be between 2005 and 2013");

87. }

88. }

89. };

90. }

92. // ...

93. }

94. }

En lançant notre exemple, nous pouvons activer l'édition de la cellule soit en cliquant, soit en appuyant sur F2 et nous pouvons observer les erreurs qui s'affichent lorsque les entrées sont invalides.

Erreur de conversion

Erreur de validation

Décoration sur l'édition

VII - Persistez !

Toute la configuration de la NatTable peut être enregistrée afin d'être rechargée automatiquement au prochain affichage. Les éléments enregistrés incluent, sans y être limités, les éléments suivants : ordre des colonnes, colonnes cachées, tri sur les données, groupement des colonnes, filtres sur les lignes.
La sauvegarde de l'état se fait grâce à la méthode « saveState » de la NatTable. Il est sauvé dans un objet « java.util.Properties », autrement dit un fichier clé/valeur séparés par le signe « = ». La restauration quant à elle se fait par la méthode « loadState » à partir d'un fichier Properties.

Il est évidemment possible de sauvegarder d'autres données particulières dans ce fichier « Properties », en appelant la méthode « registerPersistable(IPersistable) » sur un objet de type « ILayer ».

VIII - Liens utiles

Vous trouverez ci-dessous une sélection de liens qui pourraient vous être utiles lors de la mise en œuvre de la NatTable.

- Site officiel du projet.

- Documentation officielle du projet.

- La NatTable sur les forums Eclipse officiels.

- Javadoc officielle de l'API.

IX - Conclusion

Au travers de cet article, nous avons découvert toute la puissance de la NatTable. Bien entendu, il en reste beaucoup à dire, tant ce composant est riche en possibilités. Pour aller plus loin, vous pouvez étudier les exemples, dont l'installation est décrite en annexe.
Grâce à la NatTable, vous pourrez proposer aux utilisateurs de votre application un rendu et des fonctionnalités très complètes en un minimum de temps par rapport aux composants classiques.

X - Remerciements

Tout d'abord, nous ne pouvons que féliciter toute l'équipe de développement de ce composant pour la mise en place de ses fonctionnalités. Je remercie ensuite Mickaël BARON et Marc PEZZETTI pour leur relecture technique ainsi que Claude LELOUP pour sa relecture orthographique.

XI - Annexe : lancer les exemples

Tout d'abord, il est nécessaire de télécharger les exemples à cette adresse. Vous devez ensuite les lancer en indiquant le plugin SWT spécifique à votre OS à utiliser dans le classpath.

java -cp org.eclipse.swt.win32.win32.x86_%version%.jar;NatTableExamples-1.0.1.jar
org.eclipse.nebula.widgets.nattable.examples.NatTableExamples

« %version% » indique la version SWT que vous possédez. Dans mon cas, cela donne :

java -cp org.eclipse.swt.win32.win32.x86_3.100.1.v4236b.jar;NatTableExamples-1.0.1.jar
org.eclipse.nebula.widgets.nattable.examples.NatTableExamples

Lors du lancement, une fenêtre s'affiche, vous permettant de lancer les exemples via double-clic. Vous pouvez consulter le code source de chaque exemple en cliquant sur le lien « View Source » en bas de chaque exemple.

Exemples NatTable