Este tutorial está pensado como una introducción y primera toma de contacto con la creación de interfaces gráficas en Python usando la biblioteca tkinter. Sin entrar en mucho detalle veremos los widgets más comunes y como se llama a trozos de código cuando ocurre un evento como la pulsación de un botón con el ratón.
Finalmente, en la parte más práctica construiremos un programa que genera combinaciones de la lotería Primitiva en dos formas. La primera usando funciones y la segunda, más avanzada, con programación orientada a objetos.
Figura 0: Euromillones GUI.
TKINTER BÁSICO
Autor: Alberto Peiró
Labels: Tkinter, Python 3
Tkinter básico
Tkinter y el modelo dirigido por eventos
Tkinter es la biblioteca estándar de Python para construir interfaces gráficas (GUI). En esencia es un puente entre Python y la biblioteca Tk que es la que en última instancia se comunica con el sistema de ventanas del sistema operativo. La biblioteca Tk es usada por otros lenguajes como Perl, Ruby y TCL por lo que tiene un amplio soporte. Otros módulos de Pyhton que extienden las capacidades de tkinter con nuevos y avanzados widgets son pmw (python megawidgets), tix (módulo tkinter.tix) y ttk (módulo tkinter.ttk que separa el comportamiento de la apariencia). Finalmente, otro paquete útil es la librería de procesamiento de imágenes PIL o el nuevo fork PILLOW.
La estructura que se crea cuando se usa tkinter es la que se denomina event driven (dirigida por eventos). Esta consiste en crear la GUI y esperar a que ocurra un evento. Durante la espera, la librería Tk se encuentra en un event loop (bucle) vigilante a cualquier evento como una pulsación de ratón o de una tecla, una vez se produce se llama inmediatamente a un trozo de código para su ejecución.
import tkinter
w = tkinter.Tk() # crea un objeto de la clase Tk
w.title('Ventana Tkinter')# título de la ventana
w.geometry('450x100+200+100')# posición y tamaño
tkinter.Label(w, text='Hola').pack() # introducimos un widget
w.mainloop() # inicia el bucle de eventos
Ejemplo 1. Creación de una ventana e inicio del bucle de eventos:
Los elementos de interfaz añadidos a una GUI se llaman widgets (controles, diales..). Cada tipo de ellos viene definido por una clase en tkinter. Los parámetros que se pasan al constructor son el objeto widget padre en el que quedará contenido (un valor None o ningún valor hace que se asigne el widget a la ventana que está en el nivel más alto). Otras opciones de configuración que se pueden dar son un texto, el tamaño del widget y, seguramente la más importante, los callback handlers que son las funciones que se llaman cuando se produce un evento.
Widgets
Cada widget que coloquemos en la interfaz gráfica es una instancia de un clase de tkinter que lo define. Las más comunes son las siguientes.
Tk
Clase contenedor raíz de la interfaz. Cada instancia de esta clase tiene un intérprete Tcl asociado. También es la ventana padre por defecto cuando no se especifica ninguna otra o se usa el valor None.
Toplevel
Crea ventanas independientes de la ventana raíz. Aunque no son procesos independientes.
Label
Muestra un mensaje (una cadena de texto) en un widget.
Button
Crea un botón, al evento de pulsación se le puede asociar una función.
Frame
Crea un marco. Se usa como contenedor para otros widgets.
Nota
Algunos métodos de los widgets tienen vida propia como funciones. Por ejemplo mainloop() puede llamarse sin asociarse a una ventana.
Empaquetador
La forma de añadir estos widgets a una una ventana o otro widget contenedor es con el método pack (hay otros métodos como grid o placer). Este método empaquetador acepta varios parámetros como side, anchor, expand y fill.
side = 'right', 'left', 'top' o 'bottom'. (Figura 1). Posiciona el widget en el lado indicado dentro del contenedor dejando el lado opuesto para el siguiente widget.
Figura 1: tres widgets con el parámetro side tomando respectivamente los valores 'left', 'top', y 'right'.
expand = 'yes' o 'no'. (Figura 2 y 3). Toma el espacio del contenedor que resta después de colocar los otros. El tamaño inicial del widget es el mínimo para mostrar lo que contiene.
Figura 2: Comportamiento de los widgets al agrandar la ventana con expand = 'no'.
Figura 3: Comportamiento de los widgets al agrandar la ventana con expand del primer widget a 'yes' y el resto a 'no'.
anchor = 'nw', 'n', 'ne', ... ,'se' y 'center'. Selecciona la posición en el hueco dado al widget.
fill = 'x', 'y' o 'both'. Estira el widget hasta rellenar todo el espacio reservado para él.
Configuración
El objeto puede configurarse de varias formas. En tiempo de construcción con los parámetros pasados en la llamada a la clase, o a posteriori usando el diccionario del widget o con el método config().
Callbacks
El siguiente paso es asociar eventos con ejecución de código (callbacks). La forma de hacerlo es pasando una función (realmente cualquier objeto con un método __call__) en la opción command. En un primer momento aparece una restricción que es que no se pueden pasar argumentos con la función, ya que en el caso de aportarlos la función se ejecuta cuando el botón es creado y no cuando capta una pulsación.
Sin embargo hay varias formas de conseguir pasar parámetros:
Usando funciones anónimas con la instrucción lambda. Básicamente, consiste en diferir la llamada creando una función intermedia que pasamos en la opción command. (Notar que el valor de los parámetros son fijados en el tiempo de construcción de la interfaz gráfica y no en tiempo de ejecución)
Usando variables globales. Aunque siempre es mejor utilizar parámetros que son más explicitos y fáciles de seguir en un programa.
Métodos ligados. Nos encontraríamos ya en programación orientada a objetos. En este caso se suministra en la opción command un método de la clase en el que implícitamente se pasa la instancia en el argumento self.
from tkinter import *
classBotonSalida:
def__init__(self):
boton = Button(None, text='PRESS', command=self.cierre)
boton.pack()
defcierre(self): # pasamos todo el objeto en self
self.quit()
BotonSalida()
mainloop()
Clases con un método __call__. Este método hace que las instancias de la clase puedan ser llamadas igual que una función pero con el plus de tener disponible todo el objeto con sus atributos y estado.
from tkinter import *
classExeSalida:
def__init__(self):
self.texto = 'Saliendo de la aplicacion'# informaciondef__call__(self): # se ejecuta cuando hacemos: instancia()print(self.texto)
self.quit()
boton = Button(None, text='PRESS', command=ExeSalida())
boton.pack()
mainloop()
Hay otras formas de capturar eventos además de la opción command. El método bind captura eventos a un nivel más básico, como una pulsación simple o un doble click, etc.
Nota
A lo largo de los ejemplos hemos ido cambiando la forma de importar el módulo tkinter. La última forma from modulo import *, generalmente, no es la más aconsejable, sin embargo para el caso de tkinter se considera que es relativamente seguro hacerlo.
Personalizando widgets con clases
El usar clases permite dos cosas: primera, personalizar los widgets usando la propiedad de herencia, y segunda crear una estructura fácilmente reutilizable en otros proyectos. En el siguiente programa definimos una nueva clase basada en la clase Frame y que puede ser reutilizada fácilmente.
from tkinter import *
classEuromillones(Frame):
def__init__(self, parent=None, **kargs):
Frame.__init__(self, parent, **kargs)
self.pack()
self.construccion()
self.iteracion = 0# estadodefconstruccion(self):
self.label = Label(self, text='Combinacion', width=30)
self.label.pack(side='left')
Button(self, text='Nueva', command=self.sorteo).pack()
defsorteo(self):
combinacion = .... # codigo que calcula una combinacion
self.iteracion += 1# actualiza el numero de iteraciones
self.label.configure(text = combinacion)
if __name__ == '__main__':
Euromillones().mainloop()
Ejemplo práctico
A continuación tenemos dos códigos que construyen la misma interfaz gráfica para el programa de generación de combinaciones de la Lotería de Euromillones. El primero está construido con funciones y el segundo usando programación orientada a objetos.
# euromillones.py
#
# Primera aproximacion al modulo tkinter.
import random
from tkinter import *
def combinacion():
'''Funcion que devuelve un cadena de texto con una combinacion de
la loteria de Euromillones.
Ejemplo: 12, 15, 23, 43, 45 C: 2, 10'''
numeros = random.sample(range(1,51),5)
numeros.sort()
complementarios = random.sample(range(1,12),2)
complementarios.sort()
return (str(numeros).strip('[]') +' C: ' +
str(complementarios).strip('[]'))
def sorteo():
'''Funcion que toma una cadena de texto y la presenta en el
widget label'''
label.configure(text = combinacion())
# construccion de las ventanas
winroot = Tk()
winroot.title('Euromillones')
label_info = Label(winroot,
text='Pulsa el boton para generar una combinacion.')
label_info.pack(side='top')
label = Label(winroot, text='Combinacion', width = 30)
label.pack(side='left')
boton = Button(winroot, text="Nueva", command = sorteo)
boton.pack(side='left')
mainloop() # inicia el bucle de eventos
Ejemplo GUI euromillones usando funciones.
# OOP_Euromillones.py
#
# Usando clases
import random
from tkinter import *
class Euromillones(Frame):
def __init__(self, parent=None, **kargs):
Frame.__init__(self, parent, **kargs)
self.pack()
self.construccion()
self.iteracion = 0 # estado
def construccion(self):
Label(self, text=
'Pulsa el boton para generar una combinacion.').pack()
self.label = Label(self, text='Combinacion', width=30)
self.label.pack(side='left')
Button(self, text='Nueva', command=self.sorteo).pack()
def combinacion(self):
numeros = random.sample(range(1,51),5)
numeros.sort()
complementarios = random.sample(range(1,12),2)
complementarios.sort()
return (str(numeros).strip('[]')+' C: ' +
str(complementarios).strip('[]')(
def sorteo(self):
self.iteracion += 1 # actualiza el numero de iteraciones
self.label.configure(text = self.combinacion())
if __name__ == '__main__':
Euromillones().mainloop()
Ejemplo GUI Euromillones usando programación orientada a objetos (OOP).