jueves, 17 de marzo de 2022

Interfaces gráficas de usuario con Python. (Tkinter y Guizero). Introducción.

 


¿Que es GUI?

Llamámos GUI (Grafical User Interface) a una interfaz de software basado en elementos visuales, tales como ventanas, botones y cajas de texto. Cuando utilizas un sistema operativo como Windows, Mac o Rapsbian estás usando el GUI propio de cada sistema operativo. 

¿Qué necesitaremos?

A lo largo de tema, que nos llevará varios capítulos, crearemos nuestros propios GUI usando Python 3 y varias librerías:

- TKinter. Será nuestra librería principal. Se basa en una biblioteca anterior llamada tcl/tk que está disponible también para otros lenguajes como Perl y Rubi. 

- Guizero. Importante. Necesita tener la anterior librería instalada (tkinter) para que funcione. Su misión es permitirnos crear GUI´s para nuestras aplicaciones más rápida y fácilmente que con Thinker pero basándose en la anterior.

Y si Guizero se basa o necesita tkinter ¿Por que utilizarla? Pues porque Guizero está pensado para usuarios noveles y es a mi gusto, más rápida de aprender y fácil de utilizar. No obstante, para ver cual te viene mejor, vamos a ver los diferentes temas que trataremos usando ambas librerías para que puedas comparar y utilizar la que mejor te venga.

Pero recuerda que puedes usar uno u otro indistintamente, e incluso puedes usar los dos a la vez como veremos más adelante.


Consultar la versión de tkinter.

Normalmente el paquete Tkinter está disponible en la instalación de Python que tengamos. No obstante para comprobar si realmente la tenemos instalada y cual es su versión, vamos a crear un entorno virtual e introducir los siguientes comandos:

>>> import tkinter

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

comprobación de versión de tkinter


 Instalar tkinter.

Si por lo que fuese en nuestra instalación de Python, en un equipo con GNU/linux, no se encuentra el paquete tkinter lo podemos hacer manualmente dentro o fuera del entorno virtual con:

pip3 install tk

o

si lo queremos instalar fuera del entorno virtual, es decir para que este disponible para todo el equipo lo podemos hacer con:

 $ sudo apt-get install python3-tk

Instalación de Guizero.


Desde el gestor Pip, y en mi caso dentro de un entorno virtual, tecleamos:

miEntorno $ pip3 install guizero
En este enlace puedes encontrar la documentación del programa, que aunque está en ingles, viene muy bien estructurada y explicada.

Ahora que tenemos todo instalado vamos a hacer la primera aplicación usando ambos sistemas tkinter y guizero. Los siguientes ejemplos los he puesto para que veas la estructura general de la aplicación en ambos sistemas, pero no te preocupes si de momento no entiendes mucho. Lo iremos viendo poco a poco en próximos capítulos.

La primera aplicación con tkinter.

En el siguiente ejemplo crearemos un simple ventana con un botón. Cuando presionemos dicho botón, el programa termina su ejecución y se cerrará. La ventana es el elemento principal en este tipo de aplicaciones gráficas y todo gira en torno a ellas. Va a ser el primer elemento que se crea siempre en un programa y sobre el que se colocan el resto de objetos llamados widgets (menús, barras, botones, cajas de texto etc)




""" Crea una ventana simple con un botón para salir """

# Las dos líneas siguientes son necesarias 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)
import tkinter.ttk as ttk  # Carga ttk (para poder usar widgets nuevos 8.5+)

# Define la ventana principal de la aplicación

raiz = Tk()

# Define las dimensiones de la ventana. 
# Si se omite esta línea, es decir no se especifica su tamaño, 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 el usuario 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 con guizero.


Ahora que hemos hecho nuestra primera ventana con Tkinter vamos a hacer lo mismo, pero usando la librería Guizero. No te preocupes si no entiendes todos los conceptos ya que iremos profundizando en ellos a lo largo del curso. Por ahora se trata de tener una idea general de su funcionamiento. 

Lo importante es ver como se crea la aplicación y entender que dentro de la ventana principal iremos colocando los diferentes widgets o elementos. Cuales son y su funcionamiento, los iremos viendo con calma mas adelante.




from guizero import *

def claro():
    boton.bg = "#ececec"
    boton.when_mouse_leaves = oscuro

def oscuro():
    boton.bg = "#d9d9d9"


raiz=App(title="Aplicación", bg='beige', width=300, height=200)

boton = PushButton(raiz, text='Salir', align='bottom', padx=25, pady=2, command=quit)
boton.bg="#d9d9d9"

boton.when_mouse_enters = claro

raiz.display()

Echemos un vistazo al código creado línea por línea para ver que hace.

