Vous êtes à peu près ici : Accueil  »   tutoriel PyGTK  »   PyGTK : sommaire

14.2. L'interface TreeModel et le stockage des données

14.2.1. Introduction

Toutes les sous-classes TreeModel implémentent l'interface TreeModel. Elle fournit des méthodes pour :

  • récupérer les caractéristiques du modèle, telles que le nombre de colonnes et le type de données dans une colonne.
  • récupérer un TreeIter (une référence temporaire pointant sur une ligne du modèle).
  • récupérer des informations sur un nœud (ou une ligne) comme une liste de ses nœuds enfants, leur nombre, le contenu de ses colonnes ou un pointeur sur son nœud parent.
  • obtenir des notifications de changement des données du TreeModel

14.2.2. Créer des objets TreeStore et ListStore

Les classes de base de stockage des données, le ListStore et le TreeStore, permettent de définir et de manipuler les lignes et les colonnes de données dans le modèle. Dans les constructeurs de ces deux objets, les colonnes devront être déclarées comme étant de l'un des types suivants :

  • un type Python, comme les types prédéfinis int, str, long, float et object
  • un type PyGTK, comme les Button, VBox, gdk.Rectangle, gdk.Pixbuf
  • un type GObject (les GTypes de GTK+), spécifié sous la forme soit d'une constante gobject.TYPE, soit d'une chaine de caractères. La majorité des GTypes sont mappés sur un type Python :

    • gobject.TYPE_CHAR ou 'gchar'
    • gobject.TYPE_UCHAR ou 'guchar'
    • gobject.TYPE_BOOLEAN ou 'gboolean'
    • gobject.TYPE_INT ou 'gint'
    • gobject.TYPE_UINT ou 'guint'
    • gobject.TYPE_LONG ou 'glong
    • gobject.TYPE_ULONG ou 'gulong
    • gobject.TYPE_INT64 ou 'gint64'
    • gobject.TYPE_UINT64 ou 'guint64'
    • gobject.TYPE_FLOAT ou 'gfloat'
    • gobject.TYPE_DOUBLE ou 'gdouble'
    • gobject.TYPE_STRING ou 'gchararray'
    • gobject.TYPE_OBJECT ou 'GObject

Par exemple, la création d'un ListStore ou d'un TreeStore dont les lignes contiendraient un gdk.Pixbuf, un entier, une chaine et un booléen, pourrait ressembler à ceci :

  liststore = ListStore(gtk.gdk.Pixbuf, int, str, 'gboolean')

  treestore = TreeStore(gtk.gdk.Pixbuf, int, str, 'gboolean')

Une fois un ListStore ou un TreeStore créé et ses colonnes définies, ils ne peuvent plus être modifiés. Il est également important de savoir qu'il n'y a pas de relation prédéfinie entre les colonnes d'un TreeView et celles de son TreeModel. Ainsi, la cinquième colonne de données d'un TreeModel peut être affichée dans la première colonne d'un TreeView et dans la troisième d'un autre. On n'a donc pas à se soucier de l'affichage des données lorsque l'on crée un modèle.

Si ces deux modèles ne conviennent pas à votre application, il est possible de définir votre propre modèle personnalisé en Python, pourvu qu'il implémente l'interface TreeModel. Nous verrons ceci plus en détail dans la Section 14.11, « Le TreeModel générique ».

14.2.3. Les références aux lignes du modèle

Avant de pouvoir manipuler des lignes de données dans un TreeStore ou un ListStore, il nous faut pouvoir désigner les lignes à traiter. PyGTK offre trois moyens de faire référence à des lignes de TreeModel : le chemin d'accès, le TreeIter et le TreeRowReference.

14.2.3.1. Le chemin d'accès

Le chemin d'accès consiste en un entier, une chaine ou un tuple représentant l'emplacement d'une ligne dans le modèle. Une valeur entière indique le numéro d'une ligne dans un modèle en partant de 0 (le premier niveau). Par exemple, un chemin d'accès valant 4 indiquerait la cinquième ligne du modèle. Par comparaison, la même ligne serait désignée par "4" sous forme de chaine et par (4) sous forme de tuple. Si cela suffit effectivement à désigner toutes les lignes d'un ListStore, il nous reste encore à désigner les lignes enfants pour un TreeStore. On n'utilisera pour ce faire que les représentations sous forme de chaine ou de tuple.

