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
5 comments
Gracias Pablo por el blog, es de mucha utilidad para mi, estoy siguiendo cada post paso a paso y he aprendido un montón, estoy usando wxGlade. Sigue adelante
Que bueno que te este siendo de utilidad el blog. Gracias por el aliento.
Un saludo!
Hola tenes idea de como hacer que el LoadFrame reconozca objeto personalizados ? es decir, tengo una clase ("MiBoton") que extiende el objeto Button, pero al cargar el frame me dice "No handler found for XML node 'object', Class 'MiBoton'!"
skiros@hotmail.com
Pablo, buenisimos tus tutoriales. Estoy construyendo una GUI pequeña para usar con Python. Me estuve adentrando en wxFormBuilder (v3.1, windows XP SP2) y estoy teniendo un problema con wxScrolledWindow. No encontré nada en google que pueda evitar esta consulta:
En la vista diseño, dentro de mi proyecto
agrego un Frame (size=150,140)
dentro un boxsizer (vertical)
dentro un wxScrolledWindow (size=-1,-1)
dentro un boxsixer (vertical)
y dentro 4 wx.Button's
En la vista diseño me muestra un wxScrolledWindow bien, con la funcionalidad de scroll propiamente dicha, y solo se ven dos botones, teniendo que hacer un scroll hacia abajo para ver los otros dos.
Pero cuando corro el proyecto (F5) o genero el XRC y lo levanto en un modulo Python, desaparecen las scroll's horizontal y vertical, mostrando los 4 botones.
Hay algun bug con wxScrolledWindow en wxFormBuilder?
Agradezco mucho tu respuesta. Saludos, Robertino
Me auto-respondo por si a alguién le sucede algo parecido:
Si utilizan un widget wxScrolledWindow en wxFormBuilder v3.1.70, y generan el archivo XRC (F8), el código generado no incluye el tag en el XRC, la cual es necesaria para visualizar las scrolls del wxScrolledWindow.
Solución: manualmente modifique el XRC, dentro de la etiqueta
object class="wxScrolledWindow" name="m_scrolledWindow1">
style wxVSCROLL /style
...
/object
agregué manualmente
scrollrate 5 scrollrate quedandome:
object class="wxScrolledWindow" name="m_scrolledWindow1"
style wxVSCROLL style
scrollrate 5 scrollrate
...
object
Esto hace que el archivo XRC pueda utilizarse desde Python v2.6.5 sin problemas con wxScrolledWindow.
No es la mejor solución, pero hasta ahora es lo que pude hacer. Saludos
Publicar un comentario