viernes, 25 de diciembre de 2015

Tkinter: interfaces gráficas en Python


Introducción


Con Python hay muchas posibilidades para programar una interfaz gráfica de usuario (GUI) pero Tkinter es fácil de usar, es multiplataforma y, además, viene incluido con Python en su versión para Windows, para Mac y para la mayoría de las distribuciones GNU/Linux. Se le considera el estándar de facto en la programación GUI con Python.

Tkinter es un binding de la biblioteca Tcl/Tk que está también disponible para otros lenguajes como Perl y Rubí.

A pesar de su larga historia, su uso no está demasiado extendido entre los usuarios de equipos personales porque su integración visual con los sistemas operativos no era buena y proporcionaba pocos widgets (controles) para construir los programas gráficos.

Sin embargo, a partir de TKinter 8.5 la situación dio un giro de ciento ochenta grados en lo que se refiere a integración visual, mejorando en este aspecto notablemente; también en el número de widgets que se incluyen y en la posibilidad de trabajar con estilos y temas, que permiten ahora personalizar totalmente la estética de un programa. Por ello, ahora Tkinter es una alternativa atractiva y tan recomendable como otras.

Este tutorial tiene como objetivo principal introducir al desarrollador que no está familiarizado con la programación GUI en Tkinter. Para ello, seguiremos una serie de ejemplos que muestran, de manera progresiva, el uso de los elementos que son necesarios para construir una aplicación gráfica: ventanas, gestores de geometría, widgets, menús, gestión de eventos, fuentes, estilos y temas. Todo a velocidad de crucero. Para impacientes.


Consultar la versión de Tkinter


Normalmente, el paquete Tkinter estará disponible en nuestra instalación Python, excepto en algunas distribuciones GNU/Linux. Para comprobar la versión de Tkinter instalada existen varias posibilidades:

1) Iniciar el entorno interactivo de Python e introducir:

>>> import tkinter
>>> tkinter.Tcl().eval('info patchlevel')


2) Si tenemos el instalador Pip introducir:

$ pip3 show tkinter


Instalar Tkinter


Si en nuestra instalación de Python, en un equipo con GNU/Linux, no se encuentra el paquete Tkinter instalar con:

$ sudo apt-get install python3-tk

(En el resto de plataformas cuando se instala Python se incluyen también los módulos de Tkinter).


La primera aplicación con Tkinter


El siguiente ejemplo crea una aplicación que incluye una ventana con un botón en la parte inferior. Al presionar el botón la aplicación termina su ejecución. Una ventana es el elemento fundamental de una aplicación GUI. Es el primer objeto que se crea y sobre éste se colocan el resto de objetos llamados widgets (etiquetas, botones, etc.).




#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Las dos líneas siguientes son necesaias para hacer 
# compatible el interfaz Tkinter con los programas basados 
# en versiones anteriores a la 8.5, con las más recientes. 

from tkinter import *    # Carga módulo tk (widgets estándar)
from tkinter import ttk  # Carga ttk (para widgets nuevos 8.5+)

# Define la ventana principal de la aplicación

raiz = Tk()

# Define las dimensiones de la ventana, que se ubicará en 
# el centro de la pantalla. Si se omite esta línea la
# ventana se adaptará a los widgets que se coloquen en
# ella. 

raiz.geometry('300x200') # anchura x altura

# Asigna un color de fondo a la ventana. Si se omite
# esta línea el fondo será gris

raiz.configure(bg = 'beige')

# Asigna un título a la ventana

raiz.title('Aplicación')

# Define un botón en la parte inferior de la ventana
# que cuando sea presionado hará que termine el programa.
# El primer parámetro indica el nombre de la ventana 'raiz'
# donde se ubicará el botón

ttk.Button(raiz, text='Salir', command=quit).pack(side=BOTTOM)

# Después de definir la ventana principal y un widget botón
# la siguiente línea hará que cuando se ejecute el programa
# construya y muestre la ventana, quedando a la espera de 
# que alguna persona interactúe con ella.