from guizero import *

Esta línea le dice a Python que importe todos los objetos existentes en la librería Guizero. Por ejemplo, en este código hemos usado los objetos App y Pushbutton.

def claro():
    boton.bg = "#ececec"
    boton.when_mouse_leaves = oscuro

def oscuro():
    boton.bg = "#d9d9d9"

Estas dos funciones no serían estrictamente necesarias pero si, si queremos que este ejemplo con Guizero sea exactamente igual al comportamiento del creado con Tkinter. Veras, si ejecutas el ejemplo de Tkinter y pasas el puntero por encima del botón, verás que su color de fondo pasa a un gris muy claro para dar una sensación de movimiento y vuelve a ser oscuro cuando se sale de él.

Sin embargo en Guizero, si no definiésemos estas funciones y un evento posterior que hace que el botón tenga un color claro cuando se entra a el y oscuro cuando se sale, el botón siempre tendría el mismo color. Si no se especifica sería el mismo que el fondo de la ventana. Vamos quedaría una cosa como esta:

botón sin eventos

Para que se vea más claro el código que genera la imagen de arriba es este:

from guizero import *

raiz=App(title="Aplicación", bg='beige', width=300, height=200)

boton = PushButton(raiz, text='Salir', align='bottom', padx=25, pady=2, command=quit)

raiz.display()

Seguimos con la explicación del código original.

La siguiente línea es:

raiz=App(title="Aplicación", bg='beige', width=300, height=200)

Aquí estamos asignando a la variable que hemos llamado "raiz" un objeto que es la aplicación o ventana principal de guizero. Este objeto "App()" es el básico de todas las GUIs creadas con guizero ya que es la ventana principal en la que se recogerán todos los demás elementos. 

También configuramos su titulo (title), color de fondo (bg) y dimensiones, tanto ancho (width) como alto(heigth). En esta aplicación es donde colocaremos todos los otros widgets a medida que construyamos la interfaz del usuario. Nota: En este punto aun no hemos pedido que se dibuje nada en la pantalla, solo hemos definido la ventana principal.

boton = PushButton(raiz, text='Salir', align='bottom', padx=25, pady=2, command=quit)
boton.bg="#d9d9d9"

boton.when_mouse_enters = claro

Hemos asignado a la variable "boton" un objeto que no es otra cosa que un "PushButton" es decir un botón que se pueda pulsar. Lo primero le hemos especificado que lo coloque en la ventana principal (raiz), que le asigne un texto (salir), que lo coloque en la parte baja (align='bottom') con un determinado tamaño. Cuando se pulse el botón lo que este hará será salir de la aplicación (command=quit);

Luego por tema estético, como ya te explique antes, le damos a ese botón un color de fondo (gris) y creamos un evento, que es que cuando el puntero del ratón entre en ese botón se ejecuta la función claro, que pone el color de fondo en un gris más claro. Acto seguido se crea otro evento para que cuando se salga del área definida por el ratón se vuelva al color original.

Por último,

raiz.display()

¡Ahora si!. Este último comando es el que le dice al ordenador que muestre en la pantalla todo lo que hemos dibujado.

Lo mismo pero con programación orientada a objetos.


Si has trabajado antes con la programación orientada a objetos, te habrás dado cuenta que los objetos tienen diferentes propiedades. Si no lo has hecho, ¡No te preocupes! Las programación de GUI´s es una de las mejores formas de familiarizarse con ellas.

A continuación vamos a hacer las mismas aplicaciones que antes, con las mismas librerías, pero con una programación orientada a objetos. Lo vamos a ver porque programarlo de esta forma nos facilita la gestión de los widgets y los eventos que se producirán en las mismas. 

Normalmente cuando haces una aplicación de este tipo la persona va a interactuar con ella, presionando un botón, escribiendo algo en una caja de texto, seleccionando una opción de un menú etc. En estos casos lo habitual será relacionar estos eventos con unas acciones a realizar y esto lo facilita bastante la POO.

Primer ejemplo con Tkinter.

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 funcion main() que es en realidad la que indica
# el comienzo del programa. Dentro de ella se crea el objeto
# aplicacion 'mi_app' basado en la clase 'Aplicacion':

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

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

if __name__ == '__main__':
    
    main()
    

Es lo mismo que el primer ejemplo pero creando una clase "Aplicación" e inicializándola con las propiedades de la misma.


Primer ejemplo pero con POO y Guizero.

# Importa la libreria guizero
from guizero import *

