viernes, 27 de febrero de 2009

Acceso a una base de datos con Python

.
7 comentarios

En este artículo les mostrare como hacer algunas de las tareas mas típicas a la hora de trabajar con bases de datos. Veremos como realizar altas, bajas y modificaciones de datos, realizar consultas y hacer uso de transacciones.

Una de las formas mas simple para trabajar con una base de datos desde Python, es hacer uso de DB-API. DB-API es una especificación estándar de accedo a base de datos, que se compone de dos niveles: un primer nivel que funciona como una interface genérica para acceder a cualquier base de datos, y un segundo nivel compuesto por los drivers propios de cada motor de base de datos. Este esquema, nos permite lograr mayor independencia con respecto al motor de base de datos que usemos, y nos simplifica enormemente tareas como el cambio de una base de datos por otra.

Para los ejemplos usaré MySQL como motor de base de datos. Teniendo en cuenta lo dicho en el párrafo anterior, antes de empezar a trabajar, debemos instalar el driver que realmente se encargara de los detalles del acceso a MySQL. Para instalar el driver (al menos Linux) puedes hacer uso de los repositorios, ya que en las distribuciones mas populares lo podemos encontrar por defecto. El nombre del driver es MySQLdb. En el caso de Ubuntu (como es mi caso), el paquete que debemos instalar es: Python-MySQLdb.

Asumiendo que tienes instalado Python y MySQL; al instalar el driver MySQLdb, ya estamos listos para empezar a trabajar.

Trabajando con MySQLdb

Importar MySQLdb

Antes de poder conectarnos a una base de datos MySQL e interactuar con esta, debemos importar el módulo MySQLdb desde nuestro script Python:

import MySQLdb

Conectarse a la base de datos

Una vez importado el modulo MySQLdb, nos conectamos del siguiente modo:

cnn = MySQLdb.connect(host='localhost', user='usuario_mysql',passwd='password', db='base_de_datos')

Como puedes ver, para conectarnos a MySQL, usamos el método Connect() de MySQLdb, al cual le pasamos como parámetro, la dirección del servidor, el usuario, la contraseña y la base de datos. Debes tener en cuenta que esto es valido para conectarnos a MySQL, pero los parámetros de conexión difieren entre los distintos gestores de bases de datos.

Crear un cursor

Luego de conectarnos, debemos crear un cursor para poder realizar las acciones que necesitemos sobre la base de datos; para crearlo escribimos lo siguiente:

mi_cursor = cnn.cursor()

El cursor lo creamos haciendo uso del método cursor() del objeto que representa la conexión, en el ejemplo, el objeto se llama cnn, y fue instanciado como resultado de la ejecución de MySQLdb.connect() cuando nos conectamos a la base de datos.

Agregar un registro

Con el cursor creado, ya podemos interactuar con la base de datos. Para agregar un registro a la base de datos, utilizaremos el método excecute() del cursor. En realidad, este método no solo permite agregar registros, sino que lo que hace es enviar a la base de datos, el código SQL que especifiquemos como parámetro. Suponiendo que en nuestra base de datos tenemos una tabla llamada “tblArticulos” con 2 campos (“ID” y “Descripcion”), para agregar un nuevo registro escribiríamos lo siguiente:

mi_cursor.execute ("INSERT INTO tblArticulos(ID, Descripcion) VALUES(1, 'mouse')")

En este ejemplo estaríamos agregando el articulo “mouse” con ID “1”, a la tabla tblArticulos.

Modificar un registro

Para modificar un registro, procedemos igual que para agregarlo, pero obviamente debemos cambiar las sentencias SQL:

mi_cursor.execute ("UPDATE tblArticulos SET Descripcion='Mouse Logitech MX Revolution' WHERE ID=1)")

Aquí estaríamos modificando la descripción del articulo con ID “1” (el mouse que agregamos antes).

Eliminar un registro

Una vez mas, para eliminar un registro, hacemos uso del método execute() del cursor, pasándole el código SQL correspondiente a esta tarea como parámetro:

mi_cursor.execute("DELETE FROM tblArticulos WHERE ID=1)")

Aquí eliminamos el registro del articulo que creamos antes.

Consultar la base de datos

Las consultas también las hacemos con el método execute() del cursor, pero aquí nos extenderemos un poco mas en como recorrer el resultado de la consulta. Un ejemplo de consulta podría ser el siguiente:

mi_cursor.execute ("SELECT * FROM tblArticulos")

Con este código, obtendríamos todos los registros de la tabla tblArticulos. Para recorrer los distintos registros que nos devolvió la consulta, podemos elegir entre los métodos fetchone(), fetchmany() y fetchall() del cursor.

1- Usando fetchone()

res = mi_cursor.fetchone()

En el objeto res tendríamos el primer registro del resultado de la consulta representado por una tupla. Cada vez que invoquemos a este método, nos dará el registro actual e internamente quedara apuntando al siguiente, de modo que si lo invocamos sucesivas veces terminaremos por recorrer todo los registros. Para recorrer todo el resultado de la consulta, podríamos escribir algo como esto:

un_registro = mi_cursor.fetchone()

