# TreeView with ListStore (JavaScript)

Taryn Fox
jewelfox@fursona.net 2012
Un élément graphique qui affiche une liste séparée d'éléments
Luc Rebert,
traduc@rebert.name 2011
Alain Lojewski,
allomervan@gmail.com 2011-2012
Luc Pionchon
pionchon.luc@gmail.com 2011
Bruno Brouard
annoa.b@gmail.com 2011-12
Luis Menina
liberforce@freeside.fr 2014

# TreeView avec ListStore

Un TreeView est une fenêtre affichant le contenu soit d'un ListStore, soit d'un TreeStore. Un ListStore peut être comparé à une feuille de calcul : une liste « plate », en deux dimensions, contenant des données réparties sur des lignes et dans des colonnes. Alors qu'un TreeStore peut déployer ses branches dans différentes directions, à la manière d'un arbre. Dans cet exemple, nous créons un TreeView qui affiche le contenu d'un ListStore contenant des noms et des numéros de téléphone (fictifs) et nous le paramétrons pour que l'étiquette en bas de la fenêtre affiche plus d'informations sur le nom qui a été cliqué.

Un TreeView n'est pas qu'un simple élément graphique, il contient aussi un certain nombre d'autres plus petits :

Les éléments graphiques TreeViewColumn affichent chaque colonne d'informations contenue dans le ListStore. Chacune d'elle a un nom qui peut être affiché dans l'en-tête de la colonne, comme montré dans la capture d'écran.

Les éléments graphiques CellRenderer sont « empilés » dans chaque TreeViewColumn et contiennent les instructions sur la manière d'afficher chaque « cellule » individuelle ou chaque élément du ListStore. Il en existe beaucoup de types différents, y compris le CellRendererText utilisé ici et le CellRendererPixbuf qui affiche une image « pixel buffer ».

Pour finir, nous allons utiliser un objet nommé TreeIter, qui n'est pas à proprement parler un élément graphique, mais plutôt un curseur invisible qui pointe vers une ligne horizontale du ListStore. Si vous cliquez par exemple sur un nom de la liste téléphonique, cela génère un TreeIter qui pointe vers la ligne sélectionnée et qui sert à indiquer au ListStore quelles informations supplémentaires l'étiquette doit afficher.

Le TreeView est probablement l'élément graphique Gtk le plus compliqué, de par le nombre d'éléments qu'il contient et à cause de la façon dont ils sont imbriqués pour travailler ensemble. Prenez le temps d'apprendre et son fonctionnement en faisant des essais, ou alors essayez de commencer par quelque chose de plus facile si vous n'y arrivez pas.

# Bibliothèques à importer

#!/usr/bin/gjs

const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Lang = imports.lang; const Pango = imports.gi.Pango;

Ce sont les bibliothèques que nous devons importer pour faire fonctionner cette application. N'oubliez pas que la ligne qui informe GNOME que nous allons utiliser Gjs doit toujours se trouver au début.

# Création de la fenêtre de l'application

