Accueil » PyGTK - gestion d'évènements dans un gtk.ListStore (première partie)

PyGTK - gestion d'évènements dans un gtk.ListStore (première partie)

Document Actions

Par Fab le 10/05/2006 20:20

Pour des raisons diverses, j'ai voulu rajouter une interface graphique à une application que j'avais écrite en pure python. PyGTK et Glade étant un binôme plus que performant, mon interface répondait à mes attentes au bout d'un week-end.

Ma curiosité naturelle prenant le dessus, j'ai voulu découvrir un peu plus en profondeur PyGTK.
Et c'est là que j'ai commencé à rencontrer quelques difficultés à trouver des informations plus précises, comme par exemple, les évenements de la souris dans une TreeView (TreeStore ou ListStore pour mon cas).

J'essaierai de vous montrer dans cette série de tutoriels, une approche pour gérer les évenements de la souris dans ces widgets très spécifiques que sont les gtk.TreeStore et gtk.ListStore.

Cette suite de tutoriels sera composée de 3 parties :
1 - Approche sur la gestion d'évènements dans un gtk.ListStore
2 - Approche avancée sur la gestion d'évenements dans un gtk.TreeStore
3 - Élaboration d'un menu dynamique dans un gtk.TreeStore

Catégories : graphique
Modules python : gtk,gtk.glade,gobject,re,urllib2,signal
Version Python : 2.4

Prérequis

Glade2

Compte tenu du nombre d'articles sur la toile concernant Glade2, je ne vais pas m'étendre sur ce sujet. Donc dans un souci de gain de temps (ou de paresses), vous trouverez içi, le fichier glade à mettre dans votre répertoire source du tutoriel.

Ben oui, vous vous en doutez sûrement, je préfère coder que d'écrire de la documentation. Mais voilà, faut savoir de temps en temps remercier la communauté, et donc contribuer ;-)

Python

Rien de bien spécifique ;-)

On partira du principe que vous avez un dossier "tutorial", dans lequel vous y mettrez vos sources, ainsi que votre fichier glade.

Première approche

Créons un fichier treeview_tuto1.py

Code source de treeview_tuto1.py

# -*- coding: utf-8 -*-
##      treeview_tuto1.py
import gtk
import signal

import treeview_window

class Interface:
  def __init__(self):
    self.treeview_tutorial = treeview_window.Tutorial()

if __name__ == '__main__':
  signal.signal(signal.SIGINT, signal.SIG_DFL)
  Interface()
  gtk.main()

Commentaires

Rien de bien complexe, le code principal vient juste après. En effet, on peut voir qu'on importe un fichier nommé treeview_window.py.

Donc, rajoutons un fichier treeview_window.py à notre répertoire et préparons nous à construire notre interface.

Interface GTK

Code source de treeview_window.py

# -*- coding: utf-8 -*-
##      treeview_window.py

import gtk
import gtk.glade
import gobject

TREEVIEW_TUTORIAL = 'treeview_tutorial.glade'

class Tutorial:
  '''
  Définition de l'interface GTK Tutorial
  '''
  def __init__(self):
    '''
    Constructeur avec les attributs de classe décrivant notre interface.
    On y retrouve les noms de nos différents widgets déclarés dans Glade2.
    '''
    self.xml = gtk.glade.XML(TREEVIEW_TUTORIAL, 'window1')
    self.window = self.xml.get_widget('window1')
    self.xml.signal_autoconnect(self)

    self.liststore = self.xml.get_widget("treeview1")
    self.treemodel = gtk.ListStore(gobject.TYPE_INT,
                                   gobject.TYPE_STRING,
                                   gobject.TYPE_STRING)
    self.liststore.set_model(self.treemodel)
    self.liststore.set_headers_visible(True)

    renderer2 = gtk.CellRendererText()
    column = gtk.TreeViewColumn("ID", renderer2, text=0)
    column.set_resizable(True)
    self.liststore.append_column(column)
    column = gtk.TreeViewColumn("Nom", renderer2, text=1)
    column.set_resizable(True)
    self.liststore.append_column(column)
    column = gtk.TreeViewColumn("Contenu", renderer2, text=2)
    column.set_resizable(True)
    self.liststore.append_column(column)

  def on_window1_destroy(self, widget):
    gtk.main_quit()

Commentaires

Heu, j'en ai déjà mis un peu dans le code !

Bon, pour faire court, nous avons :

  • une classe Tutorial avec des attributs de classe qui servent à décrire l'interface
  • une méthode on_window1_destroy qui sert à sortir de l'interface

Résultats

Finition

Afin d'éviter de travailler sur une liste vide, créons un fichier supplémentaire que l'on appelera list.py

Code source de list.py

# -*- coding: utf-8 -*-
##      list.py

import re
import urllib2
import gtk

from urllib2 import URLError as url_error
from urllib2 import HTTPError as http_error

