jueves, 23 de diciembre de 2010

Crear una [pequeña] aplicación completa usando wxPython (Parte 7)

.
6 comentarios

Continuando con nuestro proyecto, en esta oportunidad trabajaremos en la búsqueda dentro del catalogo, para lo cual comenzaremos por hacer algunas modificaciones en la interface.

Modificando la interface

Antes de empezar con los cambios, les voy a comentar como vamos a implementar la búsqueda. En realidad, no sera nada revolucionario, simplemente la idea es que al pulsar la opción “Buscar”, aparezca una especie de “barra de búsqueda” que estará ubicada entre la barra de herramientas y el listado de películas del frame principal.


Como primera modificación, haremos que el botón “Buscar” de la barra de herramientas (toolBuscarPelicula) sea de tipo wxITEM_CHECK.

Este pequeño cambio, es para que cuando el botón este pulsado, la barra de búsqueda esté visible, y que cuando el botón no esté pulsado, esta desaparezca.

A continuación agregaremos la barra de búsqueda, para lo cual añadiremos un panel llamado panelBarraBusqueda dentro del BoxSizer que contiene al listado de las películas (bSizer1). Deben tener en cuenta, que el panel estará “arriba” del listado (listPeliculas).


Dentro del panel que agregamos, añadimos un BoxSizer (Horizontal) que llamaremos bSizerBarraDeHerramientas, en el cual distribuiremos los siguientes widgets:

1- staticTextBuscar (wxStaticText)
2- textCtrlBuscar (wxTextCtrl)
3- staticTextCriterio (wxStaticText)
4- choiceCriterio (wxChoice)
5- bpButtonBuscar (wxBitmapButton)

Una vez agregado todos los widgets, cambiamos la propiedad label del staticTextBuscar a “Buscar:” y la del staticTextCriterio a “Criterio:”. También establecemos la imagen de bpButtonBuscar.


Ahora establecemos la propiedad proportion de panelBarraBusqueda en 0.


También ponemos en 0 la propiedad proportion de la caja de texto (textCtrlBuscar).


Por último, activamos el flag wxALIGN_CENTER_VERTICAL de todos los widgets que estan dentro de la barra de búsqueda.


Dado que la barra de búsqueda sólo se debe mostrar cuando se pulsa el botón “Buscar”, en principio ésta deberá permanecer oculta; es por esto que haremos que el panel (panelBarraBusqueda) no sea visible.


Ya con esto terminado, estamos listos para seguir con nuestro código.

Mostrando y ocultando la barra de búsqueda

Si bien aún no implementamos la posibilidad de mostrar u ocultar la barra de búsqueda, si hemos creado el método onBuscarPelicula en el controlador de la Vista Principal, que es invocado al pulsar el botón “Buscar” de la barra de herramientas. Hasta ahora, al pulsar el botón, sólo recibimos un mensaje con el texto “Buscar una película”, así que vamos a modificar este método para que cumpla nuestro objetivo.

Al invocar este método, lo primero que debemos tener en cuenta es el estado en que se encuentra el botón “Buscar” (Pulsado o No pulsado). Si hiciéramos una primera aproximación de este método seria algo así:
def onBuscarPelicula(self, evt):
barraDeHerramientas = self.vista.frame.GetToolBar()
estado = barraDeHerramientas.GetToolState(id=xrc.XRCID('toolBuscarPelicula'))

if estado:
wx.MessageBox("Activo el botón-> Mostrar la barra de búsqueda")
else:
wx.MessageBox("Desactivo el botón-> Ocultar la barra de búsqueda")
Básicamente, lo que estamos haciendo, es obtener una referencia a la barra de herramientas, de modo que luego podamos averiguar el estado del botón toolBuscarPelicula, haciendo uso del método GetToolState() y asi podemos actuar en consecuencia (en esta primera versión, sólo mostramos un mensaje que indica si deberíamos o no mostrar la barra). Para que efectivamente se muestre u oculte la barra de búsqueda, crearemos un nuevo método llamado mostrarBarraDeBusqueda() en la Vista Principal.
def mostrarBarraDeBusqueda(self, mostrar=True):
barraDeBusqueda = xrc.XRCCTRL(self.frame, 'panelBarraDeBusqueda')
barraDeBusqueda.Show(mostrar)

cajaDeBusqueda = xrc.XRCCTRL(self.frame, 'textCtrlBuscar')
cajaDeBusqueda.Clear()

self.frame.Layout()
Ahora haremos que el método onBuscarPelicula(), deje de mostrar los mensajes y que pase a llamar a mostrarBarraDeBusqueda().
def onBuscarPelicula(self, evt):
barraDeHerramientas = self.vista.frame.GetToolBar()
estado = barraDeHerramientas.GetToolState(id=xrc.XRCID('toolBuscarPelicula'))

if estado:
self.vista.mostrarBarraDeBusqueda()
else:
self.vista.mostrarBarraDeBusqueda(False)
Ya con este comportamiento implementado, pasemos a la búsqueda propiamente dicha.

Buscando en el catalogo

Al buscar en el catalogo, nuestro objetivo sera que los resultados se carguen en el mismo listado donde hasta ahora siempre han estado las películas, es decir que la acción de buscar es un caso particular de cargar el listado de películas, donde en vez de cargar todas las películas como hasta ahora, solo debemos cargar las que coincidan con ciertos criterios de búsqueda.

Para obtener la lista de películas completa, nosotros usamos el método ObtenerListadoDePeliculas() de nuestro Modelo, por lo que si le hacemos algunas modificaciones a este, lograremos que devuelva sólo las que coincidan con la búsqueda del usuario.