La profondeur de l'arborescence d'un TreeStore étant complètement arbitraire, la représentation sous forme de chaine indique le chemin d'accès en partant du premier niveau jusqu'à la ligne voulue, et en séparant chaque valeur par le caractère ":". La représentation par tuple suit le même principe, le tuple étant constitué d'une séquence d'entiers conduisant à la ligne voulue en partant du premier niveau. Par exemple, les chemins "0:2" (troisième ligne enfant de la première ligne) et "4:0:1" (deuxième ligne enfant du premier enfant de la cinquième ligne) sont des représentations valides sous la forme de chaines de caractères. Les équivalents en tuples de ces chemins d'accès sont respectivement (0, 2) et (4, 0, 1).

Le chemin d'accès représente la seule possiblité de mapper une ligne d'un TreeView sur une ligne d'un TreeModel, car leurs chemins d'accès sont identiques. Le chemin d'accès pose toutefois quelques problèmes :

  • il peut désigner une ligne qui n'existe pas dans le ListStore ou dans le TreeStore.
  • il peut pointer vers une autre ligne de données après l'insertion ou la suppression d'une ligne dans le ListStore ou dans le TreeStore.

La représentation par tuple est utilisée par PyGTK lors du renvoi de chemins d'accès mais les trois formes sont acceptées indifféremment en entrée. La représentation par tuple est conseillée dans un souci de cohérence.

On peut récupérer un chemin d'accès à partir d'un TreeIter à l'aide de la méthode get_path() :

  chemin = modele.get_path(iter)

...où iter est un TreeIter pointant sur une ligne de modele et chemin le chemin d'accès de la ligne sous la forme d'un tuple.

14.2.3.2. Les TreeIter

Un TreeIter est un objet offrant une référence temporaire à une ligne de ListStore ou de TreeStore. Si le contenu du modèle est modifié (généralement par l'insertion ou la suppressiono d'une ligne), le TreeIter peut devenir invalide. Un TreeModel supportant les TreeIter persistants devrait avoir le drapeau gtk.TREE_MODEL_ITERS_PERSIST. Une application peut vérifier la présence de ce drapeau avec la méthode get_flags().

Pour créer un TreeIter, on utilise une des méthodes du TreeModel qui sont applicables aux TreeStore comme aux ListStore :

  treeiter = modele.get_iter(path)

...où treeiter pointe sur la ligne indiquée par le chemin d'accès path. Si le chemin d'accès est invalide, l'exception ValueError est levée.

  treeiter = modele.get_iter_first()

...où treeiter est un TreeIter pointant sur la ligne située au chemin (0,). treeiter vaudra None si le modèle est vide.

  treeiter = modele.iter_next(iter)

...où treeiter est un TreeIter qui pointe sur la ligne suivante, sur le même niveau que le TreeIter indiqué par iter. treeiter vaudra None s'il n'y a pas de ligne suivante (et iter sera invalidé).

Les méthodes qui suivent ne servent à recupérer un TreeIter qu'à partir d'un TreeStore :

  treeiter = treestore.iter_children(parent)

...où treeiter est un TreeIter pointant sur le premier enfant de la ligne indiquée par le TreeIter parent. treeiter vaudra None s'il n'y pas de ligne enfant.

  treeiter = treestore.iter_nth_child(parent, n)

...où treeiter est un TreeIter pointant sur la ligne enfant d'index n de la ligne désignée par le TreeIter parent. parent peut valoir None dans le cas où l'on souhaite récupérer un ligne de premier niveau. treeiter vaudra None s'il n'y a pas de ligne enfant.

  treeiter = treestore.iter_parent(child)

...où treeiter est un TreeIter pointant sur la ligne parent de la ligne désignée par le TreeIter child. treeiter vaudra None s'il n'y a pas de ligne enfant.

La méthode get_path() permet de récupérer un chemin d'accès à partir d'un TreeIter :

  chemin = modele.get_path(iter)

...où iter est un Treeiter pointant sur un ligne de modele et chemin le chemin d'accès de la ligne sous la fome d'un tuple.

14.2.3.3. Les TreeRowReference