class Aplicacion():
    # Definimos el constructor de la clase aplicación.
    def __init__(self):        
                                     
        # Definimos la ventana principal con su título, color de fondo, ancho, 
        # alto de la ventana principal.
        self.raiz = App(title='Aplicación', bg='beige', width=300, height=200)
        # Creamos un objeto boton
        # Texto, alineación abajo, distancia de las letras al x e y, 
        # comando del botón destruir la raiz.
        self.boton = PushButton(self.raiz, text='Salir', align='bottom', padx=25, pady=2, 
        command=self.raiz.destroy)
        # Le asignamos el color de fondo inicial del boton
        self.boton.bg="#d9d9d9"
        # Vamos a definir un evento para cuando el raton entre en el boton lance
        # la funcion claro
        self.boton.when_mouse_enters=self.claro
        # Despues de definir todo, mostramos la aplicación
        self.raiz.display()       
        
    def claro(self):
            self.boton.bg="#ececec"
            #evento para cuando el ratón sale del botón que vuelva la color normal
            self.boton.when_mouse_leaves=self.normal
        
    def normal(self):
            self.boton.bg="#d9d9d9"
        
# Define la funcion main() que es en realidad la que indica
# el comienzo del programa. Dentro de ella se crea el objeto
# aplicacion '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
# módulo. Python utiliza este atributo cuando se ejecuta
# un programa para conocer si el módulo es ejecutado de forma
# independiente (en este caso __name__ = '__main__' o es
# importado:      

if __name__ == '__main__':
    main() # Ejecuta la función que lanza el programa

     

Ejemplo práctico de como mostrar información en una ventana.

Para acabar el capitulo vamos a crear una aplicación que haga algo. En este caso mostrar una serie de información sobre ella misma, es decir sobre la ventana que lo ejecuta. 

Para ello en la ventana de la aplicación, además del botón salir de los ejemplos anteriores hemos añadido dos nuevos widgets. Un boton "info" que al pulsarse  (evento) haga aparecer información sobre la ventana, en una caja de texto que anteriormente estaba vacía.


Usando tkinter.




3_info.py

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.

- Gestor de Geometría Grid.
  - Gestor de Geometría Pack.
  - Gestor de Geometria Place.

Su uso lo veremos más adelante.

Usando Guizero.

ventana informativa con guizero


'''Crea una ventana con dos botones. Uno da información de la ventana y otro para salir'''

from guizero import *

class Aplicacion():    
      
    def __init__(self):
        
        self.raiz=App(title='Ver Info', bg='beige', width=310, height=210)
        
        # Vamos a impedir que los bordes puedan desplazarse para
        # ampliar o reducir el tamaño de la ventana 'self.raiz'. Esto no esta disponible directamente en guizero.
        # En guizero no encontré la opción para no poder cambiar el tamaño de la ventana, así que
        # como al fin y al cabo la raíz es un objeto de tk uso una orden de tk.resizable()
        self.raiz.tk.resizable(width=False, height=False)
        
        self.tinfo = TextBox(self.raiz, width=40, height=10, multiline=True,text="")
        self.tinfo.bg='white'
        
        self.binfo = PushButton(self.raiz, text='Info', align='left', padx=25, pady=2, command=self.verinfo)
        self.binfo.focus()
        self.binfo.when_mouse_enters=self.claro
        
        
        self.bsalir = PushButton(self.raiz, text='Salir', align='right', padx=25, pady=2, command=self.raiz.destroy)
        self.bsalir.when_mouse_enters=self.claro
        
    def verinfo(self):
            
        info1 = self.raiz.tk.winfo_class()
        info2 = self.raiz.tk.winfo_geometry()
        info3 = str(self.raiz.tk.winfo_width())
        info4 = str(self.raiz.tk.winfo_height())
        info5 = str(self.raiz.tk.winfo_rootx())
        info6 = str(self.raiz.tk.winfo_rooty())
        info7 = str(self.raiz.tk.winfo_id())
        info8 = str(self.raiz.tk.winfo_name())
        info9 = str(self.raiz.tk.winfo_manager())
            
        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"
            
        print(texto_info)
        self.tinfo.value=texto_info
        
    def claro(self, event_data):
        event_data.widget.bg="#ececec"
        #evento para cuando el ratón sale del botón que vuelva la color normal
        event_data.widget.when_mouse_leaves=self.normal
        
    def normal(self, event_data):
        event_data.widget.bg="#f5f5dc"
               
def main():
    mi_app = Aplicacion()
    return 0

if __name__ == '__main__':
    
    main()

Y con esto finalizamos el capítulo de introducción. Puedes encontrar en los siguientes links el manual de Tkinter y el de Guizero.

También puedes encontrar mucha y buena información en "Python 3 para impacientes".

No hay comentarios:

Publicar un comentario