A continuación les muestro el método ObtenerListadoDePeliculas tal como esta hasta ahora.
def ObtenerListadoDePeliculas(self):
self.conectar()
self.cursor.execute("SELECT * FROM tblPeliculas ORDER BY TituloPelicula")

listado = self.cursor.fetchall()

self.desconectar()

return listado
Como se podrán imaginar, debemos modificar la consulta SQL, de modo que haya una clausula WHERE donde este el criterio de búsqueda. Para llevar a cabo esta tarea, empezaremos por agregar dos parámetros opcionales que llamaremos textoBuscado y criterioBusqueda.
def ObtenerListadoDePeliculas(self, textoBuscado="", criterioBusqueda=""):
Si el parámetro textoBuscado esta en blanco, simplemente retornamos todo el listado como hasta ahora, de lo contrario modificaremos la consulta SQL. Si criterioBusqueda esta en blanco, la búsqueda la realizaremos en todos los campos, de lo contrario, solo la haremos en el campo especificado. Teniendo todo esto en cuenta, el método ObtenerListadoDePeliculas() quedaría así.
def ObtenerListadoDePeliculas(self, textoBuscado="", criterioBusqueda=""):
self.conectar()

if textoBuscado:
if criterioBusqueda:
consulta = "SELECT * FROM tblPeliculas WHERE "+ criterioBusqueda + " LIKE '%" + textoBuscado + "%' ORDER BY TituloPelicula"
else:
consulta = "SELECT * FROM tblPeliculas WHERE TituloPelicula LIKE '%" + textoBuscado + "%' OR Anio LIKE '%" + textoBuscado + "%' ORDER BY TituloPelicula"
else:
consulta = "SELECT * FROM tblPeliculas ORDER BY TituloPelicula"

self.cursor.execute(consulta)
listado = self.cursor.fetchall()

self.desconectar()

return listado
Debemos tener en cuenta, que este método sólo obtiene el listado, pero el método cargarListadoDePeliculas() de la Vista Principal, es el que finalmente carga el listado en la lista (listPeliculas), es por esto que debemos modificarlo para que este determine cuales son parámetros que le tendrá que enviar al método ObtenerListadoDePeliculas() del Modelo. Con las modificaciones quedaría así (los cambios están en color rojo).
def cargarListaoDePeliculas(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)

self.listaDePeliculas.InsertColumn(0, 'Titulo', format=wx.LIST_FORMAT_LEFT, width=-1)
self.listaDePeliculas.InsertColumn(1, 'Anio', format=wx.LIST_FORMAT_LEFT, width=-1)

cajaDeBusqueda = xrc.XRCCTRL(self.frame, 'textCtrlBuscar')
textoBuscado = cajaDeBusqueda.GetValue()

peliculas = self.app.modelo.ObtenerListadoDePeliculas(textoBuscado)

index = 0

for pelicula in peliculas:
item = self.listaDePeliculas.InsertStringItem (index, str(pelicula[1]))

self.listaDePeliculas.SetItemData(item, pelicula[0])
self.listaDePeliculas.SetStringItem(index, 1, str(pelicula[2]))

index+=1

self.listaDePeliculas.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.listaDePeliculas.SetColumnWidth(1, wx.LIST_AUTOSIZE)
Con esto estamos en condiciones de ejecutar las búsquedas, por lo que procederemos a hacer que las mismas se ejecutan tras pulsar el botón correspondiente (bpButtonBuscar). Para esto, agregaremos el método onEjecutarBusqueda() en el controlador de la Vista Principal, el cual simplemente llamara a cargarListadoDePeliculas().
def onEjecutarBusqueda(self, evt):
self.vista.cargarListaoDePeliculas()
Ahora enlazamos este método con la pulsación del botón de Ejecutar Búsqueda, en el método __init__() del controlador de la Vista Principal.
botonEjecutarBusqueda = xrc.XRCCTRL(self.vista.frame, 'bpButtonBuscar')
self.vista.frame.Bind(wx.EVT_BUTTON, self.onEjecutarBusqueda, botonEjecutarBusqueda)
En este punto, ya podemos hacer búsquedas desde nuestra aplicación, pero como les había dicho, aún no tenemos en cuenta el criterio. Obviamente para que el usuario pueda seleccionar el criterio, primero tenemos que cargar el combo (choiceCriterio) con los campos validos. Esto lo haremos en el método __init__() del controlador de la Vista Principal.
comboCriteriosDeBusqueda = xrc.XRCCTRL(self.vista.frame, 'choiceCriterio')
comboCriteriosDeBusqueda.AppendItems(["Todo", "Título", "Año"])
comboCriteriosDeBusqueda.SetSelection(0)
Hecho esto, volvemos a modificar el método cargarListadoDePeliculas() para tenga en cuenta el criterio de búsqueda.
def cargarListaoDePeliculas(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)

self.listaDePeliculas.InsertColumn(0, 'Titulo', format=wx.LIST_FORMAT_LEFT, width=-1)
self.listaDePeliculas.InsertColumn(1, 'Anio', format=wx.LIST_FORMAT_LEFT, width=-1)

cajaDeBusqueda = xrc.XRCCTRL(self.frame, 'textCtrlBuscar')
textoBuscado = cajaDeBusqueda.GetValue()

comboCriteriosDeBusqueda = xrc.XRCCTRL(self.frame, 'choiceCriterio')
indiceCriterio = comboCriteriosDeBusqueda.GetSelection()

criterioBusqueda=""

if(indiceCriterio==1):
criterioBusqueda="tituloPelicula"
elif(indiceCriterio==2):
criterioBusqueda="Anio"

peliculas = self.app.modelo.ObtenerListadoDePeliculas(textoBuscado, criterioBusqueda)

index = 0

for pelicula in peliculas:
item = self.listaDePeliculas.InsertStringItem (index, str(pelicula[1]))

