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

miércoles, 11 de febrero de 2009

Organización de widgets en wxPython (Parte VIII)

.
8 comentarios

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

Siguiendo con la idea de hacer “ventanas reales”, hoy será el turno de crear el cuadro de diálogo de “Ajustar balance de color” del programa Gimp (v. 2.6.1). Esta ventana tiene una organización de widgets mas compleja que la que vimos en el post anterior.

Empecemos!

La ventana “Ajustar balance de color” de Gimp

Al igual que hicimos con la ventana de gedit, lo primera será identificar cuales son los Sizers que debemos usar. Una “técnica” que puede servir para “ver” que Sizers existen en la ventana, es redimensionarla y observar el comportamiento de la misma, así que veamos ahora la ventana redimensionada:


Al redimensionar la ventana, se puede ver básicamente lo siguiente:

1- El ComboBox y los Sliders crecen a lo ancho.

2- La imagen que está en el margen superior derecho, siempre queda anclada en esa esquina.

3- Los botones “Reiniciar”, “Cancelar” y “Aceptar”, quedan “anclados” en la esquina inferior derecha; pero el botón “Ayuda” que esta en la misma fila, no cambia su posición.

4- Los dos CheckBox que están arriba del botón “Ayuda”, quedan siempre a la misma distancia de este botón.

5- Del botón “Reiniciar el rango” (inclusive) hacia arriba, la posición de los widgets no varía en forma vertical.

Para ayudarte un poco mas, y al igual que antes, te voy a mostrar la ventana original, pero con algunas pistas:


Con este análisis inicial, vamos a empezar a crear la ventana con wxFormBuilder.

Para no alargar esta parte mas de lo necesario, y ya que hemos visto como hacer muchas de las cosas que necesitaremos para realizar esta ventana (crear un Frame, un Button, un StaticText, usar Flags de alineación, etc.), voy a obviar algunas capturas y simplemente te diré el paso concreto y te mostrare la imagen con el resultado final; por ejemplo tal vez te diga: “crea dos botones, luego centralos y cambia sus textos a 'Aceptar' y 'Cancelar' respectivamente”. Para que te sirva de ayuda, también iré mostrándote el “Object Tree” en cada paso, para que puedes compararlo con lo que tu vayas haciendo. Dicho esto, veamos el desarrollo paso a paso:

Paso 1:

Para empezar crearemos un Frame, y luego como contenedor general usaremos un BoxSizer (Vertical), donde en cada fila iremos poniendo los widgets necesarios. Luego de crear el BoxSizer (Vertical); en la primera fila de éste, tenemos varias cosas por agregar. Debido a que debemos ubicar mas de un widget en esta fila, esta claro que no podemos simplemente agregarlos, ya que quedarían uno debajo del otro. Para cambiar este comportamiento, y dado que en realidad esta primera fila la podemos ver como una serie celdas una al lado de la otra, lo que agregaremos, será un BoxSizer (Horizontal) y dentro de éste, los siguientes widgets:

1- En primer lugar agregaremos la imagen del icono de la ventana: . Para esto creamos un StaticBitmap y establecemos la ruta a la imagen en file_path.


2- Al lado del icono de la ventana, tenemos que crear dos filas donde habrá un StaticText en cada una de ellas, así que vamos a usar un BoxSizer (Vertical), y de este modo, al agregar los dos StaticText, estos quedarán uno debajo del otro. El StaticText superior tendrá el texto "Ajustar el balance de colores" con una fuente de 16px y en negrita. El StaticText inferior tendrá el texto "Fondo-6 (Via.png)" con una fuente de 8px. Al agregar el BoxSizer (Vertical) y los dos StaticText con sus respectivos textos y formatos, el Editor quedaría así:


3- Al final de esta fila, debemos agregar un nuevo StaticBitmap, que representa la vista previa de la imagen a la que Gimp le ajustara el balance de colores: . El StaticBitmap se lo debemos agregar al BoxSizer (Horizontal) [bsizer2], ya que la imagen esta fuera de las dos filas de la columna del medio. Al igual que antes, una vez agregado el StaticBitmap, cambiamos la ruta (file_path) para que apunte a la imagen que queremos. Presta especial atención al “Object Tree” en la siguiente imagen, para que se entienda mejor lo que digo:


En esta imagen te remarque en rojo la relación entre el “Object Tree” y el Editor. Puedes ver que la Fila 0 del BoxSizer contenedor (bsizer1), es un BoxSizer (Horizontal), que dentro tiene 3 columnas, done la primera y la última son StaticBitmaps y la del medio, es un BoxSizer (Vertical) con dos filas, que tienen un StaticText cada una.

Por el momento terminamos con la primera fila, así que vamos al paso 2.

Paso 2:

Ya fuera del BoxSizer (Horizontal) [bsizer2], agregaremos una simple linea divisoria. Para agregarla, debes ir a la solapa “Common” y clickear sobre el botón “wxStaticLine”:


En la imagen anterior, pueden ver que en el “Object Tree”, contraje el BoxSizer que representa la fila 0, y de este modo podemos notar que el StaticLine esta por fuera del BoxSizer bSizer2. Esto de ir contrayendo los Sizers en el “Object Tree”, es una buena practica para lograr mayor claridad.

Paso 3:

La tercera fila del BoxSizer (Vertical) [bsizer1] debe contener cuatro widgets uno al lado del otro. Una vez mas, para lograr esta distribución, usaremos un BoxSizer (Horizontal) donde agregaremos los widgets que irán en cada columna. Los widgets que debemos agregar son los siguientes:

1- Un StaticText con el texto “Ajustes prefijados:”


2- Un ComboBox

Este widget lo encontramos en la solapa “Common”. Como vimos en el pequeño análisis del comportamiento de la ventana, al ser redimensionada, este ComboBox debe crecer a lo ancho, es por esto (y por estar contenido en un BoxSizer [Horizontal]) que debemos establecer la propiedad proportion en “1”.


3- Un BitmapButton

Para crear este widget lo hacemos desde la solapa “Common”. Una vez agregado; para establecer cual es la imagen que debe mostrar, lo hacemos igual que con el StaticBitmap, o sea en file_path. Un detalle que podemos notar, es que al crear el BitmapButton, este posee un borde que lo rodea dando la apariencia de ser un botón, pero en la ventana original, no existen bordes, sino que parecen simples imágenes. Para cambiar este aspecto, tenemos que establecer la propiedad window_style, con el valor wxNO_BORDER activado:

4- Otro BitmapButton con su respectiva imagen establecida en file_path, y sin el borde clásico de los botones:

Paso 4:

En este paso agregaremos cinco widgets directamente en el sizer que hace de contenedor principal (bSizer1). Los widgets que agregaremos serán los siguientes:

1- En primer lugar crearemos un StatiText con el texto “Seleccione el rango para ajustar”. Para que se asimile mas a la fuente original, ponemos el texto en negrita.


2- En segundo lugar, agregamos 3 widgets de la clase RadioButton desde la solapa “Common” . Para cambiar el texto que muestra cada uno de ellos, lo hacemos editando el valor de la propiedad label. El primer RadioButton tendrá el texto “Sombras”, el segundo dirá “Tonos medios” y el tercero “Puntos de luz”.


3- Para terminar este paso, agregamos otro StaticText con el texto “Ajustar los niveles de color”, y lo ponemos en negrita.


Paso 5:

En este paso usaremos un FlexGridSizer con cuatro columnas y tres filas. El FlexGridSizer lo agregaremos en el contenedor principal (bSizer1). Una vez creado, vamos a agregar los siguientes widgets:

1- Un StaticText con el texto “Cian”.


2- Un Slider (lo encuentras en la solapa “Common”).


3- Un StaticText con el texto “Rojo”.


4- Un SpinControl (lo encuentras en la solapa “Additional”).


5- Agregamos la misma secuencia de widgets anterior (de la 1 a la 4), pero con los textos “Magenta” y “Verde” para los StaticText.


6- Hacemos lo mismo que en el paso 5, pero con los textos “Amarillo” y “Azul” para los StaticText.


Como habrás podido notar, los sliders no crecen en forma horizontal como esperaríamos, así que para que esto suceda, y dado que están dentro de la columna 1 (la segunda) de un FlexGridSizer, lo que debemos hacer es especificar el valor “1” en la propiedad growablecols, y luego activar el flag wxEXPAND de los 3 Sliders.

