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.
Como les anticipaba en la primera parte del tutorial, en esta entrega comenzaremos a trabajar en la Ventana Principal de nuestro catálogo; pero antes de empezar con esta tarea nos concertaremos en la creación del Modelo de la aplicación.
Si bien les había comentado brevemente de que se trata el patrón MVC, a continuación les transcribo una parte del articulo que habla sobre este tema en la Wikipedia, para comprender mejor las tareas que corresponden a cada uno de los componentes (Modelo, Vista y Controlador)
1. El usuario interactúa con la interfaz de usuario de alguna forma (por ejemplo, el usuario pulsa un botón, enlace, etc.)
2. 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.
3. 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.
4. El controlador delega a los objetos de la vista la tarea de desplegar la interfaz de usuario. La vista obtiene sus datos del modelo para generar la interfaz apropiada para el usuario donde se refleja los cambios en el modelo (por ejemplo, produce un listado del contenido del carro de la compra). El modelo no debe tener conocimiento directo sobre la vista. Sin embargo, el patrón de observador puede ser utilizado para proveer cierta indirección entre el modelo y la vista, permitiendo al modelo notificar a los interesados de cualquier cambio. Un objeto vista puede registrarse con el modelo y esperar a los cambios, pero aun así el modelo en sí mismo sigue sin saber nada de la vista. 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.
5. La interfaz de usuario espera nuevas interacciones del usuario, comenzando el ciclo nuevamente. ...”
Tomando este texto como referencia, nos pondremos manos a la obra y a medida que vayamos avanzando con el código, les iré haciendo notar como se relaciona lo que estemos haciendo, con lo dicho en los 5 puntos anteriores.
Codificando el Modelo
La idea aquí sera crear una clase con una serie de métodos que nos permitan interactuar con nuestra base de datos, pudiendo realizar altas, bajas, modificaciones y consultas de las películas. Si no sabes como trabajar con una base de datos en Python, puedes leer mi tutorial sobre este tema (o cualquiera de los muchos que hay en la red). Dicho esto, veamos como podría ser nuestra primera versión del Modelo.modelo.py
import MySQLdb
class Model:
def conectar(self):
self.cnn = MySQLdb.connect(host='localhost', user='user', passwd='user', 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()
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()
def eliminarPelicula(self, id_pelicula):
self.conectar()
self.cursor.execute("DELETE FROM tblPeliculas WHERE IDPelicula=" + str(id_pelicula))
self.desconectar()
def obtenerListadoDePeliculas(self):
self.conectar()
self.cursor.execute("SELECT * FROM tblPeliculas ORDER BY TituloPelicula")
listado = self.cursor.fetchall()
self.desconectar()
return listado
Dando un rápido vistazo sobre el código, vemos que nuestra clase llamada Model, tiene seis métodos que nos permiten conectarnos y desconectarnos de la base de datos; agregar, modificar y eliminar películas, y también obtener un listado de todas las películas del catálogo. Ahora que tenemos el modelo básico, haremos una referencia a una parte del punto cuatro del artículo de la Wikipedia que les cite al principio:
“4. …. El modelo no debe tener conocimiento directo sobre la vista. Sin embargo, el patrón de observador puede ser utilizado para proveer cierta indirección entre el modelo y la vista, permitiendo al modelo notificar a los interesados de cualquier cambio. Un objeto vista puede registrarse con el modelo y esperar a los cambios, pero aun así el modelo en sí mismo sigue sin saber nada de la vista. …"
Básicamente se nos esta diciendo que si bien el Modelo NO DEBE conocer nada sobre la vista (ni tampoco sobre el Controlador), el Modelo puede utilizar algún mecanismo para informar cuando se ha producido un cambio en él. Para implementar este “sistema de avisos”, nosotros vamos a hacer uso del modulo wx.lib.pubsub; el cual nos brinda la posibilidad de usar un componente de “publicación/suscripción” donde se “publica” un cierto mensaje, y este es recibido por quienes se hayan “suscripto” a él.
Veamos como quedaría el código anterior, con la opción de que se envíe un mensaje al modificarse el Modelo.
from wx.lib.pubsub import PublisherComo se puede apreciar, es muy simple enviar un mensaje avisando de que se ha producido un cambio en el Modelo, en nuestro caso, enviamos el mensaje “pelicula_agregada”, “pelicula_modificada” y “pelicula_eliminada” según corresponda; para lo cual hacemos uso del método sendMessage del objeto Publisher (que previamente hemos importado). Mas adelante veremos como hacemos para suscribirnos a estos mensajes, pero lo que se debe tener claro, es que si bien el Modelo envía los mensajes, él realmente no sabe quien los recibirá.
import MySQLdb
class Model:
def conectar(self):
self.cnn = MySQLdb.connect(host='localhost', user='user', passwd='user', 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
Hasta aquí hemos llegado por ahora con el Modelo; mas tarde le iremos agregando mayor funcionalidad a medida que vaya surgiendo la necesidad.
A continuación, empezaremos a trabajar en la Ventana Principal.
Codificando la Ventana Principal
Para codificar la Ventana Principal (y para todas las ventanas que crearemos) la debemos pensar en dos partes: la Vista y el Controlador. Teniendo esto presente, crearemos en primer lugar una clase llamada VistaPrincipal:vistaPrincipal.py
import wxEsta clase representa la parte gráfica de la Ventana Principal. Al ser instanciada, recibe como parámetro el objeto aplicación (que mas tarde crearemos), el cual representa la aplicación propiamente dicha, y es la que posee la información acerca de cual es el Modelo de la aplicación y nos permite acceder a los recursos graficos que carga desde el archivo XRC que creamos al principio del tutorial.
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 cargarListadoDePeliculas(self):
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')
self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)
self.listaDePeliculas.InsertColumn(0, 'Título', format=wx.LIST_FORMAT_LEFT, width=-1)
self.listaDePeliculas.InsertColumn(1, 'Año', format=wx.LIST_FORMAT_LEFT, width=-1)
peliculas = self.app.modelo.obtenerListadoDePeliculas()
#Cargo el listado de películas
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)
Ahora veamos en detalle los métodos de esta clase:
__init__
Aquí obtenemos los recursos gráficos desde el objeto app que recibimos como parámetro
self.app = app
self.res = self.app.res
y luego cargamos el Frame de la Ventana Principal (FramePrincipal)
self.frame = self.res.LoadFrame(None, 'FramePrincipal')
mostrar
Este método, simplemente hace visible la Ventana Principal.
self.frame.Show()
cargarListadoDePeliculas
Como el nombre del método lo indica, aquí se carga el listado de las películas. Lo primero que hacemos es obtener una referencia al objeto listPeliculas (wxListControl)
self.listaDePeliculas = xrc.XRCCTRL(self.frame, 'listPeliculas')
luego ponemos la lista en “modo reporte”
self.listaDePeliculas.SetSingleStyle(wx.LC_REPORT, True)
agregamos las columnas de Titulo y Año:
self.listaDePeliculas.InsertColumn(0, 'Título', format=wx.LIST_FORMAT_LEFT, width=-1)
self.listaDePeliculas.InsertColumn(1, 'Año', format=wx.LIST_FORMAT_LEFT, width=-1)
accedemos al Modelo para obtener el listado de las películas:
self.app.modelo.obtenerListadoDePeliculas()
cargamos las películas en la lista
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
y por último, ajustamos el ancho de las columnas para que sea óptimo.
self.listaDePeliculas.SetColumnWidth(0, wx.LIST_AUTOSIZE)
self.listaDePeliculas.SetColumnWidth(1, wx.LIST_AUTOSIZE)
Esta clase esta relacionada con el punto 1 del texto que extrajimos de la Wikipedia:
“1. El usuario interactúa con la interfaz de usuario de alguna forma (por ejemplo, el usuario pulsa un botón, enlace, etc.)”
Si bien queda claro que esta clase se encarga de todo lo referido a la parte visual de la Ventana Principal, y permite que el usuario interaccione con nuestra aplicación, también queda claro que falta que de algún modo estemos a la escucha de los eventos que genera el usuario para actuar en consecuencia; por ejemplo, si hace click en el botón “Agregar” de la barra de herramientas, debería aparecer el Editor de Películas, o si pulsa el botón “Eliminar”, se debería eliminar la película seleccionada y así con todas las opciones. Aquí es donde entra en juego el Controlador, que sera el encargado de recibir los eventos generados por el usuario sobre la interface (pulsaciones de teclado, pulsaciones y movimientos del mouse, etc.) y de este manera realizar la opción solicitada.
Por ahora llegamos hasta aquí, y en la próxima parte nos meteremos de lleno en el Controlador de la Ventana Principal, y veremos como hacer que nuestra aplicación pueda ser ejecutada, y muestre esta ventana con el listado de películas cargado.
Como siempre, pueden comentar las dudas que tengan, sugerir alguna mejora, informar algun error o simplemente decir algo.
Saludos!