Anda di halaman 1dari 7

MIDDLEWARE

Pensando en Python (II): 3 en raya en modo grfico


DIEGO LZ. DE IPIA GZ. DE ARTAZA (profesor del departamento de Ingeniera del Software de la facultad de Ingeniera (ESIDE) de la Universidad de Deusto)

En esta segunda entrega de la serie sobre Python aprenderemos a realizar aplicaciones grficas y a guardar de manera persistente datos que han de subsistir a la ejecucin de un programa. Extenderemos la aplicacin de tres en raya de la anterior entrega, con una interfaz grfica y la capacidad de guardar en ficheros los detalles de los jugadores y sus estadsticas.
Introduccin
La clave del xito de toda aplicacin profesional es proveer una interfaz grfica amigable que facilite la interaccin con el usuario y la explotacin de su funcionalidad. Adems, la mayora de los programas que usamos a diario deben guardar (o recordar) datos o preferencias utilizados en anteriores ejecuciones de los mismos. Es decir, a menudo las aplicaciones requieren de un mecanismo para persistir datos a disco, siendo los mecanismos ms convencionalmente utilizados los ficheros o bases de datos. En este artculo aprenderemos a usar Python en el desarrollo de interfaces grficas y la serializacin de datos en ficheros. Pospondremos la programacin de bases de datos desde Python hasta la tercera entrega de esta serie.

Programacin grfica en Python


Python ofrece varias libreras de clases (o toolkits) alternativas para la implementacin de una aplicacin grfica. Entre otras, las ms conocidas son: Tkinter, PyGTK, PyQt, Pythonwin y wxPython. A continuacin las analizamos y elegimos una de ellas, wxPython. Tkinter es la toolkit para el desarrollo de interfaces grficas (GUIs) que acompaa a la distribucin de Python. La documentacin sobre esta API es parte

Material complementario
El material complementario de este artculo est disponible en http://digital.revistasprofesionales.com