self.listaDePeliculas.SetItemData(item, pelicula[0])
self.listaDePeliculas.SetStringItem(index, 1, str(pelicula[2]))

index+=1

self.listaDePeliculas.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.listaDePeliculas.SetColumnWidth(1, wx.LIST_AUTOSIZE)
Con estas modificaciones, la búsqueda cumpliría nuestro objetivo de que el usuario pueda buscar por el titulo de la película, el año o por todos los campos.

Claramente, este sistema de búsqueda es muy simple, pero nos sirve para nuestra pequeña aplicación. Seguramente, seria bueno que los criterios de búsqueda se obtengan en forma dinámica, leyendo los campos directamente desde la base de datos, al igual la posibilidad de hacer búsquedas exactas, entre otras opciones, pero esto esta fuera del alcance de este tutorial (lo que no quita que en otros tutoriales mas específicos, lo veamos).

Si bien nuestro sistema de búsqueda esta funcionando, aun nos falta que al ocultarse la barra de búsqueda, se vuelva a cargar todo el listado. Para lograr esto, haremos una pequeña modificación en el método onBuscarPelicula() del controlador de la Vista Principal.
def onBuscarPelicula(self, evt):
barraDeHerramientas = self.vista.frame.GetToolBar()
estado = barraDeHerramientas.GetToolState(id=xrc.XRCID('toolBuscarPelicula'))

if estado:
self.vista.mostrarBarraDeBusqueda()
else:
self.vista.mostrarBarraDeBusqueda(False)
self.vista.cargarListaoDePeliculas()
Con esto, damos por culminada esta séptima entrega, y nos acercamos al final de este tutorial. En la octava parte, trabajaremos en algunos puntos sobre mejoras de usabilidad y corregiremos algunos bugs que seguramente hay. En cuanto al tema de los bugs, los invito a que busquen por su cuenta y me los hagan saber, así entre todos podemos encontrar la mayor cantidad posible.

Hasta la próxima!

viernes, 3 de diciembre de 2010

Crear una [pequeña] aplicación completa usando wxPython (Parte 6)

.
0 comentarios

Esta entrega no será para hacer modificaciones en nuestra aplicación, sino, tan solo para concentrar todo el código visto hasta ahora en un solo post, de manera que puedan ver mejor el contenido de cada archivo que compone nuestro proyecto.

controladorEditorDePelicula.py

from vistas.vistaEditorDePelicula import VistaEditorDePelicula
from wx.lib.pubsub import Publisher

import wx
from wx import xrc
import pelicula

class ControladorEditorDePelicula:
def __init__(self, app, parent, agregar=True):
self.app = app

self.vistaPrincipal = parent

self.vista = VistaEditorDePelicula(self.vistaPrincipal.frame, self.app, agregar)

Publisher.subscribe(self.onSeCambioItemSeleccionado, 'cambio_item_seleccionado')

btnAgregarPelicula = xrc.XRCCTRL(self.vista.Dialog, 'btnAceptar')

self.vista.Dialog.Bind(wx.EVT_CLOSE, self.onCerrarEditorDePelicula, self.vista.Dialog)

if(agregar):
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onAgregarPelicula, btnAgregarPelicula)
else:
self.vistaPrincipal.habilitarEdicion(False)

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onModificarPelicula, btnAgregarPelicula)

self.cargarPeliculaSeleccionadaEnVistaPrincipal()

btnPeliculaAnterior = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton2')
btnPeliculaSiguiente = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton3')
btnPrimeraPelicula = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton1')
btnUltimaPelicula = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton4')

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarPeliculaAnterior, btnPeliculaAnterior)
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarSiguientePelicula, btnPeliculaSiguiente)
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarPrimeraPelicula, btnPrimeraPelicula)
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarUltimaPelicula, btnUltimaPelicula)

self.vista.Mostrar(agregar)

def onAgregarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.agregarPelicula(infoPelicula.getTitulo(), infoPelicula.getAnio())

self.vistaPrincipal.seleccionarPrimeraPelicula()

def onModificarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.modificarPelicula(self.idPelicula, infoPelicula.getTitulo(), infoPelicula.getAnio())

def onCargarSiguientePelicula(self, evt):
self.vistaPrincipal.seleccionarSiguientePelicula()

def onCargarPeliculaAnterior(self, evt):
self.vistaPrincipal.seleccionarAnteriorPelicula()

def onCargarPrimeraPelicula(self, evt):
self.vistaPrincipal.seleccionarPrimeraPelicula()

def onCargarUltimaPelicula(self, evt):
self.vistaPrincipal.seleccionarUltimaPelicula()

def onSeCambioItemSeleccionado(self, evt):
self.cargarPeliculaSeleccionadaEnVistaPrincipal()

def cargarPeliculaSeleccionadaEnVistaPrincipal(self):
self.idPelicula = self.app.controladorVistaPrincipal.vista.getIDItemSeleccionado()

self.vista.cargarPelicula(self.idPelicula)

def onCerrarEditorDePelicula(self, evt):
self.vistaPrincipal.habilitarEdicion(True)

Publisher.unsubscribe(self.onSeCambioItemSeleccionado, 'cambio_item_seleccionado')
self.vista.Dialog.Destroy()

controladorVistaPrincipal.py

import wx

from wx import xrc
from wx.lib.pubsub import Publisher

from vistas.vistaPrincipal import VistaPrincipal
from controladores.controladorEditorDePelicula import ControladorEditorDePelicula

class ControladorVistaPrincipal:
def __init__(self, app):
self.app = app

self.vista = VistaPrincipal(app)

