viernes, 5 de febrero de 2010

Crear una agenda en Python - Tercera parte (juntando todo)

En este tercer post vamos a encajar todas las piezas para terminar de crear nuestra fantástica agenda personal, escrita en Python, que utiliza XML para manejo de datos, y por encima el framework de wxPython. ¡Qué mas se puede pedir!

Evidentemente la aplicación, tal como va a quedarse, puede ser mejorada, y darle sobretodo mucha más funcionalidad, como permitir envío de correo (registro de direcciones, etc.), dividir los días en horas, implementación de alarmas, etc.

Recapitulando, tenemos:

1) Un diseño gráfico completo, realizado en wxGlade.

2) Un diseño del modelo de datos que está bajo el diseño gráfico, cómo se gestiona la información, cómo se guarda, las herramientas de acceso, independiente del diseño que va por encima.

El tercer paso consiste, mediante la programación de manejadores de eventos, el relacionar los posibles eventos que se dan en el diseño gráfico con la funcionalidad que queremos que tenga para nuestro modelo de datos. Para ello vamos a crear un tercer módulo, llamado agenda_controlador.py, en donde vamos a importar la clase "gestion_agenda" del módulo agenda_modelo.py (los datos) y la clase frame_agenda del módulo agenda_widget.py (la vista).
Ahora toca el paso de programar el módulo que relaciona los datos con la vista (el controlador).

Aunque parezca que estoy realizando una programación Modelo-Vista-Controlador, en realidad no lo es tanto, ya que en el controlador no voy a utilizar la clase AbstractModel, por tanto, no es un MVC puro, aunque intente que se asemeje. Si lo hago es por la claridad a la hora de exponer la idea, pero ni que decir tiene, que un diseño MVC puro es lo correcto.

A continuación se presenta el contenido del tercer fichero, que será el principal (__main__):

# -*- coding: cp1252 -*-

import agenda_modelo
import agenda_widget
import wx

class agenda(agenda_widget.frame_agenda):
def __init__(self, *args, **kwds):
# Llamamos al constructor de la clase de la cual heredamos.
agenda_vista.frame_agenda.__init__(self, *args, **kwds)

# Creamos el objeto que contendrá el modelo de datos.
self.datos_agenda = info_agenda("agenda")

# Indicamos la fecha actual.
self.datos_agenda.fecha_actual = (self.datos_agenda.generar_id())

# Buscamos datos en la estructura del modelo de datos,
# y si tiene texto, lo mostramos en el widget.
aux = self.datos_agenda.texto_dia(self.datos_agenda.fecha_actual)
self.insertar_texto(aux)

# ------------------------------------------------------------
# Creamos los controladores. Para ello incluimos la asociación
# de eventos con manejadores (binding).
# ------------------------------------------------------------

# Asociamos evento de cambiar fecha del calendario
# con el manejador de eventos OnCambiarFecha.
self.calendar_ctrl_1.Bind(wx.calendar.EVT_CALENDAR_SEL_CHANGED, self.OnCambiarFecha)

# Asociamos evento de cerrar ventana (frame)
# con el manejador deeventos OnCerrarVentana.
self.Bind(wx.EVT_CLOSE, self.OnCerrarVentana)

# ---------------------------------------------------------------------
# A continuación definimos los manejadores de eventos (event handlers).
# ---------------------------------------------------------------------

# Event Handler que se dispara al cerrar la ventana.
def OnCerrarVentana(self, event):

# Actualizamos información, si la hubiera.
texto = self.obtener_texto()
self.datos_agenda.modificar_dia(self.datos_agenda.fecha_actual, texto)

# Guardamos datos en fichero XML.
self.datos_agenda.guardar_agenda()

# Y nos vamos.
self.Destroy()

# Event Handler que se dispara al cambiar de fecha el calendario.
def OnCambiarFecha(self, event):
# Obtenemos texto del widget.
texto = self.obtener_texto()

# Actualizamos información en estructura de datos.
self.datos_agenda.modificar_dia(self.datos_agenda.fecha_actual, texto)

# Obtenemos la nueva fecha.
siguiente_fecha = str(self.calendar_ctrl_1.PyGetDate())[0:10]

# Actualizamos, ahora la fecha actual es la nueva fecha.
self.datos_agenda.fecha_actual = siguiente_fecha

# Buscamos información de la fecha actual y la mostramos en el widget.
aux = self.datos_agenda.texto_dia(self.datos_agenda.fecha_actual)
self.insertar_texto(aux)