de la Python Library Referente (http://www. python.org/doc/current/lib/module-Tkinter.html). Tkinter es una adaptacin en Python de la API grfica tk ofrecida por el lenguaje de programacin Tcl. Se distingue por ser multiplataforma y venir integrada con la distribucin estndar de Python, no tendremos que instalar nada adicional. Sin embargo, no presenta una apariencia nativa. Es similar a Java Swing que independientemente de la plataforma (Windows, UNIX o Mac) ofrece, por defecto, la misma apariencia. Su uso es muy sencillo, pero es lenta y ofrece un nmero limitado de controles grficos. Carece de controles avanzados como los paneles multipestaa, los rboles de directorios o incluso controles ms sencillos, como el combobox. Pmw (Python meta widgets) disponible en http://pmw.sourceforge.net, define encima de Tkinter una serie de componentes ms sofisticados. Aunque resuelve las limitaciones de controles de Tkinter, hereda su lentitud y su apariencia no nativa. Existen otras toolkits que definen finas capas de Python encima de toolkits grficas ms conocidas que tk tales como GTK y Qt en Linux y la Windows API. Estos mdulos Python son llamados PyGTK (http://www.riverbankcomputing.co.uk/pyqt/), PyQt (http://www.pygtk.org/) y Pythonwin (http://www. python.org/windows/pythonwin/), respectivamente. Su principal desventaja es que estn orientados a una plataforma especfica (UNIX o Windows), aunque las interfaces que producen son muy rpidas dado que interactan directamente con APIs nativas de la plataforma. Su uso, es ms complicado que en el caso de Tkinter. La toolkit wxPython (http://www.wxpython.org/) es multiplataforma, presenta apariencia nativa y ofrece un alto rendimiento grfico. Est basada en la API en C++ wxWidgets (http://www.wxwidgets.org/). wxWidgets ha venido representado desde hace unos aos una alternativa muy capaz y sencilla tanto a las APIs de Windows: MFC (Microsoft Foundation Classes) e incluso .NET Windows Forms, como a las toolkits grficas de Linux: GTK y Qt. El combo wxWidgets/wxPython nos permite realizar interfaces grficas muy sofisticadas de una manera tan sencilla como Tkinter. A juicio del autor wxPython es la mejor toolkit para el desarrollo de interfaces grficas con Python. Combina eficiencia, con sencillez de uso y nos permite escribir cdigo fuente una sola vez

SOLO PROGRAMADORES n 119

26

MIDDLEWARE

Pensando en Python (II): 3 en raya en modo grfico

independientemente de la plataforma de explotacin, adoptando tu aplicacin la apariencia y forma ms apropiada a tu plataforma. En la siguiente seccin estudiamos esta toolkit en ms profundidad.

wxPython
wxWidgets fue creada para proveer una manera barata y flexible de maximizar la inversin realizada en el desarrollo de aplicaciones con interfaz grfica. A pesar de que otras libreras de clases ya existan para desarrollo multiplataforma, ninguna cumpla los siguientes criterios: Bajo precio Cdigo abierto (Open Source) Facilidad de programacin Soporte de un amplio rango de compiladores Hasta Febrero del 2004, wxWidgets era conocida como wxWindows. Sin embargo, dadas las repercusiones legales que el uso del trmino Windows, marca registrada por Microsoft, podra tener, los creadores de wxWindows y wxPython, Julian Smart y Robin Dunn, respectivamente, decidieron, previa sugerencia de Microsoft, renombrar el proyecto como wxWidgets. wxPython aade una simple capa de Python encima de wxWidgets, aislando la complejidad de la programacin en C++ y todava ofreciendo la misma funcionalidad de esta completsima API a los programadores de Python.

vez contenedoras de controles. Toda ventana puede tener una barra de mens y herramientas (MenuBar y ToolBar), una lnea de estado (StatusBar) y un icono para cuando sta se minimice. Los marcos, dilogos y paneles se usan para colocar controles que sirven para interactuar con una aplicacin. Algunos de los controles ms comunes son: Button, CheckBox, Choice, ListBox, RadioBox y Slider. wxWidgets/wxPython proporciona cualquiera de los controles que te puedes encontrar en tus aplicaciones grficas en Linux o Windows. Estos controles derivan de la clase base Control, la cual deriva a su vez de la clase base Window. Para facilitar la creacin de aplicaciones grficas, wxPython provee un conjunto de clases de dilogo comunes ya preparadas tales como MessageDialog o FileDialog.

Aplicacin Hola Solop


Pongamos en prctica estas nociones sobre wxPython con el ms simple de los ejemplos. Vamos a desarrollar una simple ventana que muestre el logo de Slo Programadores (vase la figura 1). El listado 1 muestra el cdigo de esta aplicacin. En primer lugar, creamos una clase MiAplic que deriva de wx.App, requerimiento que debe cumplir toda aplicacin wxWidgets. Para evitar que la aplicacin termine una vez creada la instancia de MiAplic, invocamos su mtodo MainLoop, el cul har que el programa empiece a escuchar eventos de ratn y teclado sobre la aplicacin grfica. La clase MiAplic sobrescribe el mtodo OnInit de wx.App, donde se crean los elementos grficos de la aplicacin. En este mtodo llamamos en primer lugar a la funcin wx.InitAllImageHandlers() para inicializar los gestores de imgenes (bmp, jpg, png, etc.), dado que vamos a usar un fichero .jpg con el logo de Slo Programadores. Luego, creamos una instancia de una imagen, pasando como parmetros el nombre de la imagen (solop.jpg) y el tipo de la imagen (wx.BITMAP_TYPE_JPEG). A continuacin, creamos una instancia de la clase MiMarco. Finalmente, visualizamos la ventana MiMarco y nos aseguramos que sea la ventana principal de la aplicacin al invocar el mtodo SetTopWindow. La clase MiMarco convierte la imagen a un bitmap, obtiene el tamao de la imagen creada e inicializa la clase padre de MiMarco, que es un wx.Frame. Finalmente, inicializa el atributo self.bmp con una instancia de un control wx.StaticBitmap, que contiene el bitmap creado con anterioridad. No siempre es necesario (o conveniente) crear de manera programtica las interfaces en wxPython. Hay herramientas que nos ayudarn a generar el cdigo wxPython

Instalando wxPython
En Windows, wxPython tan slo requiere que hayas instalado previamente Python en tu sistema. De la direccin de Internet http://www.wxpython.org/ download.php se puede bajar un ejecutable con un instalador sobre el que slo hay que hacer doble clic. Para Linux, la instalacin es un poco ms complicada. wxPython depende de las libreras glib y gtk+, que normalmente ya estn instaladas en todos los sistemas Linux. Adems, requiere el uso de la librera compartida wxGTK. Para detalles sobre cmo conseguir su instalacin, mirar tanto la URL http://www.wxpython.org/ download.php como http://www.wxwidgets.org, seccin de Downloads.

Programando con wxPython


En wxPython todas las clases estn definidas dentro del mdulo wx. Por tanto, en toda aplicacin debemos importar ese mdulo. Para inicializar una aplicacin wxPython hay que crear una clase que derive de la clase wx.App y sobreescriba su mtodo App.OnInit. Toda aplicacin debe tener al menos un marco (Frame) o dilogo (Dialog) como nivel superior. Cada marco o dilogo contendr, a su vez, opcionalmente, una o ms instancias de clases como Panel, SplitterWindow u otras ventanas, que son a su

Figura 1. Aplicacin Hola Solop en wxPython.

27

SOLO PROGRAMADORES n 119

MIDDLEWARE

LISTADO 1
#!/usr/bin/env python import wx

Hola Solop en wxPython

cada control se detallan los eventos que ste puede generar.

Gestores de posicin: sizers


Los gestores de posicin, representados por la clase wx.Sizer y sus descendientes en la jerarqua de clases de wxPython, se han convertido en el mtodo elegido para definir el posicionamiento de controles en dilogos, dada su habilidad para crear dilogos atractivos independientes de la plataforma, que tengan en consideracin las diferencias en tamao y estilo de los controles individuales. El cuadro Gestores de posicin ms comnmente utilizados muestra algunos de los sizers disponibles en wxPython.

class MiMarco(wx.Frame): Clase frame que visualiza una imagen. def __init__(self, imagen, padre=None, id=-1, pos=wx.DefaultPosition, titulo=Hola solo programadores): Crea una instancia de Frame y visualiza una imagen. temp = imagen.ConvertToBitmap() tamano = temp.GetWidth(), temp.GetHeight() wx.Frame.__init__(self, padre, id, titulo, pos, tamano) self.bmp = wx.StaticBitmap(self, -1, temp) class MiAplic(wx.App): La clase aplicacion. def OnInit(self): wx.InitAllImageHandlers() imagen = wx.Image(solop.jpg, wx.BITMAP_TYPE_JPEG) self.miMarco = MiMarco(imagen) self.miMarco.Show() self.SetTopWindow(self.miMarco) return True if __name__ == __main__: miAplic = MiAplic() miAplic.MainLoop()

Gestores de posicin ms comnmente utilizados


Sizer GridSizer Clase base abstracta Un gestor de posicin que dispone los controles en una matriz donde las celdas tienen el mismo tamao. Gestor para disponer controles en una fila o columna.

correspondiente. wxGlade (http://wxglade.sourceforge.net/) es un buen ejemplo. wxGlade es un diseador de interfaces que asiste en la creacin de interfaces grficas con wxPython y wxWidgets. Genera cdigo fuente tanto en Python como en C++.

BoxSizer

Dibujando grficos
En wxPython no se puede dibujar directamente en una ventana. Es preciso utilizar un contexto de dispositivo (device context o DC). DC es la clase base para ClientDC, PaintDC, MemoryDC, PostScriptDC, MemoryDC, MetafileDC and PrinterDC. La virtud de los DC es que se puede utilizar exactamente el mismo cdigo para dibujar encima de cualquiera de estos dispositivos. Algunos ejemplos de las funciones disponibles para dibujar son DC.DrawLine, DC.DrawCircle o DC.DrawText. Para optimizar el pintado de figuras geomtricas, es recomendable incluir las primitivas de dibujo entre las llamadas DC.BeginDrawing y DC.EndDrawing. El color del fondo de cada figura dibujada se puede controlar con el concepto de brocha (Brush) y el grosor y color de los bordes de cada figura con el concepto de bolgrafo (Pen).

Manejo de eventos
Toda ventana, y, por extensin, todo control (dado que los controles derivan de la clase wx.Window), definen una serie de eventos que se lanzan cuando el usuario interacta sobre ellos. wxPython define por cada control una tabla de eventos que asocia un tipo de evento con un gestor del mismo. Por ejemplo, como se ilustra debajo, para el evento EVT_BUTTON que ocurre cuando se pulsa un botn (botonNuevoUsuario), se podra asociar el manejador OnNuevoUsuario. Obsrvese que el segundo parmetro de este mtodo evt es de tipo wx.Event. Este objeto contiene informacin sobre el evento: su tipo, identificador del control que lo gener, cundo ocurri el evento, una referencia al objeto que lo gener, etc.:
... wx.EVT_BUTTON(self, self.botonNuevoUsuario. GetId(), self.OnNuevoUsuario) ... def OnNuevoUsuario(self, evt): # cdigo para crear nuevo usuario

Aadiendo interfaz grfica a la aplicacin de tres en raya


Recordemos que en la anterior entrega definimos una aplicacin de tres en raya en modo texto que requera el login de cada jugador y mantena en memoria las estadsticas sobre los resultados de sus partidas. Usando wxPython, aadiremos ahora las siguientes ventanas grficas a la aplicacin tres en raya: 1- Ventana de login (vase la figura 2) a travs de la cual el usuario puede entrar en la aplicacin, crear nuevos detalles de login de un usuario o simplemente salir de la aplicacin.

Figura 2. Ventana de login a la aplicacin tres en raya.

Para poder conocer qu eventos puede generar cada control en wxPython remitimos al lector a la documentacin de wxWidgets, que acompaa a la distribucin de wxPython. Por

SOLO PROGRAMADORES n 119

28

MIDDLEWARE

Pensando en Python (II): 3 en raya en modo grfico

2- Ventana de nuevo usuario (vase la figura 3), donde se puede introducir el nombre de usuario y contrasea de un nuevo usuario 3- Ventana principal de la aplicacin (vase la figura 4), a travs de la cual el usuario puede iniciar una partida, observar sus estadsticas de partidas jugadas o regresar a la ventana de login. 4- Ventana de partida de tres en raya (vase la figura 4), en la cual el usuario ver un tablero de tres en raya sobre el cual podr jugar. 5- Dilogo de resultado de partida (vase la figura 4), en el que se indicar a la conclusin de una partida el ganador de la misma o si ha habido empate. 6- Ventana de estadsticas (vase la figura 5) que muestre los resultados obtenidos por un jugador.

self.botonNuevoUsuario, self.botonLogin y self.botonSalir. Mediante las llamadas en la forma:


wx.EVT_BUTTON(self, <identificadorBotnPulsado>, <mtodoAInvocar>)

Asocia mtodos que sern invocados cada vez que se pulse uno de estos botones. Por ejemplo:
wx.EVT_BUTTON(self, self.botonSalir.GetId(), self.OnSalir)

Ventana de login
El listado 2 muestra un extracto del constructor de la clase LoginGUI, responsable de generar la ventana mostrada en figura 2. La clase LoginGUI hereda de wx.Frame. Su constructor recibe como parmetro obligatorio una referencia al registro de jugadores y estadsticas de partidas. Esta clase podra instanciarse con la llamada:
ventanaLogin = LoginGUI(registro)

La implementacin de los manejadores de eventos para estos botones: OnLogin, OnNuevoUsuario y OnSalir pueden verse en el listado 3. El manejador OnNuevoUsuario crear una ventana NuevoUsuarioGUI

Figura 3. Ventana de nuevo usuario.

LISTADO 2

Constructor de clase LoginGUI

class LoginGUI(wx.Frame): def __init__(self, registro, parent=None, id=-1, title=Juego tres en raya: login): wx.Frame.__init__(self, parent, id, title) self.SetBackgroundColour(wx.WHITE) self.registro = registro # Preparamos la barra de menu menuBar = wx.MenuBar() menu1 = wx.Menu() menu1.Append(101, &Salir, Haz clic para salir) menuBar.Append(menu1, &TresEnRaya) self.SetMenuBar(menuBar) wx.EVT_MENU(self, 101, self.OnSalir) nombreUsuarioLabel = wx.StaticText(self, -1, Nombre usuario:) self.nombreUsuarioTxtCtrl= wx.TextCtrl(self, -1, , size=(125, -1)) passwordLabel = wx.StaticText(self, -1, Password:) self.passwordTxtCtrl = wx.TextCtrl(self, -1, , size=(125, -1), style=wx.TE_PASSWORD) ... self.botonNuevoUsuario = wx.Button(self, -1, Nuevo usuario) wx.EVT_BUTTON(self, self.botonNuevoUsuario.GetId(), self.OnNuevoUsuario) self.botonLogin = wx.Button(self, -1, Login) self.botonLogin.Enable(False) wx.EVT_BUTTON(self, self.botonLogin.GetId(), self.OnLogin) self.botonSalir = wx.Button(self, -1, Salir) wx.EVT_BUTTON(self, self.botonSalir.GetId(), self.OnSalir) bsizer = wx.BoxSizer(wx.VERTICAL) topSizer = wx.BoxSizer(wx.HORIZONTAL) topSizer.Add(nombreUsuarioLabel, 0, wx.GROW|wx.ALL, 4) topSizer.Add(self.nombreUsuarioTxtCtrl, 0, wx.GROW|wx.ALL|wx.ALIGN_RIGHT , 4) bsizer.Add(topSizer, 0, wx.GROW|wx.ALL, 4) ... botonAccionSizer = wx.BoxSizer(wx.HORIZONTAL) botonAccionSizer.Add(self.botonNuevoUsuario, 0, wx.GROW|wx.ALL, 4) ... bsizer.Add(botonAccionSizer, 0, wx.GROW|wx.ALL, 4) bsizer.Fit(self) self.SetAutoLayout(True) self.SetSizer(bsizer) wx.EVT_CLOSE(self, self.OnSalir)

en el constructor de LoginGUI, despus de invocar el constructor de la clase padre, hacemos que el fondo de la pantalla tenga color blanco. A continuacin, guardamos una referencia al registro y creamos una barra de men a la que aadimos la cabecera de men TresEnRaya, que contiene como una nica opcin de seleccin Salir. Asociamos al evento de seleccin de esta opcin, el mtodo de la clase self.OnSalir, mediante la sentencia:
wx.EVT_MENU(self, 101, self.OnSalir)

El contenido de este mtodo es mostrado en el listado 3. Simplemente destruye las ventanas que hayan sido creadas a partir de ella, ventanaUsuario (vase la figura 4) si existe, y luego se destruye a si misma para acabar la aplicacin. Despus de crear la barra de men el constructor (vase el listado 2) crea los campos de entrada de datos nombre de usuario y contrasea. Lo hace usando los controles wx.StaticText y wx.TextCtrl. Ntese que el botn Login en la figura 2 slo se activar si hay datos en los dos campos de entrada self.nombreUsuarioTxtCtrl y self.passwordTxtCtrl. Esto se consigue al manejar el evento de introduccin de texto en uno de esos controles, mediante llamadas en la forma:
wx.EVT_TEXT(self, self.nombreUsuarioTxtCtrl. GetId(), self.EvtText)

Tras crear los campos de entrada, el constructor instancia los botones de la ventana de login:

29

SOLO PROGRAMADORES n 119

MIDDLEWARE

LISTADO 3

Manejadores de eventos de la clase LoginGUI

def OnSalir(self, evt): self.__cerrarAplicacion() def OnLogin(self, evt): try: self.registro.login(self.nombreUsuarioTxtCtrl.GetValue(), self.passwordTxtCtrl.GetValue()) except: mostrarDialogo(self, No hay ningun usuario registrado con el nombre de usuario y password especificados, Login incorrecto) return try: self.ventanaUsuario = LoggedInGUI(self.nombreUsuarioTxtCtrl.GetValue(), self.passwordTxtCtrl.GetValue(), parent=self) self.ventanaUsuario.Show() self.Hide() except Exception, e: mostrarDialogo(self, Problemas haciendo login para usuario + self.nombreUsuarioTxtCtrl.GetValue() + : + str(e.args), Error al hacer login) def OnNuevoUsuario(self, evt): ventanaNuevoUsuario = NuevoUsuarioGUI(self) ventanaNuevoUsuario.Show() self.Hide() def __cerrarAplicacion(self): if self.__dict__.has_key(ventanaUsuario): if self.ventanaUsuario: self.ventanaUsuario.Destroy() self.Destroy() def EvtText(self, event): if len(self.nombreUsuarioTxtCtrl.GetValue().strip()) > 0 and len(self.passwordTxtCtrl.GetValue().strip()) > 0: self.botonLogin.Enable(True) else: self.botonLogin.Enable(False)

El constructor (lneas finales del listado 2) usa gestores de posicionamiento de tipo BoxSizer para colocar tanto en vertical como en horizontal los controles antes mencionados. Cuando creamos un BoxSizer indicamos la direccin en la que colocaremos los controles, y luego simplemente usamos el mtodo Add del BoxSizer para aadir controles al mismo. Como ltima lnea del constructor definimos un manejador para el evento cerrar ventana.

Ventana de partida (figura 4)


El cdigo de la ventana de partida (JuegoTresEnRayaGUI, vase el cuadro Material complementario), presenta como una nica novedad el uso del manejador GridSizer. El siguiente mtodo ilustra cmo se inicializa el tablero de tres en raya con bitmaps que indican casillas no seleccionadas. Bsicamente, se define una matriz de 3x3 en la que se colocan los botones sobre los que el usuario puede hacer clic. Cuando el usuario hace clic se invoca el mtodo self.OnClick que cambiar el icono para mostrar que esa casilla ha sido seleccionada. La clase JuegoTresEnRayaGUI hereda de la clase JuegoTresEnRaya vista en la anterior entrega:
def __initTablero(self): self.gridsizer = wx.GridSizer(3,3,0,0) self.celdaTablero = [] self.bitmaps = [] for i in range(9): self.bitmaps.append(wx.Bitmap (images/blank.png, wx.BITMAP_TYPE_PNG)) self.celdaTablero.append(wx.BitmapButton (self, i, self.bitmaps[i])) wx.EVT_BUTTON(self, i , self.OnClick) self.gridsizer.Add(self.celdaTableroa[i]) self.gridsizer.Fit(self) self.SetAutoLayout(True) self.SetSizer(self.gridsizer)

Figura 4. Ventana principal de la aplicacin tres en raya, con ventana de partida y dilogo de resultado.

(vase la figura 3) donde se podrn introducir los detalles de un nuevo usuario y esconder la ventana de login. El manejador OnLogin, comprobar que el usuario ha sido registrado y si es as, esconder la ventana de login y crear una ventana principal de la aplicacin (vase la figura 4).

Dilogo de resultado de partida (figura 4)


Todos los resultados, o mensajes que hay que dar al usuario se visualizan a travs de una funcin de ayuda, que simplemente instancia un wx.MessageDialog:
def mostrarDialogo(ventanaPadre, mensaje, titulo, modo=wx.OK): dialog = wx.MessageDialog(ventanaPadre, mensaje, titulo, modo); dialog.ShowModal()

Ventana de estadsticas (figura 5)


El listado 4 muestra la implementacin de la clase (EstadisticasGUI) que hereda de

SOLO PROGRAMADORES n 119

30

MIDDLEWARE

Pensando en Python (II): 3 en raya en modo grfico

LISTADO 4

Implementacin de la ventana de estadsticas

class EstadisticasGUI(wx.Frame): def __init__(self, ventanaUsuario, nombreUsuario, resultadoEstadisticas): ... self.anchura = 640 self.altura = 480 self.SetSize((self.anchura, self.altura)) # asociar tamao a ventana self.SetBackgroundColour(wx.WHITE) # cambiar color fondo wx.EVT_PAINT(self, self.OnPaint) def OnPaint(self, evt): dc = wx.PaintDC(self) # inicializar un contexto de dispositvo dc.Clear() dc.BeginDrawing() # indicar que se va a empezar a dibujar dc.SetPen( wx.Pen(BLACK,1) ) dc.SetBrush( wx.Brush(RED) ) partidasJugadas = self.resultadoEstadisticas[0] + self.resultadoEstadisticas[1] + self.resultadoEstadisticas[2] + 0.0 if partidasJugadas > 0: alturaBarraGanadas = int(250*(self.resultadoEstadisticas[0]/partidasJugadas)) alturaBarraEmpatadas = int(250*(self.resultadoEstadisticas[1]/partidasJugadas)) alturaBarraPerdidas = int(250*(self.resultadoEstadisticas[2]/partidasJugadas)) if alturaBarraGanadas < 1: alturaBarraGanadas = 1 if alturaBarraEmpatadas < 1: alturaBarraEmpatadas = 1 if alturaBarraPerdidas < 1: alturaBarraPerdidas = 1 else: alturaBarraGanadas = 1 alturaBarraEmpatadas = 1 alturaBarraPerdidas = 1 cabecera = Total partidas jugadas: + str(int(partidasJugadas)) (w, h) = dc.GetTextExtent(cabecera) dc.DrawText(cabecera, 320-(w/2), 70-h) # dibujar cabecera grficos (w, h) = dc.GetTextExtent(Ganadas) # cabecera barra Ganadas dc.DrawText(Ganadas, 160-(w/2), 390-h) dc.DrawRectangle(100, 350, 120, -alturaBarraGanadas) # dibujar barra de ganadas (w, h) = dc.GetTextExtent(`self.resultadoEstadisticas[0]`) dc.DrawText(`self.resultadoEstadisticas[0]`, 160-(w/2), 350alturaBarraGanadas-20) dc.SetBrush( wx.Brush(GREEN) ) # cdigo similar a barra Ganadas para barra verde de empates ... dc.SetBrush( wx.Brush(BLUE) ) # cdigo similar a barra Ganadas para barra azul de empates ... dc.EndDrawing()

Figura 5. Ventana de estadsticas.

wx.Frame. En el constructor se le asigna un tamao fijo de 640x480 pixels y se define un manejador OnPaint para el redibujado de la ventana. Este evento ser invocado cada vez que se mueva, maximice o cambie el tamao de la ventana de estadsticas. En OnPaint, se crea una instancia de DC, se limpia el rea de dibujado del marco y se inicia el redibujado con la sentencia dc.BeginDrawing(). A continuacin, se inicializa el formato de los trazos a dibujar con un objeto Pen de color negro: dc.SetPen(wx.Pen(BLACK,1)). Similarmente, se inicializa una brocha de color rojo para el relleno de la primera barra de resultados a dibujar: dc.SetBrush(wx.Brush(RED)). Luego se dibujan las barras que muestran el porcentaje de victorias, empates y partidas perdidas por un jugador. Se calculan las alturas de cada una de las barras y a continuacin se procede a su dibujado, por medio de las primitivas dc.DrawRectangle. Es importante indicar que el origen de coordenadas de dibujado se encuentra en la parte superior izquierda de la ventana. Esa es la razn por la que se utilizan valores negativos para indicar tamaos en vertical. Por ejemplo:
dc.DrawRectangle(100, 350, 120, -alturaBarraGanadas)

dibuja un rectngulo que empieza en las coordenadas (100, 350), tiene 120 pixels de anchura y se extiende hasta la coordenada 350-alturaBarraGanadas de altura.

Serializacin de datos en Python


La mayora de los datos en los sistemas de informacin se guardan en ficheros. Python ofrece unas interfaces muy sencillas para guardar y recuperar datos de ficheros. Para leer o escribir un fichero en Python usamos la funcin predefinida open, que tiene la siguiente firma:
open(<nombre-fichero>, <modo: r (read), w(write), a(append), b(binario)>

Esta funcin devuelve un objeto de tipo fichero que define entre otros los mtodos read, para leer uno o un buffer de caracteres, write, para escribir uno o un grupo de caracteres, readlines para leer todas las lneas de un fichero y close para cerrar un fichero. A continuacin se muestra el cdigo para leer un fichero de texto lnea a lnea. Como siempre, remitimos al lector a la Python Library Referente (http://docs.python.org/lib/lib.html) para ms detalles:
# leerfichero.py

31

SOLO PROGRAMADORES n 119

MIDDLEWARE

LISTADO 5

Haciendo persistente el registro de jugadores

d[key] = data # guarda un valor bajo key data = d[key] # lo recupera del d[key] # lo borra d.close() # cierra el diccionario persistente

class RegistroJugadoresPersistente(RegistroJugadores): def __init__(self): RegistroJugadores.__init__(self) self._RegistroJugadores__jugadores = shelve.open(jugadores) if not self._RegistroJugadores__jugadores.has_key(solop): self._RegistroJugadores__jugadores[solop] = solop self._RegistroJugadores__estadisticas = shelve.open(estadisticas) if not self._RegistroJugadores__estadisticas.has_key(solop): # jugador -> [ganados, empatados, perdidos] self._RegistroJugadores__estadisticas[solop] = [0, 0, 0] def __del__(self): self._RegistroJugadores__jugadores.close() self._RegistroJugadores__estadisticas.close() fh = open(holamundo.py) # open crea un objeto de tipo fichero for line in fh.readlines() : # lee todas las lneas en un fichero print line, fh.close() $ python leerfichero.py #!/usr/bin/python print Hola mundo

Aadiendo persistencia de datos a la aplicacin tres en raya


Una vez aadida una interfaz grfica a nuestra aplicacin de tres en raya, quizs el nico aspecto que le resta para darle un carcter ms profesional, es aadirle la capacidad de recordar los usuarios que se registran con la misma, as como las estadsticas de los resultados obtenidos por esos usuarios. En la primera entrega de esta serie definimos una clase llamada RegistroJugadores que mantena en dos diccionarios, en memoria, informacin sobre los jugadores registrados (__jugadores) y sobre los resultados obtenidos por los mismos (__estadisticas). Para hacer que estos datos se guarden de manera persistente, slo debemos definir una nueva clase que herede de RegistroJugadores a la que llamaremos RegistroJugadoresPersistente. El listado 5 muestra el cdigo de esta clase. En el constructor de esta nueva clase nos aseguramos que los miembros de RegistroJugadores __jugadores y __estadisticas pasen de ser simples mapas a shelves, es decir, mapas de datos persistentes. Es preciso recordar que en Python se usa la tcnica de name mangling, para hacer que todos los campos y funciones privadas o protected, no sean visibles desde fuera. Por esa razn, tenemos que usar la notacin _nombreClase__nombreCampoPrivado, por ejemplo, self._RegistroJugadores__estadisticas, para tener acceso a estos campos. Finalmente, para asegurarnos que el mapa persistente es correctamente cerrado, aadimos en el destructor de la clase (__del__) llamadas a los metodos close() de las baldas de datos (shelves).

El siguiente cdigo muestra cmo escribir datos a un fichero de texto:


# escribirfichero.py fh = open(out.txt, w) fh.write (estamos escribiendo ...\n) fh.close() $ python escribirfichero.py $ cat out.txt estamos escribiendo ...

Serializacin de objetos: Pickle y shelves


Al igual que otros lenguajes modernos como Java y C#, Python ofrece mecanismos para serializar y deserializar objetos desde disco o a travs de la red. El mdulo pickle implementa un algoritmo para la serializacin y deserializacin de objetos en Python. Para serializar una jerarqua de objetos, deberemos crear un Pickler, y luego llamar al mtodo dump(), pasndole como argumento el objeto a serializar. Para deserializar crearemos un Unpickler e invocaremos su mtodo load(). El mdulo shelve, por su parte, define diccionarios persistentes. Las claves tienen que ser cadenas de caracteres mientras que los valores pueden ser cualquier objeto que se puede serializar con pickle. A continuacin mostramos un ejemplo de cmo implementar un diccionario o mapa persistente:
import shelve d = shelve.open(filename) # abre un fichero

Conclusiones
En esta entrega hemos incrementado nuestros conocimientos del lenguaje de programacin Python, aprendiendo cmo se pueden crear ventanas grficas con la toolkit wxPython y cmo se pueden serializar datos a travs de ficheros, y los mdulos pickle y shelve. Con sto, hemos conseguido darle una apariencia y un funcionamiento mucho ms profesional a la aplicacin de tres en raya que comenzamos en la anterior entrega. En el prximo artculo aprenderemos a crear una interfaz web al juego de tres en raya y a guardar los datos de jugadores y estadsticas en una base de datos.

SOLO PROGRAMADORES n 119

32