Un TreeRowReference est une référence persistante à une ligne de données dans un modèle. Alors que le chemin d'accès (c'est-à-dire la situation) de la ligne (c'est-à-dire son emplacement) est susceptible de changer avec l'ajout ou la suppression d'autres lignes, le TreeRowReference pointera toujours sur la même ligne de données.

Note

Les TreeRowReference ne sont disponibles dans PyGTK qu'à partir de la version 2.4.

On crée un TreeRowReference grâce à son constructeur :

  treerowref = TreeRowReference(model, path)

...où model est le TreeModel contenant la ligne et path le chemin d'accès vers la ligne à laquelle faire référence. Si path n'est pas un chemin d'accès valide pour model, None est renvoyé.

14.2.4. Ajouter des lignes

14.2.4.1. Ajouter des lignes à un ListStore

Une fois que vous avez un ListStore, il vous faut lui ajouter des lignes de données. Plusieurs méthodes sont à votre disposition :

  iter = append(row=None)
  iter = prepend(row=None)
  iter = insert(position, row=None)
  iter = insert_before(sibling, row=None)
  iter = insert_after(sibling, row=None)

Chacune de ces méthodes insère une ligne à un emplacement implicite ou spécifié du ListStore. Les méthodes append() et prepend() utilisent des emplacements implicites : respectivement après la dernière ligne et avant la première. La méthode insert() attend un entier (le paramètre position) qui spécifie l'emplacement où insérer la ligne. les deux autres méthodes attendent un TreeIter (sibling) pointant sur un ligne du ListStore, afin d'insérer la ligne avant ou après.

Le paramètre row indique les données devant être insérées dans la ligne après sa création. Si row vaut None ou n'est pas spécifié, la ligne créée sera vide. Si row est spécifié, il doit être un tuple ou une liste contenant autant d'éléments que le nombre de colonnes dans le ListStore. Les éléments doivent également respecter le type de données de leurs colonnes respectives dans le ListStore.

Toutes ces méthodes renvoient un TreeIter pointant sur la ligne qui vient d'être insérée. La portion de code suivante illustre la création d'un ListStore et l'ajout de lignes de données :

  ...
  liststore = gtk.ListStore(int, str, gtk.gdk.Color)
  liststore.append([0,'red',colormap.alloc_color('red')])
  liststore.append([1,'green',colormap.alloc_color('green')])
  iter = liststore.insert(1, (2,'blue',colormap.alloc_color('blue')) )
  iter = liststore.insert_after(iter, [3,'yellow',colormap.alloc_color('blue')])
  ...

14.2.4.2. Ajouter des lignes à un TreeStore

L'ajout de lignes à un TreeStore est identique à l'ajout de lignes dans un ListStore à ceci près qu'il faut également indiquer une ligne parent (avec un TreeIter) à laquelle ajouter la nouvelle ligne. Les méthodes pour les TreeStore sont :

  iter = append(parent, row=None)
  iter = prepend(parent, row=None)
  iter = insert(parent, position, row=None)
  iter = insert_before(parent, sibling, row=None)
  iter = insert_after(parent, sibling, row=None)

Si parent vaut None, la ligne sera ajoutée au premier niveau.

Chacune de ces méthodes insère une ligne à un emplacement implicite ou spécifié du TreeStore. Les méthodes append() et prepend() utilisent des emplacements implicites : respectivement après la dernière ligne enfant et avant la première. La méthode insert() attend un entier (le paramètre position) qui spécifie l'emplacement où insérer la ligne enfant. Les deux autres méthodes attendent un TreeIter (sibling) pointant sur une ligne enfant du TreeStore, afin d'insérer la ligne avant ou après.

Le paramètre row indique les données devant être insérées dans la ligne après sa création. Si row vaut None ou n'est pas spécifié, la ligne créée sera vide. Si row est spécifié, il doit être un tuple ou une liste contenant autant d'éléments que le nombre de colonnes dans le TreeStore. Les éléments doivent également respecter le type de données de leurs colonnes respectives dans le TreeStore.

Toutes ces méthodes renvoient un TreeIter pointant sur la ligne qui vient d'être insérée. La portion de code suivante illustre la création d'un TreeStore et l'ajout de lignes de données :

  ...
  pbdossier = gtk.gdk.pixbuf_from_file('dossier.xpm')
  pbfichier = gtk.gdk.pixbuf_from_file('fichier.xpm')
  treestore = gtk.TreeStore(int, str, gtk.gdk.Pixbuf)
  iter0 = treestore.append(None, [1,'(0,)',pbdossier] )
  treestore.insert(iter0, 0, [11,'(0,0)',pbfichier])
  treestore.append(iter0, [12,'(0,1)',pbfichier])
  iter1 = treestore.insert_after(None, iter0, [2,'(1,)',pbdossier])
  treestore.insert(iter1, 0, [22,'(1,1)',pbfichier])
  treestore.prepend(iter1, [21,'(1,0)',pbfichier])
  ...

14.2.4.3. Modèles de grande taille

Lorsqu'un ListStore ou un TreeStore contient déjà un grand nombre de lignes, en ajouter de nouvelles peut se révéler très lent. Pour pallier cet inconvénient, on pourra suivre les conseils suivants :

  • Dans le cas de l'ajout d'un grand nombre de lignes, déconnecter le TreeModel de son TreeView (en utilisant la méthode set_model() avec son paramètre model à None). Ceci afin d'éviter que le TreeView n'actualise l'affichage à chaque nouvelle ligne.
  • Toujours dans le cas de l'ajout d'un grand nombre de lignes, désactiver le classement (en utilisant la méthode set_default_sort_func() avec son paramètre sort_func à None).
  • Limiter le nombre de TreeRowReference utilisés car ils actualisent leur chemin d'accès à chaque ajout/suppression.
  • Fixez la propriété "fixed-height-mode" du TreeView sur TRUE pour faire en sorte que toutes les lignes aient la même hauteur et ainsi éviter le calcul individuel de la hauteur pour chaque ligne (disponible seulement à partir de PyGTK 2.4).

14.2.5. Supprimer des lignes

14.2.5.1. Supprimer des lignes d'un ListStore

On peut supprimer une ligne de données d'un ListStore en faisant appel à la méthode remove() :

  treeiter = liststore.remove(iter)

...où iter est un TreeIter pointant sur la ligne à supprimer. Le TreeIter renvoyé (treeiter) pointera sur la ligne suivante ou sera invalide si iter pointait sur la dernière ligne.

La méthode clear() supprimer toutes les lignes du ListStore :

  liststore.clear()

14.2.5.2. Supprimer des lignes d'un TreeStore

Les méthodes servant à suprimer des lignes de données d'un TreeStore sont similaires à celles du ListStore :

  result = treestore.remove(iter)
  treestore.clear()

...où resultat vaudra TRUE si la ligne a été supprimée et iter pointera sur la ligne valide suivante. Dans le cas contraire, resultat vaudra FALSE et iter sera invalidé.

14.2.6. Manipuler les données des lignes

14.2.6.1. Définir et récupérer des valeurs

Les méthodes permettant d'accéder aux valeurs stockées dans un ListStore et dans un TreeStore ont le même format. Toutes les manipulations des données du modèle nécessitent un TreeIter pour indiquer la ligne sur laquelle on travaille. Une fois qu'on a un TreeIter, la méthode get_value() permet de récupérer les valeurs d'une colonne dans une rangée donnée :

  valeur = modele.get_value(iter, column)

...où iter est un TreeIter pointant sur une ligne, column le numéro d'une colonne de modele, et valeur la valeur stockée à l'emplacement ligne-colonne.

Pour récupérer les valeurs de plusieurs colonnes en un seul appel, on utilisera la méthode get() :

  valeurs = modele.get(iter, column, ...)

...où iter est un TreeIter pointant sur une ligne, column le numéro d'une colonne de modele, et ... représente d'éventuels numéros de colonne supplémentaires et valeurs est un tuple contenant les valeurs récupérées. Par exemple, pour récupérer les valeurs des colonnes 0 et 2 :

  val0, val2 = modele.get(iter, 0, 2)

Note

La méthode get() n'est disponible qu'à partir de PyGTK 2.4

Pour définir la valeur d'une colonne unique, on utilise la méthode set_value() :

  modele.set_value(iter, column, value)

...où iter (un TreeIter) et column (un entier) indiquent l'emplacement ligne-colonne dans modele et column est le numéro de la colonne où l'on veut stocker value. value doit être du même type de données que la colonne du modele.

Pour définir les valeurs de plusieurs colonnes d'un même ligne à la fois, on utilisera la méthode set() :

  modele.set(iter, ...)

...où iter désigne la ligne du modèle et ... est un ou plusieurs numéros de colonne - les paires de valeurs indiquant la colonne et la valeur à stocker. Par exemple, l'appel suivant :

  modele.set(iter, 0, 'Foo', 5, 'Bar', 1, 123)

...stockera 'Foo' dans la première colonne, 'Bar' dans la sixième et 123 dans la deuxième, tout ceci à la ligne de modele indiquée par iter.

14.2.6.2. Réordonner les lignes d'un ListStore

Les lignes d'un ListStore peuvent être déplacées à l'aide des méthodes suivantes (disponibles à partir de PyGTK 2.2) :

  liststore.swap(a, b)
  liststore.move_after(iter, position)
  liststore.move_before(iter, position)

swap() intervertit les positions des lignes désignées par les TreeIter a et b. move_after() et move_before() déplacent la ligne désignée par le TreeIter iter après ou avant la ligne désignée par le TreeIter position. Si position vaut None, move_after() placera la ligne au début du modèle et move_before() à la fin.

Pour réordonner complètement les lignes d'un ListStore, on fera appel à la méthode suivante :

  liststore.reorder(nouvel_ordre)

...où nouvel_ordre est une liste d'entiers représentant le nouvel ordre des lignes tel que :

  nouvel_ordre[nouvelleposition] = ancienneposition

Par exemple, si liststore contient quatre lignes :

  'un'
  'deux'
  'trois'
  'quatre'

...l'appel suivant :

  liststore.reorder([2, 1, 3, 0])

...produirait le nouvel ordre suivant :

  'trois'
  'deux'
  'quatre'
  'un'

Note

Ces méthodes réordonnent uniquement les ListStore non-ordonnés.

Pour réordonner les lignes avec PyGTK 2.0, il vous faudra les supprimer et les insérer avec les méthodes décrites aux Section 14.2.4, « Ajouter des lignes » et Section 14.2.5, « Supprimer des lignes ».

14.2.6.3. Réordonner les lignes d'un TreeStore

Les méthodes utilisées pour réordonner les lignes du TreeStore sont identiques à celles du ListStore à ceci près qu'elles n'affectent que les lignes enfants d'une ligne parent implicite - il n'est pas possible, par exemple, d'intervertir des lignes de parents différents :

  treestore.swap(a, b)
  treestore.move_after(iter, position)
  treestore.move_before(iter, position)

swap() intervertit les positions des lignes enfants désignées par les TreeIter a et b. a et b doivent avoir la même ligne parent. move_after() et move_before() déplacent la ligne désignée par le TreeIter iter après ou avant la ligne désignée par le TreeIter position. iter et position doivent avoir la même ligne parent. Si position vaut None, move_after() placera la ligne au début du modèle et move_before() à la fin.

La méthode reorder() attend un paramètre supplémentaire qui désigne la ligne parent dont les enfants doivent être réordonnés :

  treestore.reorder(parent, new_order)

...où new_order est une liste d'entiers représentant le nouvel ordre des lignes enfants pour la ligne parent spécifiée par le TreeIter parent, telle que :

  new_order[nouvelleposition] = ancienneposition

Par exemple, si treestore contient quatre lignes :

  'ligne parent'
      'un'
      'deux'
      'trois'
      'quatre'

... l'appel suivant

  treestore.reorder(parent, [2, 1, 3, 0])

...produirait le nouvel ordre suivant :

  'ligne parent'
      'trois'
      'deux'
      'quatre'
      'un'

Note

Ces méthodes réordonnent uniquement les TreeStore non-ordonnés.

14.2.6.4. Manipuler plusieurs lignes

L'une des opérations les plus délicates avec les ListStore ou des TreeStore est la manipulation de plusieurs lignes, par exemple le déplacement de plusieurs lignes d'une ligne parent à une autre, ou encore la suppression de certaines lignes en fonction de critères spéciaux. La difficulté provient de la nécessité d'utiliser un TreeIter qui peut devenir invalide du fait même de l'opération. Pour les ListStore et les TreeStore, les TreeIter sont persistants (afin de s'en assurer, la présence du drapeau gtk.TREE_MODEL_ITERS_PERSIST peut être vérifiée avec la méthode get_flags()). En revanche, les classes empilables TreeModelFilter et TreeModelSort ne disposent pas de TreeIter persistants.

Comment déplacer toutes les lignes enfants d'une ligne vers une autre si les TreeIter ne résistent pas à l'opération ? Il faut :

  • itérer sur les enfants de la ligne parent
  • récupérer les données de chaque ligne enfant
  • supprimer chaque ligne enfant
  • ajouter une nouvelle ligne, contenant les données de la ligne supprimée, à la liste de la nouvelle ligne parent

On ne peut pas se fier à la méthode remove() pour renvoyer un TreeIter valide. On demandera donc simplement l'iter de la première ligne enfant, et ce jusqu'à recevoir None. Voici un exemple de fonction pouvant déplacer des lignes enfants :

  def deplace_enfants(treestore, parent_source, parent_destination):
    nb_colonnes = treestore.get_n_columns()
    iter = treestore.iter_children(parent_source)
    while iter:
      valeurs = treestore.get(iter, *range(nb_colonnes))
      treestore.remove(iter)
      treestore.append(parent_destination, valeurs)
      iter = treestore.iter_children(parent_source)
    return

La fonction ci-dessus convient pour le simple cas où l'on souhaiterait déplacer tous les enfants d'une même ligne parent. Mais que faire si l'on veut supprimer toutes les lignes du TreeStore qui correspondent à un critère donné, comme la valeur de la première colonne par exemple ? Vous pensez peut-être à utiliser la méthode foreach() pour itérer sur toutes les lignes et supprimer celles qui correspondent :

  modele.foreach(fonction, donnees_utilisateur)

...où fonction est une fonction à invoquer pour chaque ligne du modèle et dont la signature est :

  def fonction(modele, chemin, iter, donnees_utilisateur):

...où modele est le TreeModel, chemin le chemin d'accès à une ligne de modele, iter un TreeIter pointant sur chemin et donnees_utilisateur les données transmises. Si fonction renvoie TRUE, la méthode foreach() cesse d'itérer et se termine.

Le problème de ce procédé est que modifier le contenu du modèle pendant que la méthode foreach() est en train d'itérer peut avoir des conséquences imprévisibles. Une meilleure stratégie serait d'utiliser la méthode foreach() pour créer et sauvegarder des TreeRowReferences pointant sur les lignes à supprimer, puis de supprimer celles-ci une fois la méthode foreach() terminée. Mais cela ne fonctionnerait pas avec PyGTK 2.0 et 2.2 dans lesquels les TreeRowReference n'existent pas.

Si l'on souhaite couvrir toutes les variantes de PyGTK, on peut utiliser la méthode foreach() pour rassembler les chemins d'accès des lignes à supprimer, puis les supprimer dans l'ordre inverse afin de préserver la validité des chemins d'accès. Par exemple, la portion de code suivante utilise cette stratégie :

  ...
  # On verifie que la valeur de la premiere colonne >= la valeur transmise
  # donnees est un tuple contenant la valeur de comparaison ainsi qu'une liste
  # pour sauvegarder les chemins d'acces
  def rappel_compar_valeur(modele, chemin, iter, donnees):
    if modele.get_value(iter, 0) >= donnees[0]:
      donnees[1].append(chemin)
    return False     # foreach continue d'iterer

  listechemins = []
  treestore.foreach(rappel_compar_valeur, (10, listechemins))

  # foreach fonctionne en profondeur d'abord (depth-first)
  listechemins.reverse()
  for chemin in listechemins:
    treestore.remove(treestore.get_iter(chemin))
  ...

Dans le cas où l'on voudrait rechercher la première ligne d'un TreeStore répondant à un critère donné, on pourrait effectuer soi-même l'itération :

   treestore = TreeStore(str)
   ...
   def fonction_compar(modele, iter, donnees):
       colonne, critere = donnees # donnes est un tuple contenant un numero de colonne et un critere
       valeur = modele.get_value(iter, colonne)
       return valeur == critere
   def recherche(modele, iter, fonction, donnees):
       while iter:
           if fonction(modele, iter, donnees):
               return iter
           resultat = recherche(modele, modele.iter_children(iter), fonction, donnees)
           if resultat: return resultat
           iter = modele.iter_next(iter)
       return None
   ...
   compar_iter = recherche(treestore, treestore.iter_children(None), 
                       fonction_compar, (0, 'bla'))

La fonction recherche() effectue une itération en profondeur sur la ligne (spécifiée par iter), sur ses pairs et sur leurs enfants à la recherche d'une ligne dont une colonne correspond à la chaine de caractère donnéee. La recherche prend fin lorsqu'une ligne est trouvée.

14.2.7. Support du protocole Python

Les classes qui implémentent l'interface TreeModel (TreeStore, ListStore et, à partir de PyGTK 2.4 TreeModelSort et TreeModelFilter) supportent les protocoles Python de mappage et d'itération. Le protocole d'itération permet d'utiliser la fonction iter() de Python sur un Treemodel pour créer un itérateur qui servira à itérer sur ses lignes de premier niveau. Une possibilité plus utile est d'itérer en utilisant l'instruction for ou une création fonctionnelle de liste (list comprehension). Par exemple :

  ...
  liststore = gtk.ListStore(str, str)
  ...
  # on ajoute des lignes au ListStore
  ...
  # une boucle for
  for ligne in liststore:
      # traitement individuel des lignes
  ...
  # creation fonctionnelle de liste renvoyant une liste des valeurs de la premiere colonne
  valeurs = [ l[0] for l in liststore ]
  ...

L'utilisation de del pour supprimer une ligne du modèle et l'extraction d'un TreeModelRoww PyGTK de ce dernier à partir d'une valeur-clé qui est un chemin d'accès ou un TreeIter sont d'autres parties supportées des protocoles de mappage. Par exemple, les instructions suivantes renvoient toutes la première ligne d'un TreeModel et la dernière instruction supprime le premier enfant de la première ligne :

  ligne = modele[0]
  ligne = modele['0']
  ligne = modele["0"]
  ligne = modele[(0,)]
  i = modele.get_iter(0)
  ligne = modele[i]
  del modele[(0,0)]

De plus, on peut fixer les valeurs d'une ligne existante comme ceci :

  ...
  liststore = gtk.ListStore(str, int, object)
  ...
  liststore[0] = ['Bouton', 23, gtk.Button('Etiquette')]

Les objets TreeModelRow de PyGTK supportent les protocoles de séquence et d'itération Python. On peut faire en sorte qu'un itérateur itère sur les valeurs des colonnes de la ligne, ou bien utiliser l'instruction for ou encore une création fonctionnelle de liste. Les TreeModelRow utilisent le numéro de colonne comme l'index à partir duquel extraire une valeur. Par exemple :

  ...
  liststore = gtk.ListStore(str, int)
  liststore.append(['Chaine de caracteres', 514])
  ...
  ligne = liststore[0]
  valeur1 = ligne[1]
  valeur0 = liststore['0'][0]
  for valeur in ligne:
      print valeur
  val0, val1 = ligne
  ...

On utilise l'exemple de la section précédente pour itérer sur un TreeStore afin de localiser une ligne contenant une valeur particulière, le code devient :

   treestore = TreeStore(str)
   ...
   def fonction_compar(ligne, donnees):
       colonne, critere = donnees # donnes est un tuple contenant un numero de colonne et un critere
       return ligne[colonne] == critere
   ...
   def recherche(lignes, fonction, donnees):
       if not lignes: return None
       for ligne in lignes:
           if fonction(ligne, donnees):
               return ligne
           resultat = recherche(ligne.iterchildren(), fonction, donnees)
           if resultat: return resultat
       return None
   ...
   compar_ligne = recherche(treestore, fonction_compar, (0, 'bla'))

On peut également définir une valeur dans une colonne existante ainsi :

  treestore[(1,0,1)][1] = 'abc'

Les TreeModelRow supportent aussi l'instruction del et la conversion en listes ou tuples avec les fonctions list() et tuple() de Python. Comme illustré dans l'exemple suivant, le TreeModelRow dispose de la méthode iterchildren() qui renvoie un itérateur pour itérer sur les lignes enfants du TreeModelRow.

14.2.8. Les signaux du TreeModel

Votre application peut suivre les changements d'un TreeModel en se connectant aux signaux qu'il émet : "row-changed", "row-deleted", "row-inserted", "row-has-child-toggled" et "rows-reordered". Ces signaux sont utilisés par un TreeView pour suivre les changements dans son TreeModel.

Dans une application connectée à ces signaux, l'appel de certaines méthodes peut déclencher l'émission de plusieurs signaux. Par exemple, l'appel pour ajouter à une ligne sa première ligne enfant :

  treestore.append(parent, ['qwe', 'asd', 123])

...déclenchera l'émission des signaux suivants :

  • "row-inserted" (ligne-insérée), du fait de l'insertion de la ligne (vide).
  • "row-has-child-toggled" (ligne-possède-enfant-modifié), puisque parent n'avait pas de ligne enfant auparavant.
  • "row-changed" (ligne-modifiée) car la valeur de la première colonne est fixée à 'qwe'.
  • "row-changed" (ligne-modifiée) car la valeur de la deuxième colonne est fixée à 'asd'.
  • "row-changed" (ligne-modifiée) car la valeur de la troisième colonne est fixée à 123.

Notez qu'il n'est pas possible de récupérer l'ordre des lignes dans la fonction de rappel du signal "rows-reordered" car le nouvel ordre des lignes est transmis sous la forme d'un pointeur opaque vers un tableau d'entiers.

On trouvera plus d'information sur les signaux des TreeModel dans le PyGTK Reference Manual.

14.2.9. Ordonner les lignes d'un TreeModel

14.2.9.1. L'interface TreeSortable

Les objets ListStore et TreeStore implémentent l'interface TreeSortable qui fournit des méthodes pour contrôler le classement des lignes de TreeModel. L'élément clé de l'interface est l'identificateur de classement de colonne, une valeur entière arbitraire faisant référence à une fonction de comparaison de classement et à des données utilisateur associées. Un identificateur de classement de colonne doit être supérieur ou égal à zéro. On en crée un avec la méthode suivante :

  treesortable.set_sort_func(id_class_colonne, fonction_classement, donnees_utilisateur=None)

...où id_class_colonne est une valeur entière assignée par le programmeur, fonction_classement une fonction ou une méthode utilisée pour comparer des lignes et donnees_utilisateur des données contextuelles. fonction_classement a la signature suivante :

  def fonction_classement(modele, iter1, iter2, donnees)
  def methode_classement(self, modele, iter1, iter2, donnees)

... où modele est le TreeModel contenant les lignes sur lesquelles pointent les TreeIter iter1 et iter2, et où donnees est donnees_utilisateur. fonction_classement doit renvoyer -1 si la ligne iter1 précède la ligne iter2, 0 si les lignes sont égales, et 1 si la ligne iter2 précède la ligne iter1. La fonction de comparaison de classement devrait toujours considérer que l'ordre de classement est gtk.SORT_ASCENDING puisque l'ordre de classement sera pris en compte par les implémentations TreeSortable.

La même fonction de comparaison de classement peut être utilisée pour plusieurs identificateurs de classement de colonne en variant les données utilisateur afin de fournir des informations contextuelles. Par exemple, les donnees_utilisateur spécifiées dans la méthode set_sort_func() pourraient être l'index de la colonne d'où extraire les données de classement..

Une fois qu'un identificateur de classement de colonne est créé, un modèle peut s'en servir pour le classement en appelant la méthode :

  treesortable.set_sort_column_id(sort_column_id, order)

...où order est l'ordre de classement ascendant gtk.SORT_ASCENDING ou descendant gtk.SORT_DESCENDING.

Un identificateur de classement de colonne valant -1 signifie que le modèle devrait utiliser la fonction de classement par défaut qui se définit avec la méthode suivante :

  treesortable.set_default_sort_func(fonction_classement, donnees_utilisateur=None)

On peut vérifier si un modèle a une fonction de classement par défaut avec la méthode :

  resultat = treesortable.has_default_sort_func()

...qui renvoie TRUE si une fonction de classement par défaut a été définie.

Une fois qu'un identificateur de classement de colonne a été appliqué sur un TreeModel implémentant l'interface TreeSortable, il ne peut plus revenir à son état original non classé. On peut changer la fonction de classement ou utiliser une fonction de classement par défaut mais on ne peut pas retirer au TreeModel la fonction de classement.

14.2.9.2. Classement dans les ListStore et les TreeStore

Lorsqu'un objet ListStore ou TreeStore est créé, il définit automatiquement des identificateurs de classement de colonne correspondant aux colonnes du modèle en utilisant le numéro de l'index de la colonne. Par exemple, un ListStore avec trois colonnes aurait trois identificateurs de classement de colonnes (0, 1, 2) créés automatiquement. Ces identificateurs de classement de colonne sont associés avec une fonction interne de comparaison de classement qui gère les types fondamentaux :

  • 'gboolean'
  • str
  • int
  • long
  • float

Initialement, un ListStore ou un TreeStore sont définis avec un identificateur de classement de colonne de -2 qui indique qu'aucune fonction de classement n'est utilisée et que le modèle est non-classé. Une fois défini un identificateur de classement de colonne sur un ListStore ou sur un TreeStore, il est impossible de le ramener à -2.

Si l'on souhaite maintenir les identificateurs de classement de colonne par défaut, on peut définir un identificateur de classement de colonne bien en-dehors de l'intervalle du nombre de colonnes, comme 1000 ou plus. Puis l'on peut alterner entre la fonction de classement par défaut et les fonctions de classement de l'application selon les besoins.