# Si la persona presiona sobre el botón Cerrar 'X', o bien,
# sobre el botón 'Salir' el programa llegará a su fin.

raiz.mainloop()


La primera aplicación, orientada a objetos


A continuación, se muestra la misma aplicación pero orientada a objetos. Aunque este tipo de programación siempre es recomendable con Python no es imprescindible. Sin embargo, si vamos a trabajar con Tkinter es lo más adecuado, sobre todo, porque facilita la gestión de los widgets y de los eventos que se producen en las aplicaciones. Desde luego, todo van a ser ventajas.

Normalmente, cuando se ejecuta una aplicación gráfica ésta se queda a la espera de que una persona interactúe con ella, que presione un botón, escriba algo en una caja de texto, seleccione una opción de un menú, sitúe el ratón en una posición determinada, etc., o bien, se produzca un suceso en el que no haya intervención humana como que termine un proceso, que cambie el valor de una variable, etc. En cualquiera de estos casos, lo habitual será vincular estos eventos o sucesos con unas acciones a realizar, que pueden ser mejor implementadas con las técnicas propias de la programación orientada a objetos.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
from tkinter import ttk

# Crea una clase Python para definir el interfaz de usuario de
# la aplicación. Cuando se cree un objeto del tipo 'Aplicacion'
# se ejecutará automáticamente el método __init__() qué 
# construye y muestra la ventana con todos sus widgets: 

class Aplicacion():
    def __init__(self):
        raiz = Tk()
        raiz.geometry('300x200')
        raiz.configure(bg = 'beige')
        raiz.title('Aplicación')
        ttk.Button(raiz, text='Salir', 
                   command=raiz.destroy).pack(side=BOTTOM)
        raiz.mainloop()

# Define la función main() que es en realidad la que indica 
# el comienzo del programa. Dentro de ella se crea el objeto 
# aplicación 'mi_app' basado en la clase 'Aplicación':

def main():
    mi_app = Aplicacion()
    return 0

# Mediante el atributo __name__ tenemos acceso al nombre de un
# un módulo. Python utiliza este atributo cuando se ejecuta
# un programa para conocer si el módulo es ejecutado de forma
# independiente (en ese caso __name__ = '__main__') o es 
# importado:

if __name__ == '__main__':
    main()


Obtener información de una ventana


Para finalizar este capítulo se incluye una aplicación basada en los ejemplos anteriores que sirve para algo, concretamente, para mostrar información relacionada con la ventana.

Para ello, en la ventana de la aplicación se han agregado nuevos widgets: un botón con la etiqueta "Info" y una caja de texto que aparece vacía.

También, se ha incluido un método que será llamado cuando se presione el botón "Info" para obtener la información e insertarla en la caja de texto:




#!/usr/bin/env python
# -*- coding: utf-8 -*-

from tkinter import *
from tkinter import ttk

# La clase 'Aplicacion' ha crecido. En el ejemplo se incluyen
# nuevos widgets en el método constructor __init__(): Uno de
# ellos es el botón 'Info'  que cuando sea presionado llamará 
# al método 'verinfo' para mostrar información en el otro 
# widget, una caja de texto: un evento ejecuta una acción: 