self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarAgregarPelicula, id=xrc.XRCID('itemAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onMostrarEditorDePelicula, id=xrc.XRCID('itemEditarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onEliminarPelicula, id=xrc.XRCID('itemEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_MENU, self.onSalir, id=xrc.XRCID('itemSalir'))

self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarAgregarPelicula, id=xrc.XRCID('toolAgregarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onMostrarEditorDePelicula, id=xrc.XRCID('toolEditarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onEliminarPelicula, id=xrc.XRCID('toolEliminarPelicula'))
self.vista.frame.Bind(wx.EVT_TOOL, self.onBuscarPelicula, id=xrc.XRCID('toolBuscarPelicula'))

Publisher.subscribe(self.seModificoElModelo, 'pelicula_agregada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_modificada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_eliminada')

listadoPeliculas = xrc.XRCCTRL(self.vista.frame, 'listPeliculas')
self.vista.frame.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onSeCambioItemSeleccionado, listadoPeliculas)

self.vista.cargarListaoDePeliculas()
self.vista.seleccionarPrimeraPelicula()

self.vista.Mostrar()

def onMostrarAgregarPelicula(self, evt):
self.editorDePeliculas = ControladorEditorDePelicula(self.app, self.vista, True)

def onMostrarEditorDePelicula(self, evt):
self.editorDePeliculas = ControladorEditorDePelicula(self.app, self.vista, False)

def onEliminarPelicula(self, evt):
id_pelicula = self.vista.getIDItemSeleccionado()

self.app.modelo.eliminarPelicula(id_pelicula)
wx.MessageBox("La pelicula se ha eliminado")

def onBuscarPelicula(self, evt):
wx.MessageBox("Buscar una pelicula")

def seModificoElModelo(self, mensaje):
self.vista.cargarListaoDePeliculas()

def onSalir(self, evt):
self.app.Exit()

def onSeCambioItemSeleccionado(self, evt):
Publisher.sendMessage("cambio_item_seleccionado", None)

vistaEditorDePelicula.py

import wx
from wx import xrc
import pelicula

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
titulo_pelicula.SetFocus()

barraNavegacion = xrc.XRCCTRL(self.Dialog, 'm_panel2')
barraNavegacion.Show(False)

self.Dialog.Fit()

btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def Mostrar(self, modal=True):
if modal:
self.Dialog.ShowModal()
else:
self.Dialog.Show()

def getInfoPelicula(self):
infoPelicula = pelicula.Pelicula()

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

infoPelicula.setTitulo(titulo_pelicula.GetValue())
infoPelicula.setAnio(anio_pelicula.GetValue())

return infoPelicula

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
titulo_pelicula.SetFocus()

vistaPrincipal.py

import wx
from wx import xrc

class VistaPrincipal:
def __init__(self, app):
self.app = app

self.res = self.app.res
self.frame = self.res.LoadFrame(None, 'FramePrincipal')

def Mostrar(self):
self.frame.Show()

def cargarListaoDePeliculas(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)

self.listaDePeliculas.InsertColumn(0, 'Titulo', format=wx.LIST_FORMAT_LEFT, width=-1)
self.listaDePeliculas.InsertColumn(1, 'Anio', format=wx.LIST_FORMAT_LEFT, width=-1)

peliculas = self.app.modelo.ObtenerListadoDePeliculas()

index = 0
for pelicula in peliculas:
item = self.listaDePeliculas.InsertStringItem (index, str(pelicula[1]))
self.listaDePeliculas.SetItemData(item, pelicula[0])

self.listaDePeliculas.SetStringItem(index, 1, str(pelicula[2]))

index+=1

self.listaDePeliculas.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.listaDePeliculas.SetColumnWidth(1, wx.LIST_AUTOSIZE)

def getIDItemSeleccionado(self):
itemIndex = self.listaDePeliculas.GetFirstSelected()

return self.listaDePeliculas.GetItemData(itemIndex)

def seleccionarItem(self, indiceItem):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

itemActual = self.listaDePeliculas.GetFirstSelected()

self.listaDePeliculas.Select(itemActual, False)
self.listaDePeliculas.Select(indiceItem, True)
self.listaDePeliculas.Focus(indiceItem)

def seleccionarPrimeraPelicula(self):
self.seleccionarItem(0)

def seleccionarUltimaPelicula(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

indiceUltimoItem = self.listaDePeliculas.GetItemCount() - 1

self.seleccionarItem(indiceUltimoItem)

def seleccionarSiguientePelicula(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')

itemActual = self.listaDePeliculas.GetFirstSelected()
indiceUltimoItem = self.listaDePeliculas.GetItemCount() - 1

if(itemActual < indiceUltimoItem ):
self.seleccionarItem(itemActual + 1)

def habilitarEdicion(self, habilitado=True):
barra_de_herramientas = self.frame.GetToolBar()
barra_de_herramientas.Enable(habilitado)

menu_catalogo = self.frame.GetMenuBar()
menu_catalogo.EnableTop(0, habilitado)

modelo.py

from wx.lib.pubsub import Publisher
import MySQLdb
import pelicula

class Model:

def conectar(self):
self.cnn = MySQLdb.connect(host='localhost', user='user', passwd='password', db='catalogo_peliculas_db')
self.cursor = self.cnn.cursor()

def desconectar(self):
self.cnn.close()
self.cursor.close()

def agregarPelicula(self, titulo_pelicula, anio):
self.conectar()
self.cursor.execute("INSERT INTO tblPeliculas(TituloPelicula, Anio) VALUES('" + titulo_pelicula + "'," + anio + ")")

self.desconectar()

Publisher.sendMessage("pelicula_agregada", None)

def modificarPelicula(self, id_pelicula, titulo_pelicula, anio):
self.conectar()
self.cursor.execute("UPDATE tblPeliculas SET TituloPelicula='" + titulo_pelicula + "', Anio=" + str(anio) + " WHERE IDPelicula=" + str(id_pelicula))

self.desconectar()

Publisher.sendMessage("pelicula_modificada", None)

def eliminarPelicula(self, id_pelicula):
self.conectar()
self.cursor.execute("DELETE FROM tblPeliculas WHERE IDPelicula=" + str(id_pelicula))

self.desconectar()

Publisher.sendMessage("pelicula_eliminada", None)

def ObtenerListadoDePeliculas(self):
self.conectar()

self.cursor.execute("SELECT * FROM tblPeliculas ORDER BY TituloPelicula")

listado = self.cursor.fetchall()

self.desconectar()

return listado

def obtenerInformacionDePelicula(self, idPelicula):
self.conectar()

self.cursor.execute("SELECT * FROM tblPeliculas WHERE IDPelicula = " + str(idPelicula))

resultado = self.cursor.fetchone()

infoPelicula = pelicula.Pelicula()

infoPelicula.setTitulo(str(resultado[1]))
infoPelicula.setAnio(str(resultado[2]))

self.desconectar()

return infoPelicula

catalogo.py

import wx
from wx import xrc
from controladores.controladorVistaPrincipal import ControladorVistaPrincipal
from modelo import Model

class CatalogoDePeliculas(wx.App):
def OnInit(self):
self.modelo = Model()
self.res = xrc.XmlResource('gui2.xrc')

self.controladorVistaPrincipal = ControladorVistaPrincipal(self)

return True

app = CatalogoDePeliculas()
app.MainLoop()

pelicula.py

class Pelicula:
def __init__(self):
self.titulo = ""
self.anio = ""

def setTitulo(self, titulo):
self.titulo = titulo

def getTitulo(self):
return self.titulo

def setAnio(self, anio):
self.anio = anio

def getAnio(self):
return self.anio
Este es todo el código de nuestro Catalogo hasta aquí. En la próxima parte, trabajaremos en la implementación de la búsqueda de películas.

Saludos!

martes, 16 de noviembre de 2010

Crear una [pequeña] aplicación completa usando wxPython (Parte 5)

.
0 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.

En esta entrega comenzaremos a dotar a nuestro Catálogo de una mayor “usabilidad”; concretamente vamos a implementar la posibilidad de que al estar modificando una película en el Editor, se nos permita navegar hacia la primera, última, anterior y siguiente película (a través de una típica barra de navegación con cuatro botones), sin necesidad de cerrar la ventana de edición para modificar otra película. Para hacerlo mas flexible aún, además de la barra de navegación en el Editor de películas, permitiremos que cuando el usuario esté en la ventana de edición, pueda seleccionar directamente cualquier película en la Vista Principal, y que ésta se cargue automáticamente en el Editor sin necesidad de cerrarlo.

Modificando la interface del Editor de películas con wxFormBuilder

Lo primero que haremos antes de avanzar con el código, sera modificar nuestro archivo XRC (gui.xrc), para agregar la barra de navegación que les mencione antes. A continuación les muestro una captura de pantalla del Editor y una del Árbol de objetos de wxFormBuilder, con el Dialogo del Editor de Películas modificado:

Viendo el Árbol de objetos, podemos notar que la barra de navegación, consta de cuatro botones (wxBitmapButton) contenidos en dos BoxSizer (Horizontales), que a su vez se encuentran contenidos en un GridSizer, que esta dentro de un Panel. El GridSizer tiene una sola fila y dos columnas (rows=1, cols=2). El boxSizer bSizer41 (el que contiene los botones “Primero” y “Anterior”) posee la flag wxExpand activada. El boxSizer bSizer5 (el que contiene los botones “Ultimo” y “Siguiente”) posee la flag wxALIGN_RIGTH activada y la flag wxExpand desactivada. El Panel posee la flag wxExpand activada, la propiedad proportion en “1” y tiene un borde de 3px a cada uno de sus lados. Para poner las imágenes en los botones, simplemente seleccionamos la ruta al archivo en el file_path de la propiedad bitmap de los wxBitmapButton.

Claramente los nombres de los distintos widgets no son los ideales, así que les dejo de tarea a ustedes, ponerles nombres mas representativos.

Con esto, ya estamos en condiciones de seguir trabajando con el código.

Agregando funcionalidad

Ahora que ya tenemos nuestra barra de navegación en la interface del Editor de Películas, podemos comenzar a trabajar para que esta cumpla su función, pero antes de meternos de lleno con esta tarea, vamos a implementar la función que nos permitirá que al estar abierto el Editor de películas, podamos seleccionar otra película en la Vista Principal y ésta se cargue en el Editor.

Para implementar esta característica, empezaremos por agregar un método llamado onSeCambioItemSeleccionado() en el Controlador del Editor de Películas. Básicamente, este método se encargará de "tramitar" la llamada para cargar la información de la película que esté seleccionada en la Vista Principal, dentro del Editor; porque si bien ya tenemos el método cargarPelicula() en la Vista del Editor de Película, éste necesita que le enviemos el ID de la película a cargar, es por esto, que debemos averiguarlo antes de invocarlo. Para esto, también vamos a agregar el método cargarPeliculaSeleccionadaEnVistaPrincipal(), que obtendrá el ID y se lo pasara como parámetro al método cargarPelicula() de la Vista Principal.

def onSeCambioItemSeleccionado(self, evt):
self.cargarPeliculaSeleccionadaEnVistaPrincipal()

def cargarPeliculaSeleccionadaEnVistaPrincipal(self):
self.idPelicula = self.app.controladorVistaPrincipal.vista.getIDItemSeleccionado()
self.vista.cargarPelicula(self.idPelicula)
También modificaremos el método __init__() del Controlador del Editor de Peliculas:
def __init__(self, app, parent, agregar=True):
self.app = app

self.vistaPrincipal = parent

self.vista = VistaEditorDePelicula(self.vistaPrincipal.frame, self.app, agregar)

btnAgregarPelicula = xrc.XRCCTRL(self.vista.Dialog, 'btnAceptar')

if(agregar):
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onAgregarPelicula, btnAgregarPelicula)
else:
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onModificarPelicula, btnAgregarPelicula)

self.cargarPeliculaSeleccionadaEnVistaPrincipal()

self.vista.Mostrar()
El motivo de este cambio, es que antes sólo cargábamos la información de la película al abrir el Editor, entonces el código encargado de esta tarea tenia sentido que este en el método __init__(), pero como ahora esta tarea se puede dar incluso cuando el Editor esta abierto, recurrimos a este nuevo esquema.

Si bien hemos agregado el método onSeCambioItemSeleccionado(), aun nos falta que este sea invocado cuando efectivamente el usuario selecciona una película en la Vista Principal y tiene el Editor de Películas abierto.

Para lograr esto, vamos a recurrir nuevamente a la clase Publisher del modulo wx.lib.pusub, empezando por hacer las siguientes modificaciones en el controlador de la Vista Principal.

1- Importamos la clase Publisher

from
wx.lib.pubsub import Publisher

2- Dentro del método __init__(), comenzamos la “escucha” del evento que nos indica que se selecciono un ítem en la lista de películas.

listadoPeliculas = xrc.XRCCTRL(self.vista.frame, 'listPeliculas')

self.vista.frame.Bind(wx.EVT_LIST_ITEM_SELECTED, self.onSeCambioItemSeleccionado, listadoPeliculas)

3- Creamos un manejador para este evento, para que se encargue de “comunicar” que la selección de la película en la Vista Principal ha cambiado.

def onSeCambioItemSeleccionado(self, evt):
Publisher.sendMessage('cambio_item_seleccionado', None)

Ahora haremos que el Controlador del Editor se quede a la escucha del mensaje 'cambio_item_seleccionado'. Los cambios necesarios serian los siguientes:

1- Importamos la clase Publisher

from wx.lib.pubsub import Publisher

2- Dentro del método __init__(), comenzamos la "escucha" del mensaje 'cambio_item_seleccionado':

Publisher.subscribe(self.onSeCambioItemSeleccionado, 'cambio_item_seleccionado')

Como se puede ver, al “escuchar” el mensaje 'cambio_item_seleccionado' se llama al método onSeCambioItemSeleccionado(), con lo cual lograremos nuestro objetivo de que la información de la película seleccionada en la Vista Principal se cargue en el Editor cuando esta abierto.

Por ultimo, para que el Editor sea mostrado (en modo “Agregar” o “Editar”), modificaremos los métodos onMostrarAgregarPelicula() y onMostrarEditorDePelicula() en el controlador de la Vista Principal.
def onMostrarAgregarPelicula(self, evt):
self.editorDePeliculas = ControladorEditorDePelicula(self.app, self.vista, True)

def onMostrarEditorDePelicula(self, evt):
self.editorDePeliculas = ControladorEditorDePelicula(self.app, self.vista, False)
Ahora sí estamos en condiciones de hacer que nuestra barra de navegación funcione.

Implementando la Barra de Navegación

Debemos tener en cuenta, que la Barra de Navegación sólo aparecerá en el modo “Edición” del Editor, por lo tanto modificaremos el método __init__() de la Vista del Editor, para que si lo llamamos en modo “Agregar” la barra no sea mostrada.
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
titulo_pelicula.SetFocus()

barraNavegacion = xrc.XRCCTRL(self.Dialog, 'm_panel2')
barraNavegacion.Show(False)

self.Dialog.Fit()

btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')
Como tal vez lo hayan notado, en este método también agregue el código que hace que al abrirse el Editor, la caja de texto del titulo de la película obtenga el enfoque.
titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
titulo_pelicula.SetFocus()
A continuación trabajaremos en la funcionalidad concreta de cada uno de los botones, para lo cual agregaremos los siguientes métodos al Controlador del Editor de Películas.
def onCargarSiguientePelicula(self, evt):
self.vistaPrincipal.seleccionarSiguientePelicula()

def onCargarPeliculaAnterior(self, evt):
self.vistaPrincipal.seleccionarAnteriorPelicula()

def onCargarPrimeraPelicula(self, evt):
self.vistaPrincipal.seleccionarPrimeraPelicula()

def onCargarUltimaPelicula(self, evt):
self.vistaPrincipal.seleccionarUltimaPelicula()
Ahora nos quedara “enlazar” cada uno de estos métodos, con su botón correspondiente. Esto lo haremos agregando el siguiente código en el método __init__() del Controlador del Editor, justo antes de la linea que muestra la ventana.
btnPeliculaAnterior = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton2')
btnPeliculaSiguiente = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton3')
btnPrimeraPelicula = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton1')
btnUltimaPelicula = xrc.XRCCTRL(self.vista.Dialog, 'm_bpButton4')

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarPeliculaAnterior, btnPeliculaAnterior)

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarSiguientePelicula, btnPeliculaSiguiente)

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarPrimeraPelicula, btnPrimeraPelicula)

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onCargarUltimaPelicula, btnUltimaPelicula)
Otro punto que debemos tener en cuenta, es que si bien en el modo “Edición” podremos seleccionar otra película en la Vista Principal, no debería estar habilitada la barra de herramientas ni el menú. Para lograr este comportamiento, agregaremos el método habilitarEdicion() en la Vista Principal.
def habilitarEdicion(self, habilitado=True):
#Deshabilito o habilito la barra de herramientas
barra_de_herramientas = self.frame.GetToolBar()