while(un_registro):
print un_registro
un_registro = mi_cursor.fetchone()

2- Usando fetchmany()

fetchmany() nos devuelve una lista de tuplas con parte del resultado de la consulta, en realidad, devolverá tantos registros como nosotros le especifiquemos. Por ejemplo, si escribimos los siguiente:
res = mi_cursor.fetchmany(3)

for reg in res:
print reg

obtendríamos los tres primeros registros del resultado de la consulta; si lo ejecutamos nuevamente, obtendríamos los tres siguientes y así sucesivamente.

3- Usando fetchall()

A diferencia de fetchmany(), fetchall() retorna todos los registros del resultado de la consulta. Al igual que fetchmany(), este método también retorna una lista de tuplas. El siguiente código, mostraría todos los registros devueltos por la consulta:

res = mi_cursor.fetchall()

for reg in res:
print reg

IMPORTANTE:
Vale aclarar que si la consulta retorna una gran cantidad de registros, este método no sería el más recomendado.

También podríamos recorrer todo el resultado de la consulta, directamente sobre el cursor (sin usar ninguno de los métodos fetch):

for reg in mi_cursor:
print reg

Como hemos visto, ninguno de los métodos anteriores nos retorna la información como un diccionario, por lo que si queremos mostrar un campo concreto (digamos “ID”) de un registro, lo deberíamos especificar por el índice de la columna y no por el nombre del campo. Si necesitamos hacer uso de esta característica, podemos especificar el tipo MySQLdb.cursors.DictCursor a la hora de crear el cursor:

mi_cursor = db.cursor(MySQLdb.cursors.DictCursor)

ahora podríamos recorrer el resultado como vimos antes, pero con la salvedad de que ahora si podemos escribir cosas como estas:

for reg in mi_cursor:
print reg['ID']

con lo cual veríamos todos los IDs de los registros devueltos por la consulta.

Para terminar con este punto, les voy a hablar de una opción interesante que tenemos a la hora de pasar nuestras sentencias SQL como parámetro del método execute(). La idea es que cuando necesitamos usar variables dentro de la consulta, estas las podemos escribir de la siguiente manera:

mi_cursor.execute ("UPDATE tblArticulos SET descripción = %s WHERE id = %s", ('Mouse genérico de 3 botones', 1))

En el ejemplo puedes ver, que ahora le estamos pasando dos parámetros al método execute(), el primero sigue siendo la consulta y el segundo es una tupla de valores, que representa, en forma posicional cada uno de los %s que existen dentro de la consulta, por lo que al enviarse la consulta anterior, internamente quedaría así:

UPDATE tblArticulos SET titulo_articulo = 'Mouse genérico de 3 botones' WHERE id = 1

Como tal vez notaste, no necesitamos poner los %s entre comillas cuando su valor sea una cadena, ya que MySQLdb lo hará por nosotros.

Manejar transacciones

Cuando alteramos el contenido de nuestra base de datos (Por ejemplo: al agregar un registro), estos cambios puede que no se reflejen inmediatamente. Por ejemplo, en caso de que estemos usando tablas del tipo MyISAM, los cambios serán aplicados inmediatamente, debido a que este tipo de tablas no soportan transacciones, pero si en cambio usáramos una tabla de tipo InnoDB (que si soporta transacciones) los cambios no se reflejaran en la misma, hasta que invoquemos el método commit() de la conexión (a no ser que la característica auto-commit esta activada). Si queremos abortar una transacción, podemos usar el método rollback() de la conexión.

Conclusión

En este artículo, intente hacer un paneo general por las acciones mas comunes al trabajar con una base de datos. Si bien he usado MySQL como motor de base de datos para los ejemplos, el proceso es prácticamente igual si usas otra base de datos, ya que como te comentaba al principio, gracias a DB-API, logramos una gran independencia del motor elegido. Con unos pocos cambios podrías hacer que los ejemplos funcionen con otro gestor de base de datos, solo debes instalar el driver correspondiente y luego importarlo desde tu aplicación. Los ejemplos que vimos son realmente muy triviales, es por esto que seguramente lo único que deberías modificar son los parámetros de conexión según corresponda.
Para terminar, te cuento que DB-API no es la única opción para trabajar con base de datos en Python. En otra oportunidad, les hablare de los denominados ORM (motores para mapeo objeto-relacional), que nos permiten acceder a las tablas de la base de datos, como si de objetos Python se tratara. Ejemplos de ORMs son SQLObject y SQLAlchemy.

Hasta la próxima!

lunes, 23 de febrero de 2009

Utilizar archivos de recursos [XRC] con wxPython (Parte III)

.
9 comentarios

Si has llegado aquí mediante un buscador o por simple casualidad, te cuento que estas en la tercera parte de un tutorial que empieza AQUI.

En esta ultima entrega del tutorial, agregaremos un menú a la interface de ejemplo que venimos usando, y luego veremos como detectar los eventos del menú para reaccionar en consecuencia. Como lo venimos haciendo hasta ahora, seguiremos haciendo uso de wxFormBuilder.

Agregando un menú a nuestra interface con wxFormBuilder

