Está en la página 1de 7

MIDDLEWARE

Pensando en Python (II):


3 en raya en modo grfico
DIEGO LZ. DE IPIA GZ. DE ARTAZA (profesor del departa- de la Python Library Referente (http://www.
mento de Ingeniera del Software de la facultad de python.org/doc/current/lib/module-Tkinter.html).
Ingeniera (ESIDE) de la Universidad de Deusto)
Tkinter es una adaptacin en Python de la API
grfica tk ofrecida por el lenguaje de programa-
En esta segunda entrega de la serie cin Tcl. Se distingue por ser multiplataforma y
venir integrada con la distribucin estndar de
sobre Python aprenderemos a realizar Python, no tendremos que instalar nada adicional.
aplicaciones grficas y a guardar de Sin embargo, no presenta una apariencia nativa.
manera persistente datos que han de Es similar a Java Swing que independientemente
de la plataforma (Windows, UNIX o Mac) ofrece,
subsistir a la ejecucin de un por defecto, la misma apariencia. Su uso es muy
programa. Extenderemos la sencillo, pero es lenta y ofrece un nmero limita-
do de controles grficos. Carece de controles
aplicacin de tres en raya de la avanzados como los paneles multipestaa, los
anterior entrega, con una interfaz rboles de directorios o incluso controles ms
sencillos, como el combobox. Pmw (Python meta
grfica y la capacidad de guardar en widgets) disponible en http://pmw.sourceforge.net,
ficheros los detalles de los jugadores define encima de Tkinter una serie de componen-
tes ms sofisticados. Aunque resuelve las limita-
y sus estadsticas. ciones de controles de Tkinter, hereda su lentitud
y su apariencia no nativa.
Introduccin Existen otras toolkits que definen finas capas de
Python encima de toolkits grficas ms conocidas
La clave del xito de toda aplicacin profesional es que tk tales como GTK y Qt en Linux y la Windows
proveer una interfaz grfica amigable que facilite API. Estos mdulos Python son llamados PyGTK
la interaccin con el usuario y la explotacin de su (http://www.riverbankcomputing.co.uk/pyqt/),
funcionalidad. Adems, la mayora de los progra- PyQt (http://www.pygtk.org/) y Pythonwin
mas que usamos a diario deben guardar (o recor- (http://www. python.org/windows/pythonwin/),
dar) datos o preferencias utilizados en anteriores respectivamente. Su principal desventaja es que
ejecuciones de los mismos. Es decir, a menudo las estn orientados a una plataforma especfica
aplicaciones requieren de un mecanismo para (UNIX o Windows), aunque las interfaces que pro-
persistir datos a disco, siendo los mecanismos ducen son muy rpidas dado que interactan
ms convencionalmente utilizados los ficheros o directamente con APIs nativas de la plataforma.
bases de datos. En este artculo aprenderemos a Su uso, es ms complicado que en el caso de
usar Python en el desarrollo de interfaces grficas Tkinter.
y la serializacin de datos en ficheros. La toolkit wxPython (http://www.wxpython.org/)
Pospondremos la programacin de bases de datos es multiplataforma, presenta apariencia nativa y
desde Python hasta la tercera entrega de esta ofrece un alto rendimiento grfico. Est basada en
serie. la API en C++ wxWidgets (http://www.wxwid-
gets.org/). wxWidgets ha venido representado
Programacin grfica en Python desde hace unos aos una alternativa muy capaz
y sencilla tanto a las APIs de Windows: MFC
Python ofrece varias libreras de clases (o toolkits) (Microsoft Foundation Classes) e incluso .NET
alternativas para la implementacin de una apli- Windows Forms, como a las toolkits grficas de
cacin grfica. Entre otras, las ms conocidas son: Linux: GTK y Qt. El combo wxWidgets/wxPython
Tkinter, PyGTK, PyQt, Pythonwin y wxPython. A nos permite realizar interfaces grficas muy sofis-
continuacin las analizamos y elegimos una de ticadas de una manera tan sencilla como Tkinter.
Material complementario ellas, wxPython. A juicio del autor wxPython es la mejor toolkit
El material complementario de Tkinter es la toolkit para el desarrollo de interfaces para el desarrollo de interfaces grficas con
este artculo est disponible en grficas (GUIs) que acompaa a la distribucin de Python. Combina eficiencia, con sencillez de uso y
http://digital.revistasprofesionales.com Python. La documentacin sobre esta API es parte 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 explota- vez contenedoras de controles. Toda ventana


cin, adoptando tu aplicacin la apariencia y puede tener una barra de mens y herramien-
forma ms apropiada a tu plataforma. En la tas (MenuBar y ToolBar), una lnea de estado
siguiente seccin estudiamos esta toolkit en ms (StatusBar) y un icono para cuando sta se
profundidad. minimice.
Los marcos, dilogos y paneles se usan para
wxPython colocar controles que sirven para interactuar con
una aplicacin. Algunos de los controles ms
wxWidgets fue creada para proveer una manera comunes son: Button, CheckBox, Choice, ListBox,
barata y flexible de maximizar la inversin realiza- RadioBox y Slider. wxWidgets/wxPython propor-
da en el desarrollo de aplicaciones con interfaz ciona cualquiera de los controles que te puedes
grfica. A pesar de que otras libreras de clases ya encontrar en tus aplicaciones grficas en Linux o
existan para desarrollo multiplataforma, ninguna Windows. Estos controles derivan de la clase
cumpla los siguientes criterios: base Control, la cual deriva a su vez de la clase
 Bajo precio base Window. Para facilitar la creacin de aplica-
 Cdigo abierto (Open Source) ciones grficas, wxPython provee un conjunto de
 Facilidad de programacin clases de dilogo comunes ya preparadas tales
 Soporte de un amplio rango de compiladores como MessageDialog o FileDialog.
Hasta Febrero del 2004, wxWidgets era conocida
como wxWindows. Sin embargo, dadas las reper- Aplicacin Hola Solop
cusiones legales que el uso del trmino
Windows, marca registrada por Microsoft, Pongamos en prctica estas nociones sobre
podra tener, los creadores de wxWindows y wxPython con el ms simple de los ejemplos.
wxPython, Julian Smart y Robin Dunn, respectiva- Vamos a desarrollar una simple ventana que
mente, decidieron, previa sugerencia de Microsoft, muestre el logo de Slo Programadores (vase la
renombrar el proyecto como wxWidgets. figura 1). El listado 1 muestra el cdigo de esta
wxPython aade una simple capa de Python enci- aplicacin. En primer lugar, creamos una clase
ma de wxWidgets, aislando la complejidad de la MiAplic que deriva de wx.App, requerimiento que
programacin en C++ y todava ofreciendo la debe cumplir toda aplicacin wxWidgets. Para evi-
misma funcionalidad de esta completsima API a tar que la aplicacin termine una vez creada la
los programadores de Python. instancia de MiAplic, invocamos su mtodo
MainLoop, el cul har que el programa empiece
Instalando wxPython a escuchar eventos de ratn y teclado sobre la
En Windows, wxPython tan slo requiere que aplicacin grfica.
hayas instalado previamente Python en tu sistema. La clase MiAplic sobrescribe el mtodo OnInit de
De la direccin de Internet http://www.wxpython.org/ wx.App, donde se crean los elementos grficos de
download.php se puede bajar un ejecutable con la aplicacin. En este mtodo llamamos en primer
un instalador sobre el que slo hay que hacer lugar a la funcin wx.InitAllImageHandlers() para
doble clic. Para Linux, la instalacin es un poco inicializar los gestores de imgenes (bmp, jpg,
ms complicada. wxPython depende de las librer- png, etc.), dado que vamos a usar un fichero .jpg
as glib y gtk+, que normalmente ya estn instala- con el logo de Slo Programadores. Luego, crea-
das en todos los sistemas Linux. Adems, requiere mos una instancia de una imagen, pasando como
el uso de la librera compartida wxGTK. Para deta- parmetros el nombre de la imagen (solop.jpg) y el
lles sobre cmo conseguir su instalacin, tipo de la imagen (wx.BITMAP_TYPE_JPEG). A
mirar tanto la URL http://www.wxpython.org/ continuacin, creamos una instancia de la clase
download.php como http://www.wxwidgets.org, MiMarco. Finalmente, visualizamos la ventana
seccin de Downloads. MiMarco y nos aseguramos que sea la ventana
principal de la aplicacin al invocar el mtodo
Programando con wxPython SetTopWindow.
La clase MiMarco convierte la imagen a un bit-
En wxPython todas las clases estn definidas map, obtiene el tamao de la imagen creada e ini-
dentro del mdulo wx. Por tanto, en toda apli- cializa la clase padre de MiMarco, que es un
cacin debemos importar ese mdulo. Para ini- wx.Frame. Finalmente, inicializa el atributo
cializar una aplicacin wxPython hay que crear self.bmp con una instancia de un control
una clase que derive de la clase wx.App y sobre- wx.StaticBitmap, que contiene el bitmap creado Figura 1. Aplicacin Hola
Solop en wxPython.
escriba su mtodo App.OnInit. Toda aplicacin con anterioridad.
debe tener al menos un marco (Frame) o dilo- No siempre es necesario (o conve-
go (Dialog) como nivel superior. Cada marco o niente) crear de manera program-
dilogo contendr, a su vez, opcionalmente, tica las interfaces en wxPython.
una o ms instancias de clases como Panel, Hay herramientas que nos ayuda-
SplitterWindow u otras ventanas, que son a su rn a generar el cdigo wxPython

27 SOLO PROGRAMADORES n 119


MIDDLEWARE

LISTADO 1 Hola Solop en wxPython cada control se detallan los eventos que ste
puede generar.
#!/usr/bin/env python
import wx
Gestores de posicin: sizers
class MiMarco(wx.Frame): Los gestores de posicin, representados por la
Clase frame que visualiza una imagen. clase wx.Sizer y sus descendientes en la jerar-
def __init__(self, imagen, padre=None, id=-1,
pos=wx.DefaultPosition, titulo=Hola solo programadores): qua de clases de wxPython, se han converti-
Crea una instancia de Frame y visualiza una imagen. do en el mtodo elegido para definir el posi-
temp = imagen.ConvertToBitmap() cionamiento de controles en dilogos, dada su
tamano = temp.GetWidth(), temp.GetHeight()
wx.Frame.__init__(self, padre, id, titulo, pos, tamano) habilidad para crear dilogos atractivos inde-
self.bmp = wx.StaticBitmap(self, -1, temp) pendientes de la plataforma, que tengan en
consideracin las diferencias en tamao y
class MiAplic(wx.App): estilo de los controles individuales. El cuadro
La clase aplicacion.
def OnInit(self): Gestores de posicin ms comnmente utili-
wx.InitAllImageHandlers() zados muestra algunos de los sizers disponi-
imagen = wx.Image(solop.jpg, wx.BITMAP_TYPE_JPEG) bles en wxPython.
self.miMarco = MiMarco(imagen)
self.miMarco.Show()
self.SetTopWindow(self.miMarco) Gestores de posicin ms comnmente
return True utilizados
if __name__ == __main__: Sizer Clase base abstracta
miAplic = MiAplic() GridSizer Un gestor de posicin que dispone
miAplic.MainLoop()
los controles en una matriz donde
las celdas tienen el mismo tamao.
correspondiente. wxGlade (http://wxglade.sour- BoxSizer Gestor para disponer controles en
ceforge.net/) es un buen ejemplo. wxGlade es un una fila o columna.
diseador de interfaces que asiste en la creacin
de interfaces grficas con wxPython y Dibujando grficos
wxWidgets. Genera cdigo fuente tanto en En wxPython no se puede dibujar directamen-
Python como en C++. te en una ventana. Es preciso utilizar un con-
texto de dispositivo (device context o DC). DC
Manejo de eventos es la clase base para ClientDC, PaintDC,
Toda ventana, y, por extensin, todo control MemoryDC, PostScriptDC, MemoryDC,
(dado que los controles derivan de la clase MetafileDC and PrinterDC. La virtud de los DC
wx.Window), definen una serie de eventos que es que se puede utilizar exactamente el mismo
se lanzan cuando el usuario interacta sobre cdigo para dibujar encima de cualquiera de
ellos. wxPython define por cada control una estos dispositivos. Algunos ejemplos de las
tabla de eventos que asocia un tipo de evento funciones disponibles para dibujar son
con un gestor del mismo. Por ejemplo, como DC.DrawLine, DC.DrawCircle o DC.DrawText.
se ilustra debajo, para el evento EVT_BUTTON Para optimizar el pintado de figuras geomtri-
que ocurre cuando se pulsa un botn cas, es recomendable incluir las primitivas de
(botonNuevoUsuario), se podra asociar el dibujo entre las llamadas DC.BeginDrawing y
manejador OnNuevoUsuario. Obsrvese que el DC.EndDrawing. El color del fondo de cada
segundo parmetro de este mtodo evt es de figura dibujada se puede controlar con el con-
tipo wx.Event. Este objeto contiene informa- cepto de brocha (Brush) y el grosor y color de
cin sobre el evento: su tipo, identificador del los bordes de cada figura con el concepto de
control que lo gener, cundo ocurri el bolgrafo (Pen).
evento, una referencia al objeto que lo gene-
r, etc.: Aadiendo interfaz grfica a la aplicacin
... de tres en raya
wx.EVT_BUTTON(self, self.botonNuevoUsuario. Recordemos que en la anterior entrega definimos
GetId(), self.OnNuevoUsuario) una aplicacin de tres en raya en modo texto que
... requera el login de cada jugador y mantena en
Figura 2. Ventana de login a memoria las estadsticas sobre los resultados de
la aplicacin tres en raya. def OnNuevoUsuario(self, evt): sus partidas. Usando wxPython, aadiremos
# cdigo para crear nuevo usuario ahora las siguientes ventanas grficas a la aplica-
cin tres en raya:
Para poder conocer qu eventos puede gene- 1- Ventana de login (vase la figura 2) a travs de
rar cada control en wxPython remitimos al la cual el usuario puede entrar en la aplicacin,
lector a la documentacin de wxWidgets, que crear nuevos detalles de login de un usuario o
acompaa a la distribucin de wxPython. Por simplemente salir de la aplicacin.

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), self.botonNuevoUsuario, self.botonLogin y


donde se puede introducir el nombre de usua- self.botonSalir. Mediante las llamadas en la
rio y contrasea de un nuevo usuario forma:
3- Ventana principal de la aplicacin (vase la figu- wx.EVT_BUTTON(self, <identificadorBotnPulsado>,
ra 4), a travs de la cual el usuario puede iniciar <mtodoAInvocar>)
una partida, observar sus estadsticas de parti-
das jugadas o regresar a la ventana de login. Asocia mtodos que sern invocados cada vez que
4- Ventana de partida de tres en raya (vase la se pulse uno de estos botones. Por ejemplo:
figura 4), en la cual el usuario ver un tablero wx.EVT_BUTTON(self, self.botonSalir.GetId(),
de tres en raya sobre el cual podr jugar. self.OnSalir)
5- Dilogo de resultado de partida (vase la figu- Figura 3. Ventana de nuevo
usuario.
ra 4), en el que se indicar a la conclusin de La implementacin de los mane-
una partida el ganador de la misma o si ha jadores de eventos para estos
habido empate. botones: OnLogin, OnNuevoUsu-
6- Ventana de estadsticas (vase la figura 5) que ario y OnSalir pueden verse en
muestre los resultados obtenidos por un jugador. el listado 3. El manejador
OnNuevoUsuario crear una
Ventana de login ventana NuevoUsuarioGUI
El listado 2 muestra un extracto del construc-
tor de la clase LoginGUI, responsable de gene-
rar la ventana mostrada en figura 2. La clase LISTADO 2 Constructor de clase LoginGUI
LoginGUI hereda de wx.Frame. Su constructor
recibe como parmetro obligatorio una refe-
class LoginGUI(wx.Frame):
rencia al registro de jugadores y estadsticas def __init__(self, registro, parent=None, id=-1, title=Juego tres en
de partidas. Esta clase podra instanciarse con raya: login):
la llamada: wx.Frame.__init__(self, parent, id, title)
self.SetBackgroundColour(wx.WHITE)
ventanaLogin = LoginGUI(registro) self.registro = registro

en el constructor de LoginGUI, despus de # Preparamos la barra de menu


invocar el constructor de la clase padre, hace- menuBar = wx.MenuBar()
menu1 = wx.Menu()
mos que el fondo de la pantalla tenga color menu1.Append(101, &Salir, Haz clic para salir)
blanco. A continuacin, guardamos una refe- menuBar.Append(menu1, &TresEnRaya)
rencia al registro y creamos una barra de men self.SetMenuBar(menuBar)
a la que aadimos la cabecera de men wx.EVT_MENU(self, 101, self.OnSalir)
TresEnRaya, que contiene como una nica nombreUsuarioLabel = wx.StaticText(self, -1, Nombre usuario:)
opcin de seleccin Salir. Asociamos al evento self.nombreUsuarioTxtCtrl= wx.TextCtrl(self, -1, , size=(125, -1))
de seleccin de esta opcin, el mtodo de la
clase self.OnSalir, mediante la sentencia: passwordLabel = wx.StaticText(self, -1, Password:)
wx.EVT_MENU(self, 101, self.OnSalir) self.passwordTxtCtrl = wx.TextCtrl(self, -1, , size=(125, -1),
style=wx.TE_PASSWORD)
...
El contenido de este mtodo es mostrado en el lis-
tado 3. Simplemente destruye las ventanas que self.botonNuevoUsuario = wx.Button(self, -1, Nuevo usuario)
hayan sido creadas a partir de ella, wx.EVT_BUTTON(self, self.botonNuevoUsuario.GetId(),
ventanaUsuario (vase la figura 4) si existe, y self.OnNuevoUsuario)
self.botonLogin = wx.Button(self, -1, Login)
luego se destruye a si misma para acabar la apli- self.botonLogin.Enable(False)
cacin. wx.EVT_BUTTON(self, self.botonLogin.GetId(), self.OnLogin)
Despus de crear la barra de men el constructor self.botonSalir = wx.Button(self, -1, Salir)
wx.EVT_BUTTON(self, self.botonSalir.GetId(), self.OnSalir)
(vase el listado 2) crea los campos de entrada de
datos nombre de usuario y contrasea. Lo bsizer = wx.BoxSizer(wx.VERTICAL)
hace usando los controles wx.StaticText y
wx.TextCtrl. Ntese que el botn Login en la topSizer = wx.BoxSizer(wx.HORIZONTAL)
topSizer.Add(nombreUsuarioLabel, 0, wx.GROW|wx.ALL, 4)
figura 2 slo se activar si hay datos en los dos topSizer.Add(self.nombreUsuarioTxtCtrl, 0,
campos de entrada self.nombreUsuarioTxtCtrl y wx.GROW|wx.ALL|wx.ALIGN_RIGHT , 4)
self.passwordTxtCtrl. Esto se consigue al manejar bsizer.Add(topSizer, 0, wx.GROW|wx.ALL, 4)
...
el evento de introduccin de texto en uno de esos botonAccionSizer = wx.BoxSizer(wx.HORIZONTAL)
controles, mediante llamadas en la forma: botonAccionSizer.Add(self.botonNuevoUsuario, 0, wx.GROW|wx.ALL, 4)
...
wx.EVT_TEXT(self, self.nombreUsuarioTxtCtrl. bsizer.Add(botonAccionSizer, 0, wx.GROW|wx.ALL, 4)
GetId(), self.EvtText)
bsizer.Fit(self)
Tras crear los campos de entrada, el constructor self.SetAutoLayout(True)
instancia los botones de la ventana de login: self.SetSizer(bsizer)
wx.EVT_CLOSE(self, self.OnSalir)

29 SOLO PROGRAMADORES n 119


MIDDLEWARE

LISTADO 3 Manejadores de eventos de la clase LoginGUI El constructor (lneas finales del listado 2) usa
gestores de posicionamiento de tipo BoxSizer
def OnSalir(self, evt):
self.__cerrarAplicacion()
para colocar tanto en vertical como en horizon-
tal los controles antes mencionados. Cuando
def OnLogin(self, evt): creamos un BoxSizer indicamos la direccin en
try: la que colocaremos los controles, y luego sim-
self.registro.login(self.nombreUsuarioTxtCtrl.GetValue(),
self.passwordTxtCtrl.GetValue()) plemente usamos el mtodo Add del BoxSizer
except: para aadir controles al mismo. Como ltima
mostrarDialogo(self, No hay ningun usuario registrado con el nombre lnea del constructor definimos un manejador
de usuario y password especificados, Login incorrecto)
return para el evento cerrar ventana.

try: Ventana de partida (figura 4)


self.ventanaUsuario = El cdigo de la ventana de partida
LoggedInGUI(self.nombreUsuarioTxtCtrl.GetValue(),
self.passwordTxtCtrl.GetValue(), parent=self) (JuegoTresEnRayaGUI, vase el cuadro Material
self.ventanaUsuario.Show() complementario), presenta como una nica
self.Hide() novedad el uso del manejador GridSizer. El
except Exception, e:
mostrarDialogo(self, Problemas haciendo login para usuario + siguiente mtodo ilustra cmo se inicializa el
self.nombreUsuarioTxtCtrl.GetValue() + : + str(e.args), tablero de tres en raya con bitmaps que indican
Error al hacer login) casillas no seleccionadas. Bsicamente, se defi-
def OnNuevoUsuario(self, evt):
ne una matriz de 3x3 en la que se colocan los
ventanaNuevoUsuario = NuevoUsuarioGUI(self) botones sobre los que el usuario puede hacer
ventanaNuevoUsuario.Show() clic. Cuando el usuario hace clic se invoca el
self.Hide() mtodo self.OnClick que cambiar el icono para
def __cerrarAplicacion(self): mostrar que esa casilla ha sido seleccionada. La
if self.__dict__.has_key(ventanaUsuario): clase JuegoTresEnRayaGUI hereda de la clase
if self.ventanaUsuario: JuegoTresEnRaya vista en la anterior entrega:
self.ventanaUsuario.Destroy()
self.Destroy() def __initTablero(self):

def EvtText(self, event): self.gridsizer = wx.GridSizer(3,3,0,0)


if len(self.nombreUsuarioTxtCtrl.GetValue().strip()) > 0 and
len(self.passwordTxtCtrl.GetValue().strip()) > 0: self.celdaTablero = []
self.botonLogin.Enable(True)
else: self.bitmaps = []
self.botonLogin.Enable(False)
for i in range(9):

self.bitmaps.append(wx.Bitmap
(vase la figura 3) donde se podrn introducir (images/blank.png, wx.BITMAP_TYPE_PNG))
los detalles de un nuevo usuario y esconder self.celdaTablero.append(wx.BitmapButton
la ventana de login. El manejador OnLogin, (self, i, self.bitmaps[i]))
comprobar que el usuario ha sido registrado
Figura 4. Ventana principal de wx.EVT_BUTTON(self, i , self.OnClick)
y si es as, esconder la ventana de login y
la aplicacin tres en raya, con
ventana de partida y dilogo crear una ventana principal de la aplicacin self.gridsizer.Add(self.celdaTableroa[i])
de resultado. (vase la figura 4).
self.gridsizer.Fit(self)

self.SetAutoLayout(True)

self.SetSizer(self.gridsizer)
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) )
Figura 5. Ventana de estadsticas. dc.SetBrush( wx.Brush(RED) )

partidasJugadas = self.resultadoEstadisticas[0] +
wx.Frame. En el constructor se le asigna un self.resultadoEstadisticas[1] + self.resultadoEstadisticas[2] + 0.0
tamao fijo de 640x480 pixels y se define un if partidasJugadas > 0:
manejador OnPaint para el redibujado de la alturaBarraGanadas =
int(250*(self.resultadoEstadisticas[0]/partidasJugadas))
ventana. Este evento ser invocado cada vez alturaBarraEmpatadas =
que se mueva, maximice o cambie el tamao int(250*(self.resultadoEstadisticas[1]/partidasJugadas))
de la ventana de estadsticas. En OnPaint, se alturaBarraPerdidas =
crea una instancia de DC, se limpia el rea de int(250*(self.resultadoEstadisticas[2]/partidasJugadas))
if alturaBarraGanadas < 1:
dibujado del marco y se inicia el redibujado alturaBarraGanadas = 1
con la sentencia dc.BeginDrawing(). A conti- if alturaBarraEmpatadas < 1:
nuacin, se inicializa el formato de los trazos a alturaBarraEmpatadas = 1
if alturaBarraPerdidas < 1:
dibujar con un objeto Pen de color negro: alturaBarraPerdidas = 1
dc.SetPen(wx.Pen(BLACK,1)). Similarmente, else:
se inicializa una brocha de color rojo para el alturaBarraGanadas = 1
relleno de la primera barra de resultados a alturaBarraEmpatadas = 1
alturaBarraPerdidas = 1
dibujar: dc.SetBrush(wx.Brush(RED)). Luego
se dibujan las barras que muestran el porcen- cabecera = Total partidas jugadas: + str(int(partidasJugadas))
taje de victorias, empates y partidas perdidas (w, h) = dc.GetTextExtent(cabecera)
dc.DrawText(cabecera, 320-(w/2), 70-h) # dibujar cabecera grficos
por un jugador. Se calculan las alturas de cada
una de las barras y a continuacin se procede (w, h) = dc.GetTextExtent(Ganadas) # cabecera barra Ganadas
a su dibujado, por medio de las primitivas dc.DrawText(Ganadas, 160-(w/2), 390-h)
dc.DrawRectangle(100, 350, 120, -alturaBarraGanadas) # dibujar barra
dc.DrawRectangle. Es importante indicar que de ganadas
el origen de coordenadas de dibujado se (w, h) = dc.GetTextExtent(`self.resultadoEstadisticas[0]`)
encuentra en la parte superior izquierda de la dc.DrawText(`self.resultadoEstadisticas[0]`, 160-(w/2), 350-
ventana. Esa es la razn por la que se utilizan alturaBarraGanadas-20)
valores negativos para indicar tamaos en dc.SetBrush( wx.Brush(GREEN) )
vertical. Por ejemplo: # cdigo similar a barra Ganadas para barra verde de empates
...
dc.DrawRectangle(100, 350, 120,
-alturaBarraGanadas) dc.SetBrush( wx.Brush(BLUE) )
# cdigo similar a barra Ganadas para barra azul de empates
dibuja un rectngulo que empieza en las coorde- ...
nadas (100, 350), tiene 120 pixels de anchura y se dc.EndDrawing()
extiende hasta la coordenada 350-alturaBarraGa-
nadas de altura. Esta funcin devuelve un objeto de tipo fiche-
ro que define entre otros los mtodos read,
Serializacin de datos en Python para leer uno o un buffer de caracteres, write,
para escribir uno o un grupo de caracteres,
La mayora de los datos en los sistemas de readlines para leer todas las lneas de un
informacin se guardan en ficheros. Python fichero y close para cerrar un fichero. A conti-
ofrece unas interfaces muy sencillas para nuacin se muestra el cdigo para leer un
guardar y recuperar datos de ficheros. Para fichero de texto lnea a lnea. Como siempre,
leer o escribir un fichero en Python usamos la remitimos al lector a la Python Library
funcin predefinida open, que tiene la Referente (http://docs.python.org/lib/lib.html)
siguiente firma: para ms detalles:
open(<nombre-fichero>, <modo: r (read), # leerfichero.py
w(write), a(append), b(binario)>

31 SOLO PROGRAMADORES n 119


MIDDLEWARE

LISTADO 5 Haciendo persistente el registro de jugadores d[key] = data # guarda un valor bajo
key
class RegistroJugadoresPersistente(RegistroJugadores):
def __init__(self): data = d[key] # lo recupera
RegistroJugadores.__init__(self)
self._RegistroJugadores__jugadores = shelve.open(jugadores) del d[key] # lo borra
if not self._RegistroJugadores__jugadores.has_key(solop):
self._RegistroJugadores__jugadores[solop] = solop d.close() # cierra el diccionario
self._RegistroJugadores__estadisticas = shelve.open(estadisticas) persistente
if not self._RegistroJugadores__estadisticas.has_key(solop):
# jugador -> [ganados, empatados, perdidos] Aadiendo persistencia de datos
self._RegistroJugadores__estadisticas[solop] = [0, 0, 0]
a la aplicacin tres en raya
def __del__(self):
self._RegistroJugadores__jugadores.close() Una vez aadida una interfaz grfica a nuestra
self._RegistroJugadores__estadisticas.close() aplicacin de tres en raya, quizs el nico
aspecto que le resta para darle un carcter ms
fh = open(holamundo.py) # open crea un profesional, es aadirle la capacidad de recor-
objeto de tipo fichero dar los usuarios que se registran con la misma,
for line in fh.readlines() : # lee todas as como las estadsticas de los resultados
las lneas en un fichero obtenidos por esos usuarios. En la primera
print line,
entrega de esta serie definimos una clase lla-
mada RegistroJugadores que mantena en dos
fh.close() diccionarios, en memoria, informacin sobre
$ python leerfichero.py
los jugadores registrados (__jugadores) y sobre
los resultados obtenidos por los mismos
#!/usr/bin/python (__estadisticas). Para hacer que estos datos se
guarden de manera persistente, slo debemos
print Hola mundo
definir una nueva clase que herede de
El siguiente cdigo muestra cmo escribir datos a RegistroJugadores a la que llamaremos
un fichero de texto: RegistroJugadoresPersistente. El listado 5
# escribirfichero.py muestra el cdigo de esta clase. En el cons-
tructor de esta nueva clase nos aseguramos
fh = open(out.txt, w) que los miembros de RegistroJugadores
fh.write (estamos escribiendo ...\n) __jugadores y __estadisticas pasen de ser sim-
ples mapas a shelves, es decir, mapas de datos
fh.close() persistentes. Es preciso recordar que en Python
$ python escribirfichero.py se usa la tcnica de name mangling, para hacer
que todos los campos y funciones privadas o
$ cat out.txt protected, no sean visibles desde fuera. Por esa
estamos escribiendo ... razn, tenemos que usar la notacin
_nombreClase__nombreCampoPrivado, por
Serializacin de objetos: Pickle y shelves ejemplo, self._RegistroJugadores__estadisti-
Al igual que otros lenguajes modernos como cas, para tener acceso a estos campos.
Java y C#, Python ofrece mecanismos para Finalmente, para asegurarnos que el mapa per-
serializar y deserializar objetos desde disco o a sistente es correctamente cerrado, aadimos
travs de la red. El mdulo pickle implementa en el destructor de la clase (__del__) llamadas
un algoritmo para la serializacin y deseriali- a los metodos close() de las baldas de datos
zacin de objetos en Python. Para serializar (shelves).
una jerarqua de objetos, deberemos crear un
Pickler, y luego llamar al mtodo dump(), Conclusiones
pasndole como argumento el objeto a seriali-
zar. Para deserializar crearemos un Unpickler e En esta entrega hemos incrementado nuestros
invocaremos su mtodo load(). El mdulo conocimientos del lenguaje de programacin
shelve, por su parte, define diccionarios per- Python, aprendiendo cmo se pueden crear ven-
sistentes. Las claves tienen que ser cadenas de tanas grficas con la toolkit wxPython y cmo se
caracteres mientras que los valores pueden ser pueden serializar datos a travs de ficheros, y los
cualquier objeto que se puede serializar con mdulos pickle y shelve. Con sto, hemos conse-
pickle. A continuacin mostramos un ejemplo guido darle una apariencia y un funcionamiento
de cmo implementar un diccionario o mapa mucho ms profesional a la aplicacin de tres en
persistente: raya que comenzamos en la anterior entrega. En
import shelve el prximo artculo aprenderemos a crear una
interfaz web al juego de tres en raya y a guardar
d = shelve.open(filename) # abre un los datos de jugadores y estadsticas en una base
fichero de datos.

SOLO PROGRAMADORES n 119 32

También podría gustarte