# -------------------------------------------------------------------------
# Definimos funciones para representar y recoger datos de la caja de texto.
# -------------------------------------------------------------------------

def obtener_texto(self):
aux = self.text_ctrl_1.GetValue()
return aux

def insertar_texto(self, texto):
self.text_ctrl_1.SetValue(texto)

class info_agenda(agenda_modelo.gestion_agenda):
def __init__(self, nom_fich_agenda):
# Llamamos al constructor de la clase de la que se deriva.
agenda_modelo.gestion_agenda.__init__(self, nom_fich_agenda)

# Incluimos en esta clase el atributo fecha, para controlar la fecha actual.
self.fecha_actual = ""

if __name__ == "__main__":
# Creamos objeto aplicación.
app = wx.PySimpleApp(0)
wx.InitAllImageHandlers()

# Cambiamos el idioma a los widgets, lo ponemos en español.
langid = wx.LANGUAGE_SPANISH
wx.Locale(langid)

# Creamos ventana de la aplicación, y la mostramos.
agenda1 = agenda(None, -1, "")
app.SetTopWindow(agenda1)
agenda1.Show()

# A la espera de eventos.
app.MainLoop()
Cosas a tener en cuenta:

1) En el fichero agenda_widget.py he eliminado la parte del código que crea el objeto aplicación y el MainLoop, evidentemente, ya que la aplicación se lanzará desde aquí.

2) El idioma del widget wx.calendar está en inglés. Podemos cambiar el idioma de wx, mediante la instrucción wx.Locale(wx.LANGUAGE_SPANISH). A partir de ahora, saldrán los días de la semana en español, al igual que los meses.

3) He creado una clase a partir de la clase "gestion_agenda", denominada "info_agenda". ¿Por qué? Porque quería dar una funcionalidad extra a esta clase, la inclusión del atributo "fecha_actual", para poder controlar en todo momento la fecha actual en la que se encuentra el objeto. Esta funcionalidad no está en la clase base ya que es una mejora específica para mi aplicación, y no tiene porqué ser genérica. Si necesitara otra cosa, la defino aquí, así no tengo que estar cambiando nada de la clase base. Podríamos decir que heredo de una clase para incorporar nuevas funcionalidades específicas para resolver un problema. Esto es lo bueno de POO, añado funcionalidades cuando me conviene, sin modificar nada que pueda interferir en otras cosas.

4) Creamos una clase a partir de la clase "frame_agenda", denominada "agenda" es decir, nuestro diseño gráfico en wxPython. ¿Por qué? Pues porque hay que incluir en el __init__ de la clase la asociación de eventos con los manejadores (binding), es decir, que según qué cosas hagamos en los widgets (eventos que ocurran, como por ejemplo hacer click de ratón en un día del calendario, cerrar la aplicación, etc.), tendrán una respuesta, en forma de disparar un código asociado a dicho evento. He programado 2 manejadores de eventos:
  1. Al cerrar la ventana se dispara OnCerrarVentana. Este manejador guarda la información en un fichero XML, y sale de la aplicación.
  2. Al cambiar la fecha del calendario se dispara OnCambiarFecha. Este manejador actualiza la información que hubiera en la estructura DOM, mediante el método modificar_dia. Para ello recoge los datos que están en el cajón de texto, los lleva al día correspondiente fijado por el atributo "fecha_actual", y cuando estamos en la siguiente fecha vuelve a buscar en la estructura DOM la información asociada.
5) Finalmente creo 2 funciones auxiliares para obtener e insertar el texto asociado a fechas en el cajón de texto:
    def obtener_texto(self):
aux = self.text_ctrl_1.GetValue()
return aux

def insertar_texto(self, texto):
self.text_ctrl_1.SetValue(texto)



¿Cómo podemos saber los eventos del widget wx.calendar? Otra vez, San Google está ahí para algo. De todas formas podemos hacer otra cosa, y es que en wxGlade nos aparecen los eventos que soportan los widgets.


Finalmente, nuestra aplicación debe de tener la siguiente apariencia, y la funcionalidad que le hemos dado, por supuesto.




3 comentarios:

  1. Muchas gracias por lo que escribís. Este tutorial en especial me es de mucha ayuda.

    Encontré un error en:

    class agenda(agenda_widget.frame_agenda):
    ...
    agenda_vista.frame_agenda.__init__(self, *args, **kwds)

    Donde dice vista debería decir widget (al menos así funcionó para mí).

    Estoy experimentando con sqlite con la idea de reimplementar tu código con una base de datos (para practicar un poco).

    Saludos desde Argentina.

    Eduardo

    ResponderEliminar