Paso 6:

Agregamos un botón (fuera del FlexGridSizer) con el texto “Reiniciar el rango” y lo alineamos a la derecha.

Paso 7:

Agregamos dos CheckBox con los textos “Conservar la luminosidad” y “Vista previa” respectivamente (Para cambiar el texto de un checkbox, también lo hacemos desde la propiedad label).

Paso 8:

En este paso, vamos a crear la botonera de la parte inferior. Lo primero que haremos es crear un BoxSizer (Horizontal), al cual le cambiaremos su propiedad proportion a “0”. A este BoxSizer, le agregamos un botón con el texto “Ayuda”.


Si bien podríamos agregar los otros tres botones restantes (“Reinciar”, “Cancelar” y “Aceptar”) directamente dentro del BoxSizer (bSizer5), si miras nuevamente la ventana original redimensionada, estos tres botones se deben mover juntos hacia la derecha, dejando sólo al de “Ayuda” a la izquierda. Para lograr esto, agregaremos un BoxSizer (Vertical), que si bien sólo tendrá una fila, aprovecharemos la posibilidad de que podemos cambiar la alineación horizontal de los widgets que este contenga en su interior. Para mover los tres botones juntos dentro del BoxSizer (Vertical), los ubicaremos en un GridSizer de una sola fila y tres columnas. Para verlo mejor, presta atención al “Object Tree”:


Ahora desactivaremos el flag wxEXPAND del GridSizer que contiene los tres botones, y también alineamos el GridSizer a la derecha:

Paso 9:

Ya tenemos todos los widgets en su lugar, así que ahora haremos algunos últimos retoques.

1- Establecemos la propiedad proportion en “0” del StaticBox bSizer2, para que no crezca en su altura.

2- También establecemos la propiedad proportion en “0” del StaticBox bSizer4 y el del StaticBox bSizer5, para que tampoco crezcan en su altura.


Hasta aquí esta casi todo terminado excepto por un detalle: si en este momento ejecutamos la ventana (lo cual lo puedes hacer, pulsando F5 desde wxFormBuilder), y luego la redimensionamos, verás algo como esto:


Prestando atención, si comparas esta ventana con la original, notaras que hay un error: el botón “Reiniciar el rango” no debe bajar al agrandarse la ventana, sólo debe ubicarse a la derecha, pero no bajar.

Como una solución a este problema, ubicaremos el botón dentro de un nuevo BoxSizer (Vertical). La forma mas simple de hacer esto es seleccionar el botón (m_button1) en el “Object Tree”, pulsar el botón derecho del mouse, y elegir la opción “Move into a new wxBoxSizer”:



Por último, establecemos la propiedad proportion en “0” del FlexGridSizer.


Si ahora ejecutamos la ventana y la redimensionamos, vemos que se comporta como la ventana original:


Listo, hemos terminado con esta interface. Al igual que en la ventana que hicimos en el post anterior, he tratado de concentrarme mas en lo organización de los widgets que en su apariencia, así que puede que la “copia” no sea 100% exacta en lo que se ve. Para aproximarla mas aun a la original, puedes jugar con los “bordes” de los widgets y así modificar el espaciado entre estos.

A continuación les muestro el “Object Tree” completamente expandido:


IMPORTANTE:
Si ya has estado practicando todo lo que hemos visto, o bien todavía no lo hiciste pero lo quieres intentar, podrás notar que wxFormBuilder aún es un poco inestable, así que deberías ir guardando tu trabajo a cada paso importante que vas dando.

Conclusión

Creo que con estos dos ejemplos, tienen los lineamientos para poder crear la interface que te propongas. De este modo, doy por terminado este tutorial, pero dejo abierta la posibilidad de crear dos ventanas de algún programa que ustedes propongan. En otro tutorial (seguramente, de varias partes) veremos como guardar estas interfaces como un archivo XML, y como cargarlas desde una aplicación Python, también les mostrare como hacer interfaces con las que podamos interactuar, y otros temas relacionados.

Espero que esta información te haya sido de utilidad. Me gustaría que comentes tu experiencia, para poder mejorar los tutoriales futuros.

Hasta la próxima!

Editado
Ya esta disponible la primera parte del tutorial sobre como utilizar archivos de recursos [XRC] con wxPython