class Aplicacion():
    def __init__(self):
        
        # En el ejemplo se utiliza el prefijo 'self' para
        # declarar algunas variables asociadas al objeto 
        # ('mi_app')  de la clase 'Aplicacion'. Su uso es 
        # imprescindible para que se pueda acceder a sus
        # valores desde otros métodos:
        
        self.raiz = Tk()
        self.raiz.geometry('300x200')
        
        # Impide que los bordes puedan desplazarse para
        # ampliar o reducir el tamaño de la ventana 'self.raiz':
        
        self.raiz.resizable(width=False,height=False)
        self.raiz.title('Ver info')
        
        # Define el widget Text 'self.tinfo ' en el que se
        # pueden introducir varias líneas de texto:
        
        self.tinfo = Text(self.raiz, width=40, height=10)
        
        # Sitúa la caja de texto 'self.tinfo' en la parte
        # superior de la ventana 'self.raiz':
        
        self.tinfo.pack(side=TOP)
        
        # Define el widget Button 'self.binfo' que llamará 
        # al metodo 'self.verinfo' cuando sea presionado
        
        self.binfo = ttk.Button(self.raiz, text='Info', 
                                command=self.verinfo)
        
        # Coloca el botón 'self.binfo' debajo y a la izquierda
        # del widget anterior
                                
        self.binfo.pack(side=LEFT)
        
        # Define el botón 'self.bsalir'. En este caso
        # cuando sea presionado, el método destruirá o
        # terminará la aplicación-ventana 'self.raíz' con 
        # 'self.raiz.destroy'
        
        self.bsalir = ttk.Button(self.raiz, text='Salir', 
                                 command=self.raiz.destroy)
                                 
        # Coloca el botón 'self.bsalir' a la derecha del 
        # objeto anterior.
                                 
        self.bsalir.pack(side=RIGHT)
        
        # El foco de la aplicación se sitúa en el botón
        # 'self.binfo' resaltando su borde. Si se presiona
        # la barra espaciadora el botón que tiene el foco
        # será pulsado. El foco puede cambiar de un widget
        # a otro con la tecla tabulador [tab]
        
        self.binfo.focus_set()
        self.raiz.mainloop()
    
    def verinfo(self):
        
        # Borra el contenido que tenga en un momento dado
        # la caja de texto
        
        self.tinfo.delete("1.0", END)
        
        # Obtiene información de la ventana 'self.raiz':
        
        info1 = self.raiz.winfo_class()
        info2 = self.raiz.winfo_geometry()
        info3 = str(self.raiz.winfo_width())
        info4 = str(self.raiz.winfo_height())
        info5 = str(self.raiz.winfo_rootx())
        info6 = str(self.raiz.winfo_rooty())
        info7 = str(self.raiz.winfo_id())
        info8 = self.raiz.winfo_name()
        info9 = self.raiz.winfo_manager()
        
        # Construye una cadena de texto con toda la
        # información obtenida:
        
        texto_info = "Clase de 'raiz': " + info1 + "\n"
        texto_info += "Resolución y posición: " + info2 + "\n"
        texto_info += "Anchura ventana: " + info3 + "\n"
        texto_info += "Altura ventana: " + info4 + "\n"
        texto_info += "Pos. Ventana X: " + info5 + "\n"
        texto_info += "Pos. Ventana Y: " + info6 + "\n"
        texto_info += "Id. de 'raiz': " + info7 + "\n"
        texto_info += "Nombre objeto: " + info8 + "\n" 
        texto_info += "Gestor ventanas: " + info9 + "\n"
        
        # Inserta la información en la caja de texto:
        
        self.tinfo.insert("1.0", texto_info)

def main():
    mi_app = Aplicacion()
    return 0

if __name__ == '__main__':
    main()


En la aplicación se utiliza el método pack() para ubicar los widgets en una posición determinada dentro de la ventana. Dicho método da nombre a uno de los tres gestores de geometría existentes en Tkinter, que son los responsables de esta tarea.

Siguiente: Tkinter. Diseñando ventanas gráficas


Ir al índice del tutorial de Python

22 comentarios:

Tincho H dijo...

Hace mucho que sigo y aprendo mucho de esta excelente página. Por eso decidí dejar un comentario, que si bien no es nada, me vi obligado a hacerlo para agradecerte por la excelencia y por compartir todo este conocimiento. Para mí, esta página es la referencia directa de Python.

Muchas gracias.

Pherkad dijo...

Estimado Tincho H, Eres muy considerado. Muchas gracias y feliz día de Reyes ;-)

johan tavera dijo...

lo felicito por las explicaciones de esta pagina es la mejor que he encontrado

Pherkad dijo...