class Populate:
  def __init__(self, model):
    self.model = model
    self.url = "http://www.afpy.org/"
    self.result = []
    self.regex = re.compile( r'href=\"http://www.afpy.org/Members/([\w-]+[/][\w+]+)')
    self.insertData()

  def insertData(self):
    ls = self.request_url()
    for content in range(len(ls)):
      iter = self.model.append(
        [content,
         self.split_name(ls[content]),
         self.split_content(ls[content])]
        )

  def request_url(self):
    try :
      u = urllib2.urlopen(self.url)
      r_urls = self.extract_urls(u)
      for r_url in r_urls:
        self.result.append(r_url)
      return self.result
    except url_error, e:
      print "Echec de la requete:", e.reason
    except http_error, e:
      print "Echec de la connexion:", e.reason

  def extract_urls(self, u):
    urls = []
    for line in u.readlines() :
        if (self.regex.findall(line)):
            m = self.regex.findall(line)
            for myurl in m :
              urls.append(myurl)
    u.close()
    return urls

  def split_name(self, content):
    name = re.split("/", content)
    return name[0]

  def split_content(self, content):
    name = re.split("/", content)
    return name[1]

Commentaires

Ce qui nous faut retenir dans ce fichier, c'est la façon que la méthode insertData() de la classe Populate :

  def insertData(self):
    ls = self.request_url()
    for content in range(len(ls)):
      iter = self.model.append(
        [content,
         self.split_name(ls[content]),
         self.split_content(ls[content])]
        )

En effet, c'est elle qui remplit notre tableau, une fois que nous appellerons la classe depuis treeview_window.py.

Rajoutons donc l'appel à notre classe depuis le contructeur de Tutorial, sans oublier d'importer notre nouveau fichier list.py:

# -*- coding: utf-8 -*-
##      treeview_window.py
##

import gtk
import gtk.glade
import gobject

import list # <-- IMPORT RAJOUTÉ !!

TREEVIEW_TUTORIAL = 'treeview_tutorial.glade'

class Tutorial:
  '''
  Définition de l'interface GTK Tutorial
  '''

  def __init__(self):
    '''
    Constructeur avec les attributs de classe décrivant notre interface.
    On y retrouve les noms de nos différents widgets déclarés dans Glade2.
    '''
    self.xml = gtk.glade.XML(TREEVIEW_TUTORIAL, 'window1')
    self.window = self.xml.get_widget('window1')
    self.xml.signal_autoconnect(self)

    self.liststore = self.xml.get_widget("treeview1")
    self.treemodel = gtk.ListStore(gobject.TYPE_INT,
                                   gobject.TYPE_STRING,
                                   gobject.TYPE_STRING)
    self.liststore.set_model(self.treemodel)
    self.liststore.set_headers_visible(True)

    self.populate = list.Populate(self.treemodel) # <-- APPEL RAJOUTÉ !!

    renderer2 = gtk.CellRendererText()
    column = gtk.TreeViewColumn("ID", renderer2, text=0)
    column.set_resizable(True)
    self.liststore.append_column(column)
    column = gtk.TreeViewColumn("Nom", renderer2, text=1)
    column.set_resizable(True)
    self.liststore.append_column(column)
    column = gtk.TreeViewColumn("Contenu", renderer2, text=2)
    column.set_resizable(True)
    self.liststore.append_column(column)


  def on_window1_destroy(self, widget):
    gtk.main_quit()

Résultat

Il est tout simplement magnifique ;-) :

C'est vrai, j'exagère un peu, mais l'idée est là. Il est temps de passer au vif du sujet, l'association d'un click de la souris ...

Évenements sur le gtk.ListStore

Si vous regardez correctement dans Glade2, vous verrez qu'il y a une action de prédéfinie sur le gtk.TreeView. On va récupérer le nom de cette action et lui associer une méthode dans Tutorial :

  def on_treeview1_button_press_event(self, treeview, event):
    '''Action pour le bouton droit'''
    if event.button == 3:
      x = int(event.x)
      y = int(event.y)
      pthinfo = treeview.get_path_at_pos(x, y)
      if pthinfo != None:
        path, col, cellx, celly = pthinfo
        treeview.grab_focus()
        treeview.set_cursor( path, col, 0)
      model = treeview.get_model()
      iter = model.get_iter(path)
      #Je récupère la troisième valeur
      content = model[iter][2]
      print "Debug clic droit : %s" % content

Une fois ajoutée cette méthode dans la classe Tutorial, vous pouvez relancer le fichier treeview_tuto1.py.

Chaque clic de souris sur le gtk.TreeView va :

  • appeler la méthode on_treeview1_button_press_event(),
  • récupérer la troisième valeur de la colonne
  • et l'afficher dans votre console.

Et voilà !

Conclusion

Nous avons pu voir une première approche sur la récupération d'évenments de la souris dans un gtk.ListStore. Bien entendu, il faut garder à l'esprit que pour un autre widget implémentant l'interface TreeModel, l'approche serait identique. Dans ce cas précis, l'interaction avec Glade2 est quasi-transparente. Nous verrons dans le prochain tutoriel, une approche avec gtk.TreeStore, les exceptions TypeError et une action suivant la valeur de l'iter.

L'archive des sources de ce tutoriel est disponible :

La suite ! la suite !

Posté par jpcw2002 le 11/05/2006 19:53
En attendant de lire la suite, voilà le genre de tuto agréable à lire, et qui donne envie de regarder un truc alors qu'à priori on ne voulait pas s'y pencher dessus.
Aidez l'AfPy

Rechercher
Dernières news AFPY
Les 6 dernières news
PyCon FR - 17-18 mai - Paris
07/05/2008 07:05
AFPyro d'Avril
24/04/2008 00:00