barra_de_herramientas.Enable(habilitado)

#Deshabilito o habilito el menú
menu_catalogo = self.frame.GetMenuBar()
menu_catalogo.EnableTop(0, habilitado)
Este método lo invocaremos al mostrar el Editor en el modo “Edición”, dentro del método __init__() del Controlador del Editor.
.
.
.
if(agregar):
self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onAgregarPelicula, btnAgregarPelicula)
else:
self.vistaPrincipal.habilitarEdicion(False)
.
.
.
En el caso que el Editor sea llamado en modo “Agregar”, lo que haremos sera mostrar el Dialogo en forma Modal, así la Ventana Principal quedará completamente bloqueada. Para esto modificaremos el método Mostrar() de la Vista del Editor.
def Mostrar(self, modal=True):
if modal:
self.Dialog.ShowModal()
else:
self.Dialog.Show()
Y dado que ahora este método recibe el parámetro “modal”, modificaremos la llamada en el Controlador del Editor, dentro del método __init__().
self.vista.Mostrar(agregar)
Para ir finalizando, agregaremos un método al que llamaremos onCerrarrEditorDePelicula() en el Controlador del Editor, el cual se encargara de volver a habilitar la Vista Principal tras cerrar el Editor, y también hará que se deje de escuchar el mensaje 'cambio_item_seleccionado'.
def onCerrarEditorDePelicula(self, evt):
self.vistaPrincipal.habilitarEdicion(True)

