jueves, 23 de diciembre de 2010

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

.

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!

6 comments

Anónimo dijo...

Hola Pablo.... Gracias por tremendo trabajo realizado. Me es de mucha utilidad pues es facil encontrar manuales de Python que explican la sintaxis,... etc. Pero explicar un mini proyecto con todo lo necesario para acceso a bdd, uso de widgets y la interacción que debe haber entre los componentes de una aplicación orientada a objetos... eso no se encuentra facilmente. Nuevamente Gracias por compartir tus conocimientos, y... la pregunta obligada: ¿Habrá 8a. entrega?

Saludos

Edgar
Guatemala

Anónimo dijo...

Hola Pablo, me uno a Edgar en el agradecimiento, muy bueno el tutorial. Espero algun día lo continues. ¡saludos!

Javier.

Anónimo dijo...

Solo una palabra. IMPRESIONANTE. Con tu curso en una semana estoy creando aplicaciones.

sourboy dijo...

Wuao muchas! pero muchas gracias me has salvado la vida! deberías dedicarte a esto y crear una web donde hagas cursos realmente eres bueno en esto!

Anónimo dijo...

Hola Pablo, muchas gracias por compartir tu sabiduría con nosotros los principiantes.
Agradeceriamos mucho poder descargar el proyecto y así poder ejecutarlo

Anónimo dijo...

Hola Pablo.

De verdad musumas gracias, espero podamos tener el capitulo final.

Gracias

Publicar un comentario