Hola Johan Tavera, muchas gracias, me alegra mucho que sea de utilidad ;-))

Ivan Gutierrez dijo...

Excepcional todo el contenido de la pagina. Llevo varios meses haciendo cursos, con libros en castellano, en inglés, cursos en coursera, edx y demás, y no he visto ningún sitio en el que expliquen las cosas claramente como estoy viendo aquí.

Recomiendo a cualquiera que busque aprender python que la siga.

Felicidades por este gran trabajo.

Ivan Gutierrez dijo...

Por cierto si te metieses a explicar modulos de redes y de data science estaría fenómeno, la verdad que Python puede con todo. :-D

Pherkad dijo...

Hola Ivan, tomo nota de las sugerencias. Muchísimas gracias ;-)))

Ray Vecor dijo...

Hola estimado Pherkad, muchas gracias por tus tutoriales que recien he tenido la fortuna de encontrar.

Estoy ejecutando los dos primeros ejemplos que has puesto sobre tkinter, pero con ambos se me ha generado el siguiente error:

_tkinter.TclError: no display name and no $display environment variable

Lamentablemente no lo he podido solucionar y solicito tu ayuda.

Tengo Ubuntu 16.04 corriendo en una máquina virtual instalada en Windows 10, Python 3.5 y Tkinter 8.6.5

Muchas gracias por lo que me puedas aportar

Ray Vecor dijo...

Estos son todos los detalles del error:


Traceback (most recent call last):
File "interfaz.py", line 37, in
main();
File "interfaz.py", line 27, in main
mi_app = Aplicacion();
File "interfaz.py", line 14, in __init__
raiz = Tk();
File "/usr/lib/python3.5/tkinter/__init__.py", line 1871, in __init__
self.tk = _tkinter.create(screenName, baseName, className, interactive, wantobjects, useTk, sync, use)
_tkinter.TclError: no display name and no $DISPLAY environment variable

Ray Vecor dijo...

El archivo interfaz.py tiene exactamente el mismo código del ejemplo que utilizas para la creación de aplicaciones orientadas a objetos

Pherkad dijo...

Hola Ray:

1) Antes de nada comentar que si inicias Ubuntu directamente en modo consola tkinter no funcionará. Es necesario iniciar en modo gráfico con gestor de ventanas.

2) Después, prueba en el entorno interactivo de Python el siguiente código para testear que tkinter está bien instalado:

import tkinter
tkinter._test()

Debe aparecer una ventana gráfica con dos botones para testear el funcionamiento básico. Ya me comentas el resultado de las comprobaciones.

Saludos

Ray Vecor dijo...

Muchas gracias Pherkad, por esa primera aclaración.

Instalaré el entorno gráfico y veré si con eso todo marcha bien.

Estaba trabajando en la máquina virtual (con Ubuntu server) haciendo una conexión sftp a windows a través de Notepad++ y pensé que así funcionaría.

JAGGER dijo...

No entiendo si es necesario definir name o que? Porque si defino a name como _main_ sólo me salta 0 y si le pongo otro nombre no me salta nada.

JAGGER dijo...

La verdad no entiendo para nada esta línea
if __name__ == '__main__':
main()
Cuando ejecutó el programa no hace nada y me salta algo como can't assign to function call. Estoy usando python 3.4.3 en windows 7

Pherkad dijo...

Hola JAGGER, buenos días

Antes de nada disculpa el retraso en responder.

La variable __name__ es una variable interna de Python que devuelve el nombre del script o del módulo que se está ejecutando y simplemente se utiliza para diferenciar durante el inicio del programa si el código que se ejecuta ha sido importado con "import" o no. Si el nombre es "__main__" significa que NO ha sido importado (que no es un módulo) y llama a la función main() para continuar su ejecución.

El error que te da la ejecución es de sintaxis. Revisa la escritura de los paréntesis (que no sean corchetes) en el nombre de la función.

chema dijo...

Buen comienzo de tutorial.