Publisher.unsubscribe(self.onSeCambioItemSeleccionado, 'cambio_item_seleccionado')
self.vista.Dialog.Destroy()
Por último, asociaremos el evento que nos indica que la ventana se cerrara; con este método, dentro del método __init__() del Controlador del Editor.
self.vista.Dialog.Bind(wx.EVT_CLOSE, self.onCerrarEditorDePelicula, self.vista.Dialog)
Y así hemos llegado al final de esta quinta entrega, en la que hemos realizado varios cambios. Dado que la cantidad de modificaciones con relación a la versión anterior, es bastante grande, voy a estar publicando una parte 6, en donde va a estar todo el código completo hasta este punto. Luego de la sexta entrega, trabajaremos en la busqueda de peliculas dentro del Catalogo.

Hasta la próxima!

lunes, 1 de febrero de 2010

Crear una [pequeña] aplicación completa usando wxPython (Parte 4)

.
15 comentarios

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

Como les había adelantado en la parte anterior, hoy empezaremos a trabajar en el Editor de Películas del Catálogo. Este editor sera el medio por el cual los usuarios podrán tanto agregar películas al catálogo, como editar la información de las que ya se encuentren cargadas. Tal como hicimos con la Ventana Principal, crearemos por un lado la Vista y por otro el Controlador. Dicho esto, empecemos a trabajar.

