2009-03-03 13:09

PyQt by Example (Session 2)

Requerimientos

Traducción: Nicolás Pace / Roberto Alsina

Si no lo hiciste, lee primero la Sesión 1.

Todos los archivos de esta sesion están aquí: Sesión 2 at GitHub

Sesión 2: Conectando todo

En la Sesión 1 creamos la ventana principal de nuestra aplicación, el esqueleto que muestra esta ventana, y un simple backend basado en Elixir.

Lo que no hicimos fue vincularlos. Este es el gran paso que daremos en esta sesión.

Primero trabajaremos con nuestro main.py.

Cargando los datos

Lo primero que haremos es usar el backend, asi que debemos importar todo.py.

Hacemos esto en la línea 13

Luego, en línea 25 hacemos la primera conección real: obtenemos las tareas y las insertamos en nuestra lista.

Examinemos en mas detalle este bloque de código, entre las lineas 25 y 35:

# Hagamos algo interesante: recuperar el contenido de la base de datos
# y asociarlo a nuestro componente lista
for task in todo.Task.query().all():
    tags=','.join([t.name for t in task.tags])
    item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags])
    item.task=task
    if task.done:
        item.setCheckState(0,QtCore.Qt.Checked)
    else:
        item.setCheckState(0,QtCore.Qt.Unchecked)
    self.ui.list.addTopLevelItem(item)

Recuerdan nuestro backend, todo.py? En el definimos los objetos Task que almacenamos en la base de datos. Si no estas familiarizado con Elixir, todo.Task.query().all() recupera una lista con todos los objetos Task de la base de datos.

Luego asignamos los tags separados por comas a tags. Esto se verá más o menos como "home,important".

I ahora veremos nuestro primer código relacionado con Qt.

Primero: self.ui.list es un widget. Todo lo que agreguemos a la ventana usando Designer es accesible usando self.ui.nombre_objeto donde nombre_objeto es el nombre asignado en Designer. Haciendo click derecho en el gran widget en nuestra pantalla verás que el nombre del objeto es list:

/static/tut2-object_name.png

El nombre del objeto es list.

Podés verlo (y cambiarlo) en el Inspector de objetos y en el Editor de propiedades.

Eso no es solo un widget. Es un QTreeWidget, útil para mostrar listas de varias columnas y árboles.

Puedes leer un monton acerca de este widget en su manual, aquí un breve resumen:

  • Nosotros creamos objetos QTreeWidgetItem. Éstos toman una lista de Strings, que son mostradas en las columnas del widget. En este caso, estamos agregando el texto de la tarea ("Comprar alimentos"), la fecha de vencimiento, y los tags.
  • Seteamos item.task con task. Esto es para que después sepamos cual tarea es descripta por un ítem específico. No es el modo más elegante, pero sí el más fácil.
  • Agregamos cada item al widget usando addTopLevelItem para que estén todos al mismo nivel (no jerarquizados, como padres e hijos)
  • Funcionará como una lista

Luego, si la tarea fue realizada (task.done==True) hacemos un tilde al lado de la tarea. En caso contrario, no.

Para muchos programas simples, esto es todo lo que necesitas saber de QTreeWidget. Es un widget bastante complejo, y bastante poderoso, pero para este ejemplo con esto nos alcanza. Investiguen!

Entonces, qué es lo que hace? Ejecutemos python main.py para descubrirlo!

/static/tut2-window3.png

La lista de tareas con nuestras tareas de ejemplo.

Si nuestra ventana esta vacía, intenten ejecutar python todo.py primero, quien creará algunas tareas de ejmplo.

Puedes inclusive marcar una tarea como realizada! Esto sucede ya que hemos implementado (parcialmente) el guardado y modificación de tareas...

Guardando datos

Lo que queremos es que cuando el susuario hace click en el checkbox, la tarea debe ser modificada de manera acorde, y guardada en la base de datos.

En la mayoría de los toolkits, estaríamos hablando de callbacks. Aquí las cosas son un poco distintas. Cuando un widget Qt quiere hacerte notar alfo, como "El usuario presionó este botón" o "Se activó el menú de ayuda", o lo que sea, se emiten señales (síganme la corriente un minuto).

En particular, nos interesa la señal itemChanged del QTreeWidget:

QTreeWidget.itemChanged ( item, column ) [signal]

Esta señal se emite cuando cambia el contenido de la columna en el item especificado.

Y cada vez que se usa el checkbox, se emite esta señal. ¿Porqué es importante eso? Porque podemos conectar nuestro propio código a una señal de manera que cada vez que esa señal se emite nuestro código sea ejecutado. Ése código se llama un slot en la jerga de Qt.

Es como un callback, excepto que:

  1. La señal no sabe a qué está conectada (o si está conectada a algo)
  2. Se pueden conectar cuantos slots uno quiera a cada señal.

