En esta oportunidad crearemos el Controlador de la Vista Principal, y por primera vez podremos ejecutar nuestra aplicación. Sin mas preámbulo, empecemos!.
Codificando el Controlador de la Vista Principal
Para comenzar, voy a citar el punto 2 del artículo de la Wikipedia que les comentaba en la parte anterior del tutorial."… 1. El controlador recibe (por parte de los objetos de la interfaz-vista) la notificación de la acción solicitada por el usuario. El controlador gestiona el evento que llega, frecuentemente a través de un gestor de eventos (handler) o callback. ..."
Este punto nos esta diciendo:
1. El Controlador recibe desde la Vista, la notificación de la acción solicitada por el usuario (ej: "se pulso el botón Salir", "se pulso el botón Agregar Película", etc.).
2. El Controlador gestiona el evento, haciendo una llamada a quien corresponda para que atienda esa acción en particular (ej: "Si el usuario pulso el botón Agregar Película llamar al método onAgregarPelicula, para que se encargue de mostrar el Editor de Películas").
Teniendo estos dos puntos presentes, nuestro Controlador para la Vista Principal, podría (inicialmente) ser algo así:
controladorVistaPrincipal.py
import wx
from wx import xrc
from vistas.vistaPrincipal import VistaPrincipal
class ControladorVistaPrincipal:
def __init__(self, app):
self.app = app
self.vista = VistaPrincipal(app)
#Items del Menú
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'))
#Botones de la barra de herramientas
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'))
def onMostrarAgregarPelicula(self, evt):
...
def onMostrarEditorDePelicula(self, evt):
...
def onEliminarPelicula(self, evt):
...
def onBuscarPelicula(self, evt):
...
def onSalir(self, evt):
self.app.Exit()
Lo primero que hacemos en esta clase es crear la Vista Principal de la aplicación (aunque aún no la mostramos).
self.vista = VistaPrincipal(app)
después "enlazamos" los eventos que nos interesan con su correspondiente manejador de evento. Primero lo hacemos para cuando sean activados los items del menú
#Items del Menú
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'))
y luego para cuando sean pulsados los botones de la barra de herramientas
#Botones de la barra de herramientas
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'))
Pueden observar que algunos manejadores de eventos son llamados como respuesta a mas de un evento, por ej: tanto el ítem del menú "Eliminar" como el botón "Eliminar" de la barra de herramientas, están asociados al manejador de eventos onEliminarPelicula.
Como ultimo paso, creamos (a medias) los distintos manejadores de eventos que serán llamados según las acciones que solicite el usuario.
def onMostrarAgregarPelicula(self, evt):
...
def onMostrarEditorDePelicula(self, evt):
...
def onEliminarPelicula(self, evt):
...
def onBuscarPelicula(self, evt):
...
def onSalir(self, evt):
self.app.Exit()
Ahora haremos referencia al punto 2 del artículo de la Wikipedia, para seguir creando nuestro Controlador
"... 2. El controlador accede al modelo, actualizándolo, posiblemente modificándolo de forma adecuada a la acción solicitada por el usuario (por ejemplo, el controlador actualiza el carro de la compra del usuario). Los controladores complejos están a menudo estructurados usando un patrón de comando que encapsula las acciones y simplifica su extensión. ..."
Leyendo este punto, vemos que nuestro Controlador se encarga de acceder al Modelo y hacer las modificaciones necesarias. En nuestro caso la tarea de agregar y eliminar una película no estará a cargo de este Controlador sino a cargo del Controlador de la Vista del Editor de Películas, pero en el caso de que se quiera eliminar una película, esta acción si será llevada a cabo por el método onEliminarPelicula de este Controlador, y sería mas o menos así:
def onEliminarPelicula(self, evt):
id_pelicula = self.vista.getIDItemSeleccionado()
self.app.modelo.eliminarPelicula(id_pelicula)
así de simple es la tarea de eliminar la película que este seleccionada en el listado de la Vista Principal. Primero obtenemos el ID de la película seleccionada con el método getIDItemSeleccionado de la Vista Principal y luego llamamos al método onEliminarPelicula del Modelo pasándole el ID de la película que deseamos borrar, pero por si no lo recuerdan, el método getIDItemSeleccionado aún no lo hemos creado, así que debemos agregar el siguiente código a la clase VistaPrincipal:
def getIDItemSeleccionado(self):
itemIndex = self.listaDePeliculas.GetFirstSelected()
return self.listaDePeliculas.GetItemData(itemIndex)
en este método obtenemos un referencia al ítem seleccionado y luego retornamos el ID que esta asociado a éste.
Por ahora no nos estamos preocupando por las validaciones, pero mas adelante volveremos sobre esto. También deberíamos pedir una confirmación antes de borrar la película, e incluso podríamos dar la posibilidad de borrar de a mas de una película al mismo tiempo, pero por el momento quedará pendiente.
Los métodos onMostrarAgregarPelicula y onMostrarEditorDePelicula serán (justamente) los encargados de mostrar el Editor de Películas en modo Agregar o Modificar respectivamente. Como aún no tenemos ni la Vista ni el Controlador del Editor de Películas, por ahora haremos que sólo se muestre un mensaje diciendo qué debería suceder al ser llamados, es decir que quedarían así:
def onMostrarAgregarPelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Películas para agregar una película")
def onMostrarEditorDePelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Películas para modificar la película seleccionada")
También hacemos lo mismo con el método onBuscarPelicula:
def onBuscarPelicula(self, evt):
wx.MessageBox("Buscar una película")
En este punto nos referiremos a una parte del punto 4 del artículo que hemos tomado como referencia:
"... 4. … El controlador no pasa objetos de dominio (el modelo) a la vista aunque puede dar la orden a la vista para que se actualice. Nota: En algunas implementaciones la vista no tiene acceso directo al modelo, dejando que el controlador envíe los datos del modelo a la vista. ..."
De las dos implementaciones que nos habla este punto, nosotros usaremos la primera, es decir que el Controlador no pasará información del Modelo a la Vista, sino que el Controlador sólo avisará cuando haya alguna modificación en el Modelo, y será la Vista la encargada de actualizarse. Teniendo esto claro, necesitamos algún mecanismo para que el Controlador "se entere" de que el Modelo ha cambiado. Si hacemos un poco de memoria, recordaremos que el modelo "avisaba" cuando se agregaba, modificaba o eliminaba una película haciendo uso del modulo wx.lib.pubsub, a través del método sendMessage() del objeto Publisher, o sea que lo único que nos quedaría por hacer es suscribirnos a esos mensajes y así quedarnos a la escucha de estos. Para llevar a cabo esta tarea, simplemente utilizamos el método suscribe() del objeto Publisher de la siguiente manera:
Publisher.subscribe(self.seModificoElModelo, 'pelicula_agregada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_modificada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_eliminada')
Con estas tres lineas, lo que estamos haciendo es quedarnos a la escucha de los mensajes 'pelicula_agregada', 'pelicula_modificada' y 'pelicula_eliminada', y de ser recibidos, llamamos (por ahora) al método seModificoElModelo, que codificaremos así:
def seModificoElModelo(self, mensaje):
self.vista.cargarListadoDePeliculas()
Lo único que estamos haciendo aquí, es invocar el método cargarListadoDePeliculas() de la Vista Principal, con lo cual se recargarían las películas y así se vería reflejado el cambio que se ha producido en el Modelo (se agrego, modifico o elimino una película).
Por último, una vez que nos suscribimos a los mensajes que nos interesan, nos queda cargar el listado de películas y hacer visible la Vista:
self.vista.cargarListadoDePeliculas()
self.vista.Mostrar()
A continuación les muestro como queda la clase completa hasta ahora:
import wxCon esto tenemos el Modelo, y la Vista y el Controlador de la Ventana Pricipal en condiciones de ser ejecutados, pero aún nos falta crear la clase que representa la aplicación, que será el punto de partida para que nuestro Catálogo puede iniciarse.
from wx import xrc
from wx.lib.pubsub import Publisher
from vistas.vistaPrincipal import VistaPrincipal
class ControladorVistaPrincipal:
def __init__(self, app):
self.app = app
self.vista = VistaPrincipal(app)
#Items del Menú
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'))
#Botones de la barra de herramientas
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'))
#Me suscribo a los mensajes que indican que se modifico el Modelo
Publisher.subscribe(self.seModificoElModelo, 'pelicula_agregada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_modificada')
Publisher.subscribe(self.seModificoElModelo, 'pelicula_eliminada')
#Cargo la lista de peliculas
self.vista.cargarListadoDePeliculas()
self.vista.Mostrar()
def onMostrarAgregarPelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Peliculas para agregar una pelicula")
def onMostrarEditorDePelicula(self, evt):
wx.MessageBox("Mostrar el Editor de Peliculas para modificar la pelicula seleccionada")
def onEliminarPelicula(self, evt):
id_pelicula = self.vista.getIDItemSeleccionado()
self.app.modelo.eliminarPelicula(id_pelicula)
def onBuscarPelicula(self, evt):
wx.MessageBox("Buscar una pelicula")
def onSalir(self, evt):
self.app.Exit()
Codificando la clase de la Aplicación
Primero les mostrare el código de esta pequeña clase, y luego iremos paso a paso viendo que es lo que hace: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('gui.xrc')
self.controladorVistaPrincipal = ControladorVistaPrincipal(self)
return True
app = CatalogoDePeliculas()
app.MainLoop()
En primer lugar vemos que dentro de los modulos que importamos, se encuentran el Controlador de la Vista Principal (ControladorVistaPrincipal) y el Modelo (Model). También notamos que el nombre de la clase es CatalogoDePeliculas y que esta deriva de wx.App. Ya en el método OnInit, vemos que instanciamos self.modelo con el Modelo de la aplicación:
self.modelo = Model()
luego, cargamos el archivo de recursos
self.res = xrc.XmlResource('gui.xrc')
creamos el Controlador de la Vista Principal, al cual le pasamos la propia aplicación como parámetro
self.controladorVistaPrincipal = ControladorVistaPrincipal(self)
y ya fuera de la clase, creamos la aplicación e iniciamos el bucle principal.
De este modo, estamos listos para poder ver nuestra aplicación en marcha, ejecutando este archivo (catalogo.py). Si bien aún no podemos editar las películas del catálogo, podriamos agregar algunas películas en forma manual dentro de la base de datos, y al ejecutar el catálogo veríamos que estas se cargarían en el listado, e incluso las podríamos eliminar.
Hasta aquí hemos llegado en esta entrega. En la próxima trabajaremos en el Editor de Películas y así seguiremos agregando funcionalidad a nuestro pequeño Catálogo de películas.
Saludos!