Codificando la Vista del Editor de películas

La clase que representa la Vista de nuestro Editor la llamaremos VistaEditorDePeliculas. Como les había comentado, el Editor sera invocado para realizar dos funciones distintas: agregar o modificar una película. Para que el Editor “sepa” en que modo lo estamos llamando, le pasaremos un parámetro. El parámetro lo llamaremos agregar y tendrá como valor por defecto True; es decir que si al invocarlo, no especificamos nada, el Editor se abrirá en modo “Agregar Película”. Además del modo en que queremos que se muestre el Editor, también le diremos quien es su ventana padre (en nuestro caso sera la Ventana Principal), y también recibirá una referencia a la aplicación en si, para poder acceder a los recursos (app.res) desde donde cargaremos la interface gráfica del dialogo, y también podremos saber cual es el modelo (app.modelo) de la aplicación. A continuación les muestro el código que implementa lo que les acabo de comentar:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

Con esto tenemos nuestro método __init__ casi terminado. Para finalizar agregaremos algo de código, para poder determinar si estamos en modo “Agregar” o “Editar”, y establecer el label del botón que guarda los cambios (btnAceptar) con el texto “Agregar” o “Guardar” según corresponda:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

Por otro lado, debemos tener en cuenta que en caso que el Editor sea abierto en modo “Agregar”, basta con que el dialogo se muestre, pero si se abre en modo “Editar”, los controles del dialogo deben estar cargados con los datos correspondientes a la película que esta seleccionada en la Ventana Principal; para llevar a cabo esta tarea, codificaremos el método cargarPelicula. Este método recibirá el ID que tiene asignado la película en la base de datos, y haciendo uso del Modelo obtendremos los datos de esta, para luego cargar los controles correspondientes del Editor. Veamos como quedaría:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())
Como pueden ver, el método cagarPelicula, llama al método obtenerInformacionDePelicula del Modelo, pasándole el ID de la película cuyos datos necesitamos. Tras esta llamada, tendremos el resultado en infoPelicula, y con estos cargamos los controles correspondientes al titulo y el año de la misma.

Por si no lo recuerdan, el método obtenerInformacionDePelicula al que estamos llamando, aún no existe en nuestro Modelo, así que veamos como sería el código de este:

def obtenerInformacionDePelicula(self, idPelicula):
self.conectar()

self.cursor.execute("SELECT * FROM tblPeliculas WHERE IDPelicula = " + str(idPelicula))

resultado = self.cursor.fetchone()

infoPelicula = pelicula.Pelicula()

infoPelicula.setTitulo(str(resultado[1]))
infoPelicula.setAnio(str(resultado[2]))

self.desconectar()

return infoPelicula
Este método, tan sólo ejecuta una consulta a la base de datos para obtener la información de la película cuyo ID coincida con el que le enviamos como parámetro, y luego retorna esta información a quien lo haya invocado.

Hasta ahora no tenemos la posibilidad de que se muestre el Editor, así que agregaremos un método a la Vista, llamado Mostrar para llevar a cabo esta tarea:
import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())