Esto ayuda a mantener el código loosely coupled.

Podríamos definir un método en la clase Main, y conectarlo a la señal itemChanged, pero no es necesario porque podemos usar AutoConnect. Si se agrega a Main un método con un nombre específico, queda conectado a esa señal. El nombre es on_nombreobjeto_nombreseñal.

Aquí está el código (líneas 37 a 42):

1
2
3
4
5
6
def on_list_itemChanged(self,item,column):
    if item.checkState(0):
        item.task.done=True
    else:
        item.task.done=False
    todo.saveData()

Puede verse que usamos item.task para reflejar el checkState del item (o sea, si está marcado o no) en el estado de la tarea.

La llamada a todo.saveData() al final se asegura que todos los datos se guarden en la base de datos vi Elixir. Esa función es un poco horrible porque quería que funcione con dos versiones distintas de Elixir.

AutoConnect es lo que van a usar 90% del tiempo para agregar comportamiento a sus aplicaciones. La mayor parte del tiempo simplemente se crea la ventana, se añaden los widgets y se enganchan las señales via AutoConnect.

En algunas ocasiones eso no será suficiente. Pero todavía no llegamos a eso.

Ésta fue una sesión más bien corta, pero prepárense para la próxima, vamos a estar haciendo Cosas Importantes con Designer (MR)!

Mientras tanto tal vez quieran ver estas páginas:


Aquí pueden ver los cambios entre la versión vieja y la nueva:

Modified lines:  34
Added line:  13, 14, 15, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 48
Removed line:  None
Generated by diff2html
© Yves Bailly, MandrakeSoft S.A. 2001
diff2html is licensed under the GNU GPL.

  session1/main.py     session2/main.py
  34 lines
754 bytes
Last modified : Mon Mar 2 01:29:24 2009

    60 lines
1557 bytes
Last modified : Thu Mar 5 02:03:03 2009

1 # -*- coding: utf-8 -*-   1 # -*- coding: utf-8 -*-
2   2
3 """The user interface for our app"""   3 """The user interface for our app"""
4   4
5 import os,sys   5 import os,sys
6   6
7 # Import Qt modules   7 # Import Qt modules
8 from PyQt4 import QtCore,QtGui   8 from PyQt4 import QtCore,QtGui
9   9
10 # Import the compiled UI module   10 # Import the compiled UI module
11 from windowUi import Ui_MainWindow   11 from windowUi import Ui_MainWindow
12   12
      13 # Import our backend
      14 import todo
      15
13 # Create a class for our main window   16 # Create a class for our main window
14 class Main(QtGui.QMainWindow):   17 class Main(QtGui.QMainWindow):
15     def __init__(self):   18     def __init__(self):
16         QtGui.QMainWindow.__init__(self)   19         QtGui.QMainWindow.__init__(self)
17   20
18         # This is always the same   21         # This is always the same
19         self.ui=Ui_MainWindow()   22         self.ui=Ui_MainWindow()
20         self.ui.setupUi(self)   23         self.ui.setupUi(self)
21   24
      25         # Let's do something interesting: load the database contents
      26         # into our task list widget
      27         for task in todo.Task.query().all():
      28             tags=','.join([t.name for t in task.tags])
      29             item=QtGui.QTreeWidgetItem([task.text,str(task.date),tags])
      30             item.task=task
      31             if task.done:
      32                 item.setCheckState(0,QtCore.Qt.Checked)
      33             else:
      34                 item.setCheckState(0,QtCore.Qt.Unchecked)
      35             self.ui.list.addTopLevelItem(item)
      36
      37     def on_list_itemChanged(self,item,column):
      38         if item.checkState(0):
      39             item.task.done=True
      40         else:
      41             item.task.done=False
      42         todo.saveData()
      43
      44
22 def main():   45 def main():
      46     # Init the database before doing anything else
      47     todo.initDB()
      48
23     # Again, this is boilerplate, it's going to be the same on   49     # Again, this is boilerplate, it's going to be the same on
24     # almost every app you write   50     # almost every app you write
25     app = QtGui.QApplication(sys.argv)   51     app = QtGui.QApplication(sys.argv)
26     window=Main()   52     window=Main()
27     window.show()   53     window.show()
28     # It's exec_ because exec is a reserved word in Python   54     # It's exec_ because exec is a reserved word in Python
29     sys.exit(app.exec_())   55     sys.exit(app.exec_())
30   56
31   57
32 if __name__ == "__main__":   58 if __name__ == "__main__":
33     main()   59     main()
34   60

Generated by diff2html on Fri Mar 6 00:58:30 2009
Command-line:
/home/ralsina/bin/diff2html session1/main.py session2/main.py

Comentarios

Comments powered by Disqus

Contents © 2000-2018 Roberto Alsina