En primer lugar, abriremos el proyecto que contiene la interface (archivo .fbp). De no tener el archivo del proyecto, pero si contar con el archivo .xrc, wxFormBuilder nos da la opción de importarlo; para lo cual creamos un proyecto vacío y seleccionamos la opción "Import XRC..." del menú "File". El Object Tree se vería así:


Para agregar el menú con wxFormBuilder, tenemos dos alternativas:

Opción 1: "Manual"

La primera opción, es la alternativa "más manual” de las dos.

Un menú esta compuesto por una barra de menú (wx.MenuBar), que contiene los distintos menues propiamente dichos (wx.Menu), como ser "Archivo", "Editar", "Ver", etc., y dentro de estos, existen uno o mas ítems de menú (wx.MenuItem), como ser "Abrir", "Cerrar", "Imprimir", etc.; también pueden existir submenues, que en realidad no son mas que menues (wx.Menu) dentro de otro menú. Dicho esto, pasaremos a crearlo.

El menú que vamos a crear es muy sencillo. Para que veas las opciones que tendrá, a continuación te muestro una imagen:

Para crear este menú, y teniendo en mente lo dicho antes, lo primero que crearemos será la barra que contendrá el menú (wx.MenuBar); para lo que seleccionamos el Frame en el Object Tree, y pulsamos el botón "wx.MenuBar" que encontramos en la solapa "Menu/ToolBar" de la paleta de componentes:


Tras agregar la barra de menú, el Editor se vería así:


Como segundo paso, vamos a agregar a la barra de menú, el menú "Opciones". Para agregarlo tan solo debemos clickear el botón wx.Menu en la misma solapa desde la que agregamos la barra. Una vez agregado, editamos su propiedad label con el texto "Opciones".


Por último, agregamos dos ítems pulsando dos veces el botón wxMenuItem en la barra de componentes, y modificamos la propiedad label de ambos, el primero con el valor "Mostrar bienvenida" y el segundo con el valor "Salir".


Con esto tendríamos nuestro menú terminado. El Object Tree se ve así:


Si ejecutamos la ventana pulsando F5, veremos lo siguiente:

Puedes apreciar que el botón "Cancelar" quedo "cortado"; para evitar esto moveremos el menú al inicio dentro del "Objet Tree". Para mover un ítem en forma vertical dentro del Object Tree, lo seleccionamos y pulsamos la tecla ALT y la tecla "arriba" o "abajo" según corresponda, en nuestro caso pulsamos ALT + "Arriba", con lo cual el Object Tree quedaría así.


Si ahora ejecutamos la ventana, la veríamos correctamente:

Ahora les muestro la misma captura, pero con el menú desplegado:

IMPORTANTE:
La solución de mover el menú a la parte superior dentro del Object Tree, por ahora te pido que lo tomes "tal cual te lo digo", en otra oportunidad cuando haga un tutorial especifico sobre menues en wxPython, hablaremos mas en detalle.

Opción 2: "Asistida"

En este método, usaremos una herramienta de wxFormBuilder, que nos permite editar el menú en forma mas asistida. Para invocarla, pulsamos el botón derecho del mouse sobre el Frame en el Object Tree y elegimos la opción Menu Editor...


El editor de menú lo puedes ver en la siguiente imagen:


Veamos paso a paso como crear el mismo menú que antes, pero ahora usando el Editor de menú:

A diferencia del método anterior, aquí no tenemos que agregar la barra que contendrá el menú (wx.MenuBar), ya que el asistente lo hará por nosotros, así que simplemente empezamos agregando el menú "Opciones" (wx.Menu). Para agregar este menú, tan solo debemos escribir el nombre del mismo (en este caso “Opciones” ) en la caja de texto junto a la etiqueta "label" y pulsar el botón "Add":


Tras pulsar el botón "Add", veremos que en el panel de la izquierda del Menu Editor se agregara una fila que representa el nuevo menú:


Al escribir el valor de la propiedad "label", el asistente completará las propiedades "Id" y "Name" automáticamente, pero de todos modos, podemos editarlos en forma manual.

Ahora repetimos el procedimiento anterior, pera con el texto "Mostrar bienvenida" en la propiedad label. Una vez agregado este item de menú, la lista del panel izquierdo del Menu Editor quedara así:


Si bien el ítem ha sido agregado, aun no hemos indicado que este ultimo es un wx.MenuItem; para que wxFomBuilder lo reconozca como tal, pulsamos el botón que tiene el signo mayor (>).


Al pulsar el botón “>”, el ítem que tengamos seleccionado se convierte en el hijo del ítem que tiene en la fila que se encuentre arriba de el.

Ya sólo nos queda agregar el otro ítem de menú (“Salir”), así que procedemos a repetir los mismo pasos que hicimos para agregar el ítem de menú “Mostrar Bienvenida”, pero ahora con el label "Salir". Luego de agregarlo, el Menu Editor quedaría así:


Listo, de este modo tenemos el menu terminado haciendo uso del Editor de Menú de wxFormBuilder. Una vez mas, no quise entrar en detalle sobre las características de este Editor, ya que eso lo veremos en otro tutorial más especifico sobre el uso de Menues.