const TreeViewExample = new Lang.Class({ Name: 'TreeView Example with Simple ListStore',

// Create the application itself _init: function() { this.application = new Gtk.Application({ application_id: 'org.example.jstreeviewsimpleliststore' });

// Connect 'activate' and 'startup' signals to the callback functions this.application.connect('activate', Lang.bind(this, this._onActivate)); this.application.connect('startup', Lang.bind(this, this._onStartup)); },

// Callback function for 'activate' signal presents window when active _onActivate: function() { this._window.present(); },

// Callback function for 'startup' signal builds the UI _onStartup: function() { this._buildUI (); },

Tout le code utilisé pour cet exemple va dans la classe TreeViewExample. Le code ci-dessus crée une Gtk.Application pour nos éléments graphiques et la fenêtre qui les contient.

// Build the application's UI _buildUI: function() {

// Create the application window this._window = new Gtk.ApplicationWindow({ application: this.application, window_position: Gtk.WindowPosition.CENTER, default_height: 250, default_width: 100, border_width: 20, title: "My Phone Book"});

La fonction _buildUI est l'endroit où nous mettons tout le code nécessaire à la création de l'interface utilisateur de l'application. La première étape consiste à créer une Gtk.ApplicationWindow pour y mettre tous nos éléments graphiques.

# Création du ListStore

// Create the underlying liststore for the phonebook this._listStore = new Gtk.ListStore (); this._listStore.set_column_types ([ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING]);

Créons en premier le ListStore comme nous l'aurions fait pour n'importe quel élément graphique. Appelons ensuite sa méthode set_column_types et passons lui un tableau de types de données GObject (nous aurions pu placer tous les types sur une seule et même ligne, mais pour faciliter la lecture, nous les séparons).

Les types de données GObject que vous pouvez utiliser incluent :

GObject.TYPE_BOOLEAN -- true ou false

GObject.TYPE_FLOAT -- un nombre à virgule flottante

GObject.TYPE_STRING -- une chaîne de caractères (lettres ou chiffres)

gtk.gdk.Pixbuf -- une image

Dans ce cas, nous créons un ListStore de quatre colonnes contenant chacune des valeurs de type chaînes de caractères.

Pour pouvoir utiliser les types GObject, vous devez placer la ligne const GObject = imports.gi.GObject; au début du code de votre application, comme nous l'avons fait dans cet exemple.

// Data to go in the phonebook this.phonebook = let phonebook = [{ name: "Jurg", surname: "Billeter", phone: "555-0123", description: "A friendly person."}, { name: "Johannes", surname: "Schmid", phone: "555-1234", description: "Easy phone number to remember."}, { name: "Julita", surname: "Inca", phone: "555-2345", description: "Another friendly person."}, { name: "Javier", surname: "Jardon", phone: "555-3456", description: "Bring fish for his penguins."}, { name: "Jason", surname: "Clinton", phone: "555-4567", description: "His cake's not a lie."}, { name: "Random J.", surname: "Hacker", phone: "555-5678", description: "Very random!"}];

Voici les informations contenues dans le ListStore. C'est un assortiment d'objets, chacun correspondant à une entrée de notre répertoire téléphonique.

Notez que le TreeView de la capture d'écran n'affiche pas pour l'instant les données des propriétés « description ». Elles s'afficheront dans l'étiquette du dessous à chaque clic sur une ligne. C'est parce qu'un TreeView et un ListStore sont deux choses bien distinctes et qu'un TreeView peut afficher tout ou une partie du contenu d'un ListStore de différentes façons. Vous pouvez même posséder plusieurs éléments graphiques affichant des éléments d'un même ListStore, comme l'étiquette de notre exemple ou bien encore un second TreeView.

for (i = 0; i < phonebook.length; i++ ) { let contact = phonebook [i]; this._listStore.set (this._listStore.append(), [0, 1, 2, 3], [contact.name, contact.surname, contact.phone, contact.description]); }

Cette boucle for place les chaînes de caractères de notre répertoire téléphonique dans notre ListStore dans l'ordre. Pour ce faire, nous passons à la méthode set du ListStore le curseur qui pointe vers la bonne ligne, un tableau qui indique quelles colonnes nous voulons définir et un tableau qui contient les données que nous voulons y mettre.

La méthode append du ListStore ajoute une ligne horizontale à celui-ci (au début, il n'y en a pas) et renvoie un TreeIter pointant vers cette ligne comme un curseur. Donc, en transmettant this._listStore.append() au ListStore comme propriété, nous créons une nouvelle ligne en indiquant en même temps à la méthode set à quelle ligne elle doit attribuer les données.

# Création du TreeView

// Create the treeview this._treeView = new Gtk.TreeView ({ expand: true, model: this._listStore });

Ici, nous créons un élément graphique TreeView de base, qui s'étend à la fois horizontalement et verticalement pour utiliser autant d'espace que nécessaire. Nous le paramétrons pour utiliser le ListStore que nous avons créé comme étant son « modèle » et d'où proviendront les éléments qu'il affichera.

// Create the columns for the address book let firstName = new Gtk.TreeViewColumn ({ title: "First Name" }); let lastName = new Gtk.TreeViewColumn ({ title: "Last Name" }); let phone = new Gtk.TreeViewColumn ({ title: "Phone Number" });

Nous créons maintenant chacun des TreeViewColumns vertical qui s'afficheront dans notre TreeView. Comme le montre la capture d'écran, chaque titre se situe respectivement en haut de sa colonne.

// Create a cell renderer for when bold text is needed let bold = new Gtk.CellRendererText ({ weight: Pango.Weight.BOLD });

// Create a cell renderer for normal text let normal = new Gtk.CellRendererText ();

// Pack the cell renderers into the columns firstName.pack_start (bold, true); lastName.pack_start (normal, true); phone.pack_start (normal, true);

Ici, nous créons les CellRenderers que nous allons utiliser pour afficher le texte contenu dans notre ListStore et nous les positionnons dans les TreeViewColumns. Chaque CellRendererText est utilisé pour toutes les entrées de cette colonne. Notre CellRendererText normal (normal) ne génère que du texte brut, alors que celui en gras (bold) utilise du texte plus gras. Nous le mettons dans la première en-tête de colonne et nous indiquons aux deux autres d'utiliser des copies du normal. L'argument « true » utilisé comme second paramètre de la méthode pack_start lui indique d'agrandir les cellules quand cela est possible, au lieu de les laisser compactes.

Voici iciune liste d'autres propriétés de texte que vous pouvez utiliser. Pour pouvoir utiliser les constantes Pango, assurez-vous d'ajouter la ligne const Pango = imports.gi.Pango; au début de votre code comme nous l'avons fait.

firstName.add_attribute (bold, "text", 0); lastName.add_attribute (normal, "text", 1); phone.add_attribute (normal, "text", 2);

// Insert the columns into the treeview this._treeView.insert_column (firstName, 0); this._treeView.insert_column (lastName, 1); this._treeView.insert_column (phone, 2);

Après avoir placé les CellRenderers dans les TreeViewColumns, nous utilisons la méthode add_attribute pour indiquer à chaque colonne d'extraire du modèle le texte pour lequel notre TreeView est programmée ; dans ce cas, le ListStore contenant le répertoire téléphonique.

Le premier paramètre indique quel CellRenderer nous allons utiliser pour restituer ce que nous extrayons.

Le second paramètre indique le type d'information que nous voulons extraire. Dans ce cas, nous lui indiquons que nous voulons du texte.

Le troisième paramètre indique de quelle colonne du ListStore nous voulons extraire cette information.

Après avoir paramétré ceci, nous utilisons la méthode insert_column pour trier le contenu de nos TreeViewColumns. Notre TreeView est à présent terminée.

Normalement, vous devriez utiliser une boucle pour initialiser votre TreeView, mais dans cet exemple, nous avons détaillé étape par étape ce qui ce passe afin de mieux comprendre.

# Construction du reste de l'interface utilisateur

// Create the label that shows details for the name you select this._label = new Gtk.Label ({ label: "" });

// Get which item is selected this.selection = this._treeView.get_selection();

// When something new is selected, call _on_changed this.selection.connect ('changed', Lang.bind (this, this._onSelectionChanged));

La méthode get_selection du TreeView retourne un objet appelé un TreeSelection. Un TreeSelection est comme un TreeIter, tout simplement un curseur qui pointe vers une ligne particulière, à la différence que celui-ci pointe vers la ligne qui est marqué visuellement comme sélectionnée.

Une fois obtenu le TreeSelection qui va avec notre TreeView, nous lui demandons de nous indiquer vers quelle ligne il pointe quand il est modifié. Nous obtenons cela en connectant le signal changed à la fonction _onSelectionChanged que nous avons programmée. Cette fonction modifie le texte affiché par l'étiquette que nous venons de créer.

// Create a grid to organize everything in this._grid = new Gtk.Grid;

// Attach the treeview and label to the grid this._grid.attach (this._treeView, 0, 0, 1, 1); this._grid.attach (this._label, 0, 1, 1, 1);

// Add the grid to the window this._window.add (this._grid);

// Show the window and all child widgets this._window.show_all(); },

Ceci fait, nous créons une grille (grid) pour y placer le tout, puis nous l'ajoutons à notre fenêtre et demandons à cette fenêtre de s'afficher avec son contenu.

# Fonction prenant en charge la modification de la sélection

_onSelectionChanged: function () {

// Grab a treeiter pointing to the current selection let [ isSelected, model, iter ] = this.selection.get_selected();

// Set the label to read off the values stored in the current selection this._label.set_label ("\n" + this._listStore.get_value (iter, 0) + " " + this._listStore.get_value (iter, 1) + " " + this._listStore.get_value (iter, 2) + "\n" + this._listStore.get_value (iter, 3));

}

});

La ligne de code utilisant l'instruction « let » est quelque peu compliquée, mais c'est néanmoins la meilleure façon d'obtenir un TreeIter qui pointe vers la même ligne que notre TreeSelection. Elle doit créer quelques autres références d'objets, mais le seul qui nous intéresse est l'iter.

Ceci fait, nous appelons la fonction set_label de l'étiquette et nous utilisons la fonction get_value du ListStore autant de fois que nécessaire pour la remplir de toutes les données que nous souhaitons y mettre. Ses paramètres sont un TreeIter pointant vers la ligne dont nous voulons extraire les données et la colonne.

Ici, nous voulons obtenir les données des quatre colonnes, y compris celles qui sont « masquées » et qui ne font pas partie du TreeView. De cette façon, nous pouvons utiliser notre étiquette pour afficher les chaînes de caractères qui sont trop longues pour tenir dans le TreeView et que nous n'avons pas besoin de voir au premier coup d'œil.

// Run the application let app = new TreeViewExample (); app.application.run (ARGV);

Enfin, nous créons une nouvelle instance de la classe TreeViewExample qui est terminée et nous démarrons l'application.

# Exemple complet de code

#!/usr/bin/gjs

imports.gi.versions.Gtk = '3.0';

const GObject = imports.gi.GObject; const Gtk = imports.gi.Gtk; const Pango = imports.gi.Pango;

class TreeViewExample { // Create the application itself constructor() { this.application = new Gtk.Application({ application_id: 'org.example.jstreeviewsimpleliststore' });

// Connect 'activate' and 'startup' signals to the callback functions this.application.connect('activate', this._onActivate.bind(this)); this.application.connect('startup', this._onStartup.bind(this)); }

// Callback function for 'activate' signal presents window when active _onActivate() { this._window.present(); }

// Callback function for 'startup' signal builds the UI _onStartup() { this._buildUI(); }

// Build the application's UI _buildUI() { // Create the application window this._window = new Gtk.ApplicationWindow({ application: this.application, window_position: Gtk.WindowPosition.CENTER, default_height: 250, default_width: 100, border_width: 20, title: "My Phone Book"});

// Create the underlying liststore for the phonebook this._listStore = new Gtk.ListStore (); this._listStore.set_column_types ([ GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING, GObject.TYPE_STRING]);

// Data to go in the phonebook let phonebook = [{ name: "Jurg", surname: "Billeter", phone: "555-0123", description: "A friendly person."}, { name: "Johannes", surname: "Schmid", phone: "555-1234", description: "Easy phone number to remember."}, { name: "Julita", surname: "Inca", phone: "555-2345", description: "Another friendly person."}, { name: "Javier", surname: "Jardon", phone: "555-3456", description: "Bring fish for his penguins."}, { name: "Jason", surname: "Clinton", phone: "555-4567", description: "His cake's not a lie."}, { name: "Random J.", surname: "Hacker", phone: "555-5678", description: "Very random!"}];

// Put the data in the phonebook for (let i = 0; i < phonebook.length; i++ ) { let contact = phonebook [i]; this._listStore.set (this._listStore.append(), [0, 1, 2, 3], [contact.name, contact.surname, contact.phone, contact.description]); }

// Create the treeview this._treeView = new Gtk.TreeView ({ expand: true, model: this._listStore });

// Create the columns for the address book let firstName = new Gtk.TreeViewColumn ({ title: "First Name" }); let lastName = new Gtk.TreeViewColumn ({ title: "Last Name" }); let phone = new Gtk.TreeViewColumn ({ title: "Phone Number" });

// Create a cell renderer for when bold text is needed let bold = new Gtk.CellRendererText ({ weight: Pango.Weight.BOLD });

// Create a cell renderer for normal text let normal = new Gtk.CellRendererText ();

// Pack the cell renderers into the columns firstName.pack_start (bold, true); lastName.pack_start (normal, true); phone.pack_start (normal, true);

// Set each column to pull text from the TreeView's model firstName.add_attribute (bold, "text", 0); lastName.add_attribute (normal, "text", 1); phone.add_attribute (normal, "text", 2);

// Insert the columns into the treeview this._treeView.insert_column (firstName, 0); this._treeView.insert_column (lastName, 1); this._treeView.insert_column (phone, 2);

// Create the label that shows details for the name you select this._label = new Gtk.Label ({ label: "" });

// Get which item is selected this.selection = this._treeView.get_selection();

// When something new is selected, call _on_changed this.selection.connect ('changed', this._onSelectionChanged.bind(this));

// Create a grid to organize everything in this._grid = new Gtk.Grid;

// Attach the treeview and label to the grid this._grid.attach (this._treeView, 0, 0, 1, 1); this._grid.attach (this._label, 0, 1, 1, 1);

// Add the grid to the window this._window.add (this._grid);

// Show the window and all child widgets this._window.show_all(); }

_onSelectionChanged() { // Grab a treeiter pointing to the current selection let [ isSelected, model, iter ] = this.selection.get_selected();

// Set the label to read off the values stored in the current selection this._label.set_label ("\n" + this._listStore.get_value (iter, 0) + " " + this._listStore.get_value (iter, 1) + " " + this._listStore.get_value (iter, 2) + "\n" + this._listStore.get_value (iter, 3) ); } };

// Run the application let app = new TreeViewExample (); app.application.run (ARGV);

# Documentation approfondie

Dans cet exemple, les éléments suivants sont utilisés :

Gtk.Application

Gtk.ApplicationWindow

Gtk.CellRendererText

Gtk.ListStore

Gtk.TreeIter

Gtk.TreeSelection

Gtk.TreeView

Gtk.TreeViewColumn