Me ha surgido un problema.

Ejecutando el tercer ejemplo (con copy-paste), el de los dos botones, binfo y bsalir,
los botones no se me muestran.
Si quito la restriccion de poder modificar la altura de la ventana, entonces, desplazando el borde inferior, si me aparecen los botones que permanecian ocultos, y todo funciona correctamente, aunque el aspecto no es idéntico al de su ejemplo, pero si equivalente.

Ejecuto con 3.6.1 en entorno windows 7.

Saludos y gracias.

Pherkad dijo...

Hola Chema

Disculpa el retraso en responder.

He probado con igual sistema operativo y versión de Python y todo funciona conforme a lo esperado.

Verifica si tienes algunos ajustes especiales en la configuración de la pantalla

chema dijo...

¿A qué te refieres con ajustes especiales en la configuración de pantalla?.
No soy consciente de haber hecho o intentar hacer nada especial.

Respecto al retraso, no te precupues. Si bien es cierto que el que espera desespera,
solo el que hayas podido encontrar tiempo para escribir este tutorial, ya es de agradecer. Que además nos dediques tiempo extra, un lujo asiático. :-)

Un saludo y muchas gracias.

Pherkad dijo...

Hola Chema, me refiero a las opciones de configuración de pantalla disponibles en Panel de Control para cambiar:

- Tamaños de fuentes de elementos en configuración de "Color y apariencia de las ventanas".
- Las "Opciones para facilitar la lectura",
- y opciones para "Establecer tamaño de texto personalizado (PPP)"

A ver si hay alguna opción que no sea la de por defecto que afecte.

Muchas gracias!

chema dijo...

No me oarece que el probema pueda venir por ahi. :-(
Tampoco, de un punto de vista conceptual, me parece muy importante.
Solo comentarte que si cambio la geometria a 300x230, me aparecen los botones.

Gracias a tu tutorial estoy empezando a hacer un interface para un sudoku, que progresa adecuadamnte. :-)

Me he encontrado un probema que por mucho que busco documentacion o algún ejemplo
no logro resolver. Si me pudieses dar alguna pista o referirme a alguna documentación o foro, te estaria agradecido.

Se trata de tener dentro de un Frame padre dos hijos que ocupan todo el espacio visible. Es decir estan superpuestos uno encima del otro, y busco el modo de, según, la situacion, presentar uno u otro.

Funcionalmente seria un frame que presentaria todos los números candidatos (en pequeño y por los bordes) y otro que presentaria, cuando se elija un número, ese número ya seleccionado, en grande, y centrado.

La funcionalidad de alternar la visibilidad de un frame al otro, no logró implementarla y no se me ocurren ideas.

Un saludo, enhorabuena por el tutorial y gracias.

chema dijo...

Bueno, casi siempre me pasa lo mismo.
Pedir ayuda incrementa mi suerte. :-)
He construido un ejemplo basado en la info
de la página https://mail.python.org/pipermail/tutor/2011-April/082903.html

--------------------------------------------
from tkinter import *

def toggle():
if mylabel1.visible:
mylabel1.pi = mylabel1.place_info()
mylabel1.place_forget()
mylabel2.place(mylabel2.pi)
else:
mylabel1.place(mylabel1.pi)
mylabel2.pi = mylabel2.place_info()
mylabel2.place_forget()
mylabel1.visible = not mylabel1.visible

root = Tk()

mylabel1 = Label(text="Example1")
mylabel1.visible = None
mylabel1.place(x=20,y=50)
mylabel2 = Label(text="Example2")
mylabel2.place(x=20,y=50)

mylabel1.pi = mylabel1.place_info()
mylabel2.pi = mylabel2.place_info()

btnToggle = Button(text="Toggle nr two",command=toggle)
btnToggle.place(x=70,y=150)

root.mainloop()

fabi P dijo...

amigo muy buena informacion .. talves podrias publicar algo de como mostrar lo s resultados de python en una interfaz saludos..