Uno de los problemas que se producen al crear aplicaciones en pygtk es que si se ejecuta algún comando externo o dentro del mismo código algo que se demore su tiempo en ser ejecutado, mientras se lleva a cabo esta tarea la ventana literalmente se congela/bloquea hasta que se termina de realizar esta tarea, además que no muestra el resultado hasta que termina (y en muchos casos uno quiere mostrar el progreso mientras se ejecuta la tarea).
Una de las soluciones a esto es usar threads (hilos), pero NO "de la manera normal" como es usan, ya que si se hace de forma normal, los widgets no se comportan siempre como es debido y a veces los threads ni siquiera funcionan.
La solución es usar los métodos gtk.gdk.threads_init(), gtk.gdk.threads_enter() y gtk.gdk.threads_leave() de la siguiente forma:
- Al inicio de código hay que poner gtk.gdk.threads_init() para indicarle a la aplicación que se van a usar hilos en la interfaz gráfica (linea 13 del ejemplo).
- Luego en la parte del código que que hace el trabajo pesado (y que posiblemente congele la aplicación de manera temporal) hay que poner el gtk.gdk.threads_enter() antes de esas líneas y gtk.gdk.threads_leave() después de esas lineas (lineas 25 y 33 del ejemplo).
Ahora veamos el ejemplo (con los correspondientes comentarios), mas adelante lo explico con mas detalles:
#!/usr/bin/env python # Escrito por Daniel Fuentes B. # Licencia: BSD <http://www.opensource.org/licenses/bsd-license.php> import pygtk pygtk.require("2.0") import gtk import os import threading # iniciamos los thead (los iniciamos, aun no lo usamos) gtk.gdk.threads_init() comando = "find /var" # Esta funcion hace el trabajo "pesado", luego es llamada desde la ventana def salida_comando(salida_texto, buffer_texto, comando): output = os.popen(comando) while True: linea = output.readline() if not linea: break # Comienza el trababjo pesado, asi que entramos en un thread gtk.gdk.threads_enter() # Se inserta la siguiente linea de la salida del proceso y luego se # coloca el cursor al final del textview (se mueve el scroll) iter = buffer_texto.get_end_iter() buffer_texto.place_cursor(iter) buffer_texto.insert(iter, linea) salida_texto.scroll_to_mark(buffer_texto.get_insert(), 0.1) # Finaliza el trabajo pesado, asi que salimos del thread gtk.gdk.threads_leave() # Clase con la ventana principal. class MainWindow: def __init__(self): # Definimos la ventana inicial ventana = gtk.Window(gtk.WINDOW_TOPLEVEL) ventana.set_title("Ejemplo de threading en gtk") ventana.resize(450,300) ventana.connect("destroy", gtk.main_quit) # Creamos una caja vertical con un espacio entre widgets de 5px y con # la propiedad homogeneo en False. Luego agragamos las widgets al vbox vbox = gtk.VBox(False, 5) # Una simple etiqueta que se muestra en la parte superior de la ventana etiqueta = gtk.Label("Resultado del comando:") # Creamos un textview que tenga un auto scroll, o sea que se desplace # solo al aparecer nuevo texto que sobrepase la pantalla # en este textview se mostrara la salida del comando ejecutado scrollwin = gtk.ScrolledWindow() scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) salida_texto = gtk.TextView() buffer_texto = salida_texto.get_buffer() scrollwin.add(salida_texto) # creamos un boton, el que iniciara el comando al hacer click en el boton = gtk.Button("iniciar programa") boton.connect("clicked", self.on_boton_clicked, salida_texto, buffer_texto, comando) # Empacamos el textview con scroll y el boton dentro del vbox vbox.pack_start(etiqueta, False, False, 0) vbox.pack_start(scrollwin) vbox.pack_start(boton, False) # y luego unimos el vbox la ventana pricipal y la mostramos ventana.add(vbox) ventana.show_all() def on_boton_clicked(self, widget, salida_texto, buffer_texto, comando): # Este boton es el lanza el hilo (threading) al hacer click sobre el hilo = threading.Thread(target=salida_comando, args=(salida_texto, buffer_texto, comando)) hilo.start() if __name__ == "__main__": MainWindow() gtk.main()
Lo que hace el programa es ejecutar un comando "find /var" y luego muestra la salida de este en la ventana.
La clase MainWindow crea la ventana de pygtk, es muy parecida al primer ejemplo que vimos en el tutorial de introducción a pygtk, el único detalle es que al hacer click en el boton (on_boton_clicked) se inicia un thread (hilo) que ejecuta la función salida_comando con los correspondientes argumentos.
En cuanto a la salida_comando es una función que ejecuta un comando (usando os.popen) tal como si uno lo escribiera en un terminal y luego toma el texto que resulta de ejecutar el comando y lo muestra en el textview de nuestra ventana. Esta funcion contiene la parte del código que hace el trabajo pesado, por esto tiene los gtk.gdk.threads_enter() y gtk.gdk.threads_leave()
Y así se ve la ventana funcionando:

Claro que esta no es la única forma de hacerlo, o sino miren en las faqs de pygtk