En mi caso, para agregar el menú, opte por la opción asistida. Ya sea que usemos el meo todo 1 (“Manual”) o el método 2 (”Asistido”), debemos exportar el archivo XRC como ya vimos antes, y luego pasamos nuevamente al código de nuestra aplicación, para darle uso a nuestras dos opciones de menú.

Manejando los eventos de nuestro menú

Para refrescar la memoria, a continuación te muestro el código de nuestra aplicación hasta el momento:
import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')

self.frame = self.res.LoadFrame(None, 'MyFrame')

self.botonAceptar = xrc.XRCCTRL(self.frame, 'm_button1')
self.frame.Bind(wx.EVT_BUTTON, self.mostrar_bienvenida, self.botonAceptar)

self.botonCancelar = xrc.XRCCTRL(self.frame, 'm_button2')
self.frame.Bind(wx.EVT_BUTTON, self.salir, self.botonCancelar)

self.frame.Show()

return True

def mostrar_bienvenida(self, event):
wx.MessageBox('Bienvenido!')

def salir(self, event):
self.frame.Close()

if __name__ == '__main__':
app = MiAplicacion()

app.MainLoop()

En primer lugar, enlazaremos el ítem de menú "Mostrar bienvenida" con el mismo manejador de evento que teníamos creado para el botón "Aceptar". Esto de enlazar un mismo manejador de evento con varios widgets es muy común, sobre todo lo podemos ver en el caso que una misma función, la ejecutamos desde una opción de menú y desde un botón de una barra de herramientas (Por ej: las opciones Cortar, Copiar, Pegar, en general la encontramos como items del menú Editar y como botones de una barra de herramientas).

Para hacer el enlace haremos uso (nuevamente) de la la función Bind(). Hasta el momento hemos usado la función xrc.XRCCTRL() para obtener una referencia al widget del que queremos captar el evento, pero esto no nos servirá para el caso de los menues. La función xrc.XRCCTRL() sólo puede retornar objetos que deriven de la clase wxWindow, pero cuando esto no se cumple, como es el caso de la clase wxMenuItem, deberemos hacer uso de la función xrc.XRCID(), la cual nos devuelve el ID del widget que tenga el nombre que le pasamos como parámetro y luego usar este ID en el parámetro id de la función Bind(). El código para enlazar el ítem de menú "Mostrar bienvenida" con la función mostrar_bienvenida() será el siguiente:

self.frame.Bind(wx.EVT_MENU, self.mostrar_bienvenida, id=xrc.XRCID('mostrarBienvenida'))

Teniendo en cuenta esto, sólo me queda comentarte que el Event Binder para capturar la pulsación de un ítem de menú es wx.EVT_MENU, es por esto que el valor del primer parámetro es justamente este.

Para enlazar el segundo ítem de menú ("Salir") el código sería el siguiente:

self.frame.Bind(wx.EVT_MENU, self.salir, id=xrc.XRCID('salir'))

Si ahora juntamos todo el código, nos quedaría así:

import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')

self.frame = self.res.LoadFrame(None, 'MyFrame')

self.botonAceptar = xrc.XRCCTRL(self.frame, 'm_button1')
self.frame.Bind(wx.EVT_BUTTON, self.mostrar_bienvenida, self.botonAceptar)

self.botonCancelar = xrc.XRCCTRL(self.frame, 'm_button2')
self.frame.Bind(wx.EVT_BUTTON, self.salir, self.botonCancelar)

self.frame.Bind(wx.EVT_MENU, self.mostrar_bienvenida, id=xrc.XRCID('mostrarBienvenida'))

self.frame.Bind(wx.EVT_MENU, self.salir, id=xrc.XRCID('salir'))

self.frame.Show()

return True

def mostrar_bienvenida(self, event):
wx.MessageBox('Bienvenido!')

def salir(self, event):
self.frame.Close()

if __name__ == '__main__':
app = MiAplicacion()

app.MainLoop()

Si ejecutamos el programa, ahora podemos pulsar el botón "Aceptar" o seleccionar la opción "Mostrar bienvenida" del menú "Opciones", y obtendremos el mensaje "Bienvenido!"; y si pulsamos el botón "Cancelar" o elegimos la opción "Salir" del menú "Opciones", el programa finalizará.

Con esto doy por terminado este tutorial. Como siempre, apelo a ustedes para que si consideran necesario agregar, modificar, eliminar y/o corregir algo, lo comenten así entre todos vamos generando cada vez mas material en nuestro idioma sobre wxPython.

Saludos!

miércoles, 18 de febrero de 2009

Utilizar archivos de recursos [XRC] con wxPython (Parte II)

.
5 comentarios

Si has llegado aquí mediante un buscador o por simple casualidad, te cuento que estas en la segunda parte de un tutorial que empieza AQUI.

En la primera parte de este tutorial, vimos como cargar el archivo XRC que contiene nuestra interface y como hacer para ejecutarla. Como les anticipaba en esa primera parte, ahora veremos como hacer que nuestra interface, sea interactiva y no una simple ventana que no hace nada.

Programación orientada a eventos

Un programa realizado con wxPython encaja dentro de lo que se conoce como “programa orientado a eventos”.