def Mostrar(self):
self.Dialog.ShowModal()
Luego de terminar con esta clase, pasaremos a desarrollar el Controlador del Editor, el cual se encargara entre otras cosas de enviar a guardar los datos ingresados en los controles del Editor; es por esto que necesitamos crear un método en la Vista, que retorne lo que el usuario haya cargado; así que pasaremos a agregar el método getInfoPelicula:

import wx
from wx import xrc

class VistaEditorDePelicula:
def __init__(self, parent, app, agregar=True):
self.modelo = app.modelo
self.app = app

self.Dialog = self.app.res.LoadDialog(parent, 'DlgEditorPelicula')

btnAgregarPelicula = xrc.XRCCTRL(self.Dialog, 'btnAceptar')

if(agregar):
btnAgregarPelicula.SetLabel('Agregar')
else:
btnAgregarPelicula.SetLabel('Guardar')

def Mostrar(self):
self.Dialog.ShowModal()

def cargarPelicula(self, idPelicula):
infoPelicula = pelicula.Pelicula()

infoPelicula = self.modelo.obtenerInformacionDePelicula(idPelicula)

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

titulo_pelicula.SetValue(infoPelicula.getTitulo())
anio_pelicula.SetValue(infoPelicula.getAnio())

def getInfoPelicula(self):
infoPelicula = pelicula.Pelicula()

titulo_pelicula = xrc.XRCCTRL(self.Dialog, 'txtTitulo')
anio_pelicula = xrc.XRCCTRL(self.Dialog, 'txtAnio')

infoPelicula.setTitulo(titulo_pelicula.GetValue())
infoPelicula.setAnio(anio_pelicula.GetValue())

return infoPelicula
El método getInfoPelicula, simplemente obtiene los valores que el usuario introdujo en los controles y los carga en un objeto de la clase Pelicula, para luego retornarlo como resultado de su ejecución. Aun no hemos creado la clase Pelicula, así que pasemos a ver como seria:
class Pelicula:
def __init__(self):
self.titulo = ""
self.anio = ""

def setTitulo(self, titulo):
self.titulo = titulo

def getTitulo(self):
return self.titulo

def setAnio(self, anio):
self.anio = anio

def getAnio(self):
return self.anio
De este modo, damos por terminada la Vista del Editor de películas y nos metemos de lleno con su Controlador.

Codificando el Controlador del Editor de películas

El controlador del Editor sera el encargado de enviar a agregar o modificar la película según en que estado se encuentre abierto el Editor, es por esto que crearemos en este, los métodos onAgregarPelicula y onModificarPelicula:

def onAgregarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.agregarPelicula(infoPelicula.getTitulo(), infoPelicula.getAnio())

def onModificarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.modificarPelicula(self.idPelicula, infoPelicula.getTitulo(), infoPelicula.getAnio())
Claramente los dos métodos son muy parecidos, simplemente obtienen los datos cargados en la Vista a través del método getInfoPelicula que codificamos anteriormente, y llaman a al método agregarPelicula o modificarPelicula del Modelo según corresponda.

Teniendo en cuenta que tenemos dos acciones posibles, debemos “asociar” el botón btnAceptar con una u otra función a la hora de crear la clase, es por esto que en el método __init__ (y al igual que hacíamos con la Vista) le pasamos el parámetro Agregar para determinar en que estado queremos mostrar el Editor, y así llevar a cabo la asociación. Para clarificar esto, veamos como quedaría la clase completa:

from vistas.vistaEditorDePelicula import VistaEditorDePelicula
import wx
from wx import xrc
import pelicula

class ControladorEditorDePelicula:
def __init__(self, app, parent, agregar=True):
self.app = app> self.vistaPrincipal = parent
self.vista = VistaEditorDePelicula(self.vistaPrincipal.frame, self.app, agregar)
btnAgregarPelicula = xrc.XRCCTRL(self.vista.Dialog, 'btnAceptar')

if(agregar):

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onAgregarPelicula, btnAgregarPelicula)

else:

self.idPelicula = app.controladorVistaPrincipal.vista.getIDItemSeleccionado()

self.vista.Dialog.Bind(wx.EVT_BUTTON, self.onModificarPelicula, btnAgregarPelicula)

self.vista.cargarPelicula(self.idPelicula)

self.vista.Mostrar()

def onAgregarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.agregarPelicula(infoPelicula.getTitulo(), infoPelicula.getAnio())

def onModificarPelicula(self, evt):
infoPelicula = self.vista.getInfoPelicula()
self.app.modelo.modificarPelicula(self.idPelicula, infoPelicula.getTitulo(), infoPelicula.getAnio())

Pueden ver que el método __init__ comienza creando la Vista (a la cual le pasa los parámetros que tratamos oportunamente), luego determina si estamos en el modo “Agregar” o “Editar” y asocia el botón btnAceptar con el método onAgregarPelicula o onModificarPelicula; y por ultimo hace visible el Editor invocando el método Mostrar de la Vista del Editor. Para terminar, tan solo quiero que presten atención que si estamos en el modo de edición, obtenemos el ID de la película seleccionada en la Ventana Principal, y llamamos al método cargarPelicula de la Vista del Editor pasándoselo como parámetro, de este modo logramos que cuando aparezca el Editor en pantalla, este presente los datos de la película que el usuario quiere modificar.

Con esto terminamos esta cuarta parte del tutorial. En la próxima entrega haremos algunos cambios estéticos a nuestra pequeña aplicación (Agregaremos algunos iconos a la barra de herramientas, menues y botones) y también veremos como hacer para que podamos movernos entre las distintas películas del Catálogo directamente desde el Editor de Películas, sin necesidad de estar cerrándolo para pasar a modificar otra película.