A diferencia de un programa Python “clásico” (programación secuencial o estructurada), donde el programa tiene un punto de inicio y un punto de terminación específicos, y en el cual nosotros como programadores controlamos el orden en que se ejecuta el programa a través de bucles, condicionales, etc.; en el caso de la programación orientada a eventos, la aplicación es básicamente una estructura de control que recibe eventos y responde a ellos, con lo cual el flujo de ejecución del programa, esta determinado por los eventos que ocurren en el sistema (clicks, movimientos del mouse, pulsación de botones, etc.).

Un evento es algo que sucede en el sistema, y a lo que nuestra aplicación puede responder ejecutando un código determinado.

Manejando eventos con wxPython

Tras iniciarse el programa, éste pasa la mayor parte del tiempo en un bucle ocioso a la espera de eventos. Cuando el bucle termina, también termina el programa. Como vimos en la primera parte del tutorial, en wxPython, el bucle principal debe ser iniciado explícitamente por nosotros haciendo uso del método wx.App.MainLoop(), y termina automáticamente cuando la ventana de nivel superior es cerrada.

Al disparase un evento, wxPython verá si hemos definido alguna acción para responder, y de ser así, el código correspondiente a esta acción sera ejecutado.

El código de la acción a ejecutar en respuesta a un evento, es lo que se conoce como “manejador de evento” (Event Handler).

En wxPython, los eventos son representados mediante objetos que son una instancia de la clase wx.Event o una subclase de esta. La clase wx.Event se encarga solamente de las cosas en común a todos los eventos, es por esto que disponemos de una serie de subclases de wx.Event que agregan la información especifica de cada evento concreto. Por ejemplo, tenemos la clase wx.MouseEvent que nos brinda información especifica sobre un evento producido por una acción del mouse. Otro ejemplo es wx.CommandEvent, que se dispara por cosas como pulsar un botón, o seleccionar un ítem de un menú.

Los objetos de eventos, no representan todos los tipos de eventos que existen. Por nombrar un caso, cuando se produce el evento wx.MouseEvent, seria bueno disponer de algo mas especifico, que represente por ejemplo, el evento de mover el mouse, pulsar el botón derecho del mouse, soltar el botón izquierdo del mouse, etc.. Aquí es donde entran en juego los Event Binders (algo así como “enlazadores de eventos”). Los Event Binders son instancias de la clase wx.PyEventBinder, y disponemos de uno de ellos para cada tipo de evento soportado, e incluso podemos crear los nuestros. Volviendo al caso de la subclase wx.MouseEvent, disponemos de varios Event Binders que nos brindan mucha mas claridad sobre cual fue realmente el evento relacionado con el mouse, como ser wx.EVT_LEFT_DOWN, wx.EVT_LEFT_UP, wx.EVT_MIDDLE_DOWN, wx.EVT_MOTION, etc..

Para enlazar un evento concreto con su correspondiente manejador, utilizamos la función Bind(), cuya definición es la siguiente:

Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

Esta función asocia un evento y un objeto, con un manejador de eventos concreto.

El parámetro event es un Event Binder. Como vimos antes, éste representa que tipo de evento concreto queremos manejar. Ejemplos de este parámetro, serían wx.EVT_BUTTON y wx.EVT_MOTION por citar sólo dos.

El segundo parámetro (handler), es la función o el método que se encargará de manejar el evento, es decir, el "manejador de evento" (Event Handler).

El tercer parámetro (source) es el widget que origina el evento. Este parámetro, sólo es necesario cuando queremos que un mismo evento este asociado a distintos widgets.

Los parámetros id y id2, especifican el origen del evento usando un ID, en lugar del widget en si mismo. En general, estos parámetros no son requeridos, ya que estos Ids pueden ser extraídos del parámetro source. Si se especifican los dos parámetros (id y id2), lo que estamos haciendo, es vinculando un evento con un rango de widgets (esto sólo seria útil, si los widgets tienen IDs consecutivos).

Dándole “vida” a nuestra interface

A continuación pondremos en práctica lo que vimos en los párrafos anteriores. Como ejemplo, tomaremos la interface de la parte I del tutorial, que sólo incluía dos botones dentro de un BoxSizer (Vertical) que se encontraba en un Frame.


El código de nuestra aplicación, tal cual nos había quedado al finalizar la primera parte del tutorial, es el siguiente:

import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')
self.frame = self.res.LoadFrame(None, 'MyFrame')
self.frame.Show()

return True

if __name__ == '__main__':
LaAplicacion = MiAplicacion()

LaAplicacion.MainLoop()

Ahora dotaremos a nuestra ventana, de una cierta funcionalidad. Por ser el primer ejemplo, este sera realmente muy básico: haremos que al pulsar el botón “Aceptar” se nos muestra un mensaje diciendo “Bienvenido!”, y que al pulsar el botón “Cancelar” se cierre la ventana.

Agregando los manejadores de eventos

Para empezar, agregaremos dos métodos a la clase MiAplicacion, que harán las veces de manejadores de eventos. Un método sera el encargado de mostrar el mensaje de bienvenida, y el otro será el encargado de terminar la aplicación. Veamos como quedaría el código, con los dos métodos creados:
import wx
from wx import xrc

class MiAplicacion(wx.App):
def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')
self.frame = self.res.LoadFrame(None, 'MyFrame')
self.frame.Show()

return True

def mostrar_bienvenida(self, event):
wx.MessageBox('Bienvenido!')

def salir(self, event):
self.frame.Close()

if __name__ == '__main__':
LaAplicacion = MiAplicacion()

LaAplicacion.MainLoop()

Una vez que definimos los dos manejadores de eventos, debemos enlazarlos con los botones de nuestra interface (m_button1 y m_button2).

Enlazando los eventos con los manejadores de eventos

Los “enlaces” los haremos en el método OnInit() de clase MiAplicacion. En primera instancia, enlazaremos la pulsación del botón “Aceptar” con el manejador de evento mostrar_bienvenida(). El código que deberíamos agregar sería el siguiente:

self.botonAceptar = xrc.XRCCTRL(self.frame, 'm_button1')
self.frame.Bind(wx.EVT_BUTTON, self.mostrar_bienvenida, self.botonAceptar)

Como ves, para crear el enlace hicimos dos pasos:

1- Instanciamos el objeto self.BotonAceptar (no necesariamente debe tener este nombre, puedes elegir el que mas te guste) con el botón de nuestra interface (m_button1), haciendo uso del método xrc.XRCCTRL(), el cual nos devuelve una referencia al widget que corresponda con el nombre que le enviemos (en este caso m_button1).

IMPORTANTE:
Si bien en todos los ejemplos que vimos, los nombres de los widgets son lo que wxFormBuilder nos fue dando por defecto, es muy aconsejable (y casi obligatorio) que siempre cambiemos los nombres de los widgets por nombres mas “representativos”.

2- Una vez que tenemos la referencia al botón “Aceptar” (m_button1), estamos listos para hacer uso de la función Bind(). El evento que queremos detectar es la pulsación del botón, por lo que el Event Binder que usaremos será wx.EVT_BUTTON.

Para enlazar el segundo botón, sera básicamente lo mismo:

self.botonCancelar = xrc.XRCCTRL(self.frame, 'm_button2')
self.frame.Bind(wx.EVT_BUTTON, self.salir, self.botonCancelar)

A continuación les muestro todo el código junto:

import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')

self.frame = self.res.LoadFrame(None, 'MyFrame')

self.botonAceptar = xrc.XRCCTRL(self.frame, 'm_button1')
self.frame.Bind(wx.EVT_BUTTON, self.mostrar_bienvenida, self.botonAceptar)

self.botonCancelar = xrc.XRCCTRL(self.frame, 'm_button2')
self.frame.Bind(wx.EVT_BUTTON, self.salir, self.botonCancelar)

self.frame.Show()

return True

def mostrar_bienvenida(self, event):
wx.MessageBox('Bienvenido!')

def salir(self, event):
self.frame.Close()

if __name__ == '__main__':
app = MiAplicacion()

app.MainLoop()

Si ahora ejecutamos el programa y pulsamos el botón “Aceptar”, nos aparece un mensaje de bienvenida:


Por el contrario, si pulsamos el botón “Cancelar”, la ventana se cierra y el programa finaliza.

Para terminar esta parte, haremos una pequeña modificación al código, para lograr mayor claridad. La idea es que en el método OnInit() no este el código encargado de las tareas propias de la inicialización del formulario, y que sólo exista una llamada a una función que sea la encargada de estas acciones concretas. Este cambio nos permite tener un código más mantenible y claro. Con esta modificación, el código completo quedaría del siguiente modo:
import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')
self.init_frame()

return True

def init_frame(self):
self.frame = self.res.LoadFrame(None, 'MyFrame')

self.botonAceptar = xrc.XRCCTRL(self.frame, 'm_button1')
self.frame.Bind(wx.EVT_BUTTON, self.mostrar_bienvenida, self.botonAceptar)

self.botonCancelar = xrc.XRCCTRL(self.frame, 'm_button2')
self.frame.Bind(wx.EVT_BUTTON, self.salir, self.botonCancelar)

self.frame.Show()

def mostrar_bienvenida(self, event):
wx.MessageBox('Bienvenido!')

def salir(self, event):
self.frame.Close()

if __name__ == '__main__':
app = MiAplicacion()

app.MainLoop()

Por ahora dejaremos aquí para no seguir extendiendo esta parte. En la próxima, veremos como agregarle un menú a esta misma interface, y manipular sus eventos.

Hasta la siguiente entrega!

Editado:
Ya esta disponible la parte III

domingo, 15 de febrero de 2009

Utilizar archivos de recursos [XRC] con wxPython (Parte I)

.
9 comentarios

En el tutorial de Organización de widgets en wxPython, habíamos usado el IDE wxFormBuilder para crear distintos ejemplos, e incluso algunas interfaces completas. Como les dije al final de dicho tutorial, la idea es que esos proyectos que creamos, realmente podamos usarlos en una aplicación concreta, y poco a poco, “darles vida”.

En esta primera parte, vengo a (empezar a) cumplir con lo prometido, y les explicare como exportar las interfaces que creamos con wxFormBuilder, y luego cargarlas en nuestra aplicación Python.

Exportar la interface desde wxFormBuilder

wxPython nos permite cargar la interface de nuestra aplicación, desde un archivo de recursos (XRC), de modo que podamos aislar la lógica de la aplicación, de su interface visual. Un archivo de recursos, no es mas que un archivo XML que contiene toda la información sobre los elementos que conforman la interface. Si bien no vamos entrar en detalle sobre el contenido del archivo, a continuación te muestro una pequeño archivo, para que veas que realmente no hay nada mágico dentro él, y que leyéndolo con atención, es fácilmente entendible:
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
<resource xmlns="http://www.wxwindows.org/wxxrc" version="2.3.0.1">
<object class="wxFrame" name="MyFrame">
<style>wxDEFAULT_FRAME_STYLE|wxTAB_TRAVERSAL</style>
<title>Ventana de ejemplo</title>

<object class="wxBoxSizer">
<orient>wxVERTICAL</orient>

<object class="sizeritem">
<option>0</option>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>

<object class="wxButton"name="m_button1">
<label>Aceptar</label>
<default>0</default>
</object>
</object>

<object class="sizeritem">
<option>0</option>
<flag>wxALL|wxEXPAND</flag>
<border>5</border>

<object class="wxButton" name="m_button2">
<label>Cancelar</label>
<default>0</default>
</object>
</object>
</object>
</object>
</resource>

Este archivo corresponde a la siguiente interface gráfica:



Al trabajar con wxFormBuilder, cuando guardamos nuestro trabajo, veremos que el archivo que se guarda lleva la extensión .fbp (Form Builder Proyect), y no XRC (XML resource), esto es así porque wxFormBuilder esta guardando cierta información del proyecto que solo le sirve a el mismo, pero nosotros necesitamos exportar la interface de modo que sólo nos quede un archivo XRC válido para que lo pueda manipular wxPython.

Desde wxFormBuilder, podemos ver en todo momento como esta quedando el archivo XRC; para lo cual simplemente debemos pasar a la solapa “XRC” que se encuentra en la parte inferior del Editor.



Como habrás visto, además de la vista "Designer" y "XRC", existe también la vista C++, donde podremos ver el código necesario para obtener la interface en lenguaje C++. Las tres vistas (“Desginer”, “C++” y “XRC”) están sincronizadas en todo momento, e incluso puedes estar en cualquiera de ellas, y seguir construyendo la interface (agregar Sizers, botones, etc.) y ver como va quedando en C++ o XRC. Si bien las vistas "C++" y "XRC" son sólo texto, no podemos editar su contendido en forma manual.

Para obtener nuestro archivo XRC, tenemos dos caminos:

1- El primer camino, se trata de entrar en la solapa "XRC", copiar todo el código, pegarlo en un editor de texto, y guardarlo.

2- El segundo camino consiste en que el propio wxFormBuilder guarde el archivo XRC. Para que wxFormBuilder guarde el XRC en un archivo externo al del proyecto (.fbp), debemos elegir el proyecto en el “Object Tree”, y dentro de las propiedades de éste, expandimos la propiedad code_generation, y activamos la opción XRC; incluso podemos quitar la opción C++ (la única activada por defecto) dado que no estamos usando este lenguaje. Además de decirle que crée el archivo XRC, podemos especificar como queremos que se llame y donde queremos que lo guarde. Por defecto el archivo se llamará “noname.xrc”, pero si cambiamos el valor de la propiedad file, éste será el nombre del archivo generado. Para especificar donde queremos que se guarde el archivo, lo hacemos en la propiedad path, la cual por defecto sera el directorio donde esta guardado el proyecto (.fbp), es por esto que el valor inicial que aparece en la propiedad es un punto (.), el cual representa el directorio actual.

El primer camino que vimos funciona correctemente, pero no es la opción mas práctica, dado que cada vez que modificamos algo en la interface, debemos repetir todos los pasos (copiar el código, pegarlo en otro archivo y guardarlo), es por esto que es recomendable usar siempre la segunda opción.



Configurando estas opciones, ya sólo nos resta ir al menú “File” y elegir “Generate code” (o bien pulsar F8). Luego de realizar esto, tendremos el archivo XRC listo para usar en el directorio que especificamos en la propiedad path.

Ahora que tenemos el archivo de recursos, pasaremos a ver cómo lo usamos en nuestra aplicación.

Usar el archivo XRC con wxPython

En esta parte, veremos paso a paso como cargar y empezar a utilizar el archivo XRC con wxPython.

1- Importar la librería "wx"

Antes de poder comenzar cualquier aplicación que utilice wxPython, debemos importar la librería “wx”.

import wx

2- Crear el objeto “Aplicación”

Sin entrar en detalle, debemos tener claro que todo programa que utilice wxPython, necesita de dos objetos fundamentales: el objeto “aplicación” y el objeto “ventana de nivel superior”.

El objeto “aplicación” es una instancia de la clase wx.App (o una subclase de ésta). La principal función de este objeto, es la de recoger los eventos que se vayan generando y “enlazarlos” con su correspondiente manejador de evento.

El objeto “ventana de nivel superior” es un widget que no esta contenido dentro de otro. En la aplicación debe haber al menos una ventana de nivel superior, y de hecho, si se cerraran todas las ventanas de nivel superior, el programa terminaría. En general, este objeto será una subclase de wx.Frame o wx.Dialog.

Para crear el objeto “aplicación” como una instancia de la clase wx.App, simplemente escribiremos lo siguiente:

LaAplicacion = wx.App()

Este enfoque es útil, sólo cuando nuestra aplicación sa muy sencilla y posea una única ventana (luego volvere sobre este tema), pero en general, lo aconsejado es crear una subclase de wx.App como pueden ver a continuación:

class MiAplicacion (wx.App):
....
....
....

if __name__ == '__main__':

LaAplicacion = MiAplicacion()

Al crear la subclase de wx.App, podemos utilizar el método OnInit() como lugar para generar todo lo que necesitemos referido a la GUI de la aplicación. En nuestro caso, cargaremos el archivo de recurso (XRC).

3- Cargar el archivo XRC

Para cargar el archivo de recursos (XRC), usaremos la clase XmlResource. Veamos un ejemplo

self.res = xrc.XmlResource('gui_tutorial.xrc')

Aquí estamos creando un objeto llamado self.res, que es de la clase XmlResource. Al crear el objeto, le pasamos como parámetro, el nombre del archivo que contiene nuestra interface. Luego de hacer esto, ya tendremos almacenado el archivo XRC en el objeto self.res.

IMPORTANTE:
Para que la linea de código anterior funcione, debemos importar explícitamente el módulo “xrc” desde “wx”:

from wx import xrc

de no importar este módulo, deberíamos escribir lo siguiente (pero no se suele usar):

wx.xrc.XmlResource

Como dijimos antes, la idea es cargar el archivo de recurso dentro del método OnInit() en la subclase de wx.App. Combinando todo lo dicho hasta el momento, el código quedaría así:
import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')

...
...
...

if __name__ == '__main__':

LaAplicacion = MiAplicacion()

En este punto, todavía no podemos ejecutar nuestra aplicación, ya que aún no especificamos la ventana de nivel superior (entre otras cosas).

4- Especificar la “Ventana de nivel superior”

Como les había dicho antes, además del objeto “Aplicación”, el otro objeto indispensable en toda aplicación que haga uso de wxPython, es la "ventana de nivel superior". En nuestro ejemplo, la ventana de nivel superior, es el Frame llamado “MyFrame”, por lo que para cargarlo haríamos lo siguiente:

self.frame = self.res.LoadFrame(None, 'MyFrame')

Luego de esta línea, en el objeto self.frame tendríamos cargado el Frame “MyFrame” listo para usar. Como ven, el Frame lo estamos cargando desde self.res, que es donde teníamos cargado el archivo XRC. El primer parámetro indica que el Frame no depende de nadie (None). El segundo parámetro, es el nombre del Frame que queremos cargar. Como en este caso sólo tenemos un único Frame, wxPython asumirá que es la ventana de nivel superior, de lo contrario, podríamos utilizar el método SetTopWindow() de la clase wx.App.

Al cargar el Frame, éste no será visible, a menos que invoquemos su método show().

self.frame.Show()

Si juntamos todo lo visto hasta ahora, el código sería el siguiente:
import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')
self.frame = self.res.LoadFrame(None, 'MyFrame')
self.frame.Show()

return True

if __name__ == '__main__':

LaAplicacion = MiAplicacion()

Para que este ejemplo se pueda ejecutar, debemos iniciar el bucle de eventos; con lo cual le estaremos indicando a wxPython, que se quede a la escucha de eventos (pulsaciones de teclas, clicks, etc.).

Comenzar a escuchar eventos

Para iniciar el bucle de eventos, usamos el método MainLoop() de la clase wx.App (En nuestro caso, lo hacemos sobre nuestra clase LaAplicacion que es una sublcase de wx.App).

LaAplicacion.MainLoop()

El código completo, quedaría así.
import wx
from wx import xrc

class MiAplicacion(wx.App):

def OnInit(self):
self.res = xrc.XmlResource('gui_tutorial.xrc')
self.frame = self.res.LoadFrame(None, 'MyFrame')
self.frame.Show()

return True

if __name__ == '__main__':

LaAplicacion = MiAplicacion()

LaAplicacion.MainLoop()

Al ejecutar este ejemplo, obtendremos la ventana tal como la creamos con wxFormBuilder, pero ahora es “real”. Podrás clickear los botones, redimensionar la ventana, minimizarla, maximizarla, etc., aunque claro esta, aún no hace nada interesante.



Por ahora dejaremos aquí, y en la siguiente parte de este tutorial, veremos como hacer que la ventana “haga algo”. También te mostraré como hacer este mismo ejemplo, pero sin generar la subclase de wx.App, y por último, veremos como hacer un pequeño cambio en el método OnInit() para mejorar la claridad del mismo.

Nos vemos en la segunda parte!

Editado:
Ya esta disponible la parte II