Desarrollo de GUI de Python con Tkinter: Parte 3

Esta es la tercera entrega de nuestra serie de varias partes sobre el desarrollo de GUI en Python usando Tkinter. Echa un vistazo a los enlaces a continuación para ver las otras partes de esta serie:...

Esta es la tercera entrega de nuestra serie de varias partes sobre el desarrollo de GUI en Python usando Tkinter. Echa un vistazo a los enlaces a continuación para ver las otras partes de esta serie:

Introducción

[Tkinter] (https://docs.python.org/3/library/tkinter.html#module-tkinter) es el paquete estándar de facto para crear GUI en Python. En wikihtp's first y second Como parte del tutorial de Tkinter, aprendimos cómo usar los componentes básicos de la GUI para crear interfaces simples.

En la última parte de nuestro tutorial, veremos un par de atajos que ofrece Tkinter para permitirnos ofrecer funciones complejas y muy útiles sin esfuerzo. También aprenderemos sobre Python Mega Widgets, un conjunto de herramientas, basado en Tkinter, que acelera aún más la creación de interfaces complicadas.

Diálogo de archivo

Permitir que un usuario seleccione un archivo en su máquina es obviamente una característica muy común de las interfaces gráficas. Los diálogos de archivo suelen ser bastante complejos: combinan al menos varios botones (como Abrir, Cancelar o Crear nueva carpeta) y un marco que muestra la estructura de los directorios de nuestro entorno. Según nuestros tutoriales anteriores, puede suponer que al usar Tkinter es muy difícil crear una característica tan complicada. Sin embargo, en realidad, no lo es. Echa un vistazo al siguiente ejemplo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
import tkinter
import tkinter.filedialog

root = tkinter.Tk()

def print_path():
    f = tkinter.filedialog.askopenfilename(
        parent=root, initialdir='C:/Tutorial',
        title='Choose file',
        filetypes=[('png images', '.png'),
                   ('gif images', '.gif')]
        )

    print(f)

b1 = tkinter.Button(root, text='Print path', command=print_path)
b1.pack(fill='x')

root.mainloop()

Producción:

{.img-responsive}

El código anterior es todo lo que necesita para mostrar un Diálogo de archivo agradable y útil. En la línea 2 importamos el contenido de la clase filedialog. Luego, después de crear nuestra ventana raíz en la línea 4, definimos una nueva función en la línea 6 (que se supone que debe ejecutar el botón creado en la línea 17 y empaquetado en la línea 18).

Echemos un vistazo a la definición de la función print_path(). En la línea 7, ejecutamos la función askopenfilename, que toma un par de argumentos. El primer argumento, por supuesto, es el widget principal del diálogo (que en este caso es nuestra ventana raíz). Luego, en el argumento initialdir, proporcionamos una ubicación que se mostrará en nuestro cuadro de diálogo de archivo justo después de que se abra. título controla el contenido de la barra de título del cuadro de diálogo.

Y luego tenemos el argumento filetypes, gracias al cual podemos especificar qué tipo de archivos serán visibles para el usuario en el diálogo de archivos. Restringir los tipos de archivos puede hacer que la búsqueda del archivo deseado sea mucho más rápida, además de permitir que el usuario sepa qué tipos de archivos se aceptan.

El argumento de filetypes es una lista de tuplas de 2 elementos. En cada tupla, el primer elemento es una cadena que es cualquier descripción que queramos establecer para cada uno de los tipos de archivo. El segundo elemento es donde indicamos o enumeramos las extensiones de archivo asociadas con cada tipo de archivo (si solo hay una extensión, es una cadena; de lo contrario, es una tupla). Como puede ver en la captura de pantalla de salida anterior, el usuario puede seleccionar el tipo de archivo que se muestra en la lista desplegable en la esquina inferior derecha del cuadro de diálogo.

El método askopenfilename() devuelve una cadena que es la ruta del archivo seleccionado por el usuario. Si el usuario decide pulsar Cancelar, se devuelve una cadena vacía. En la línea 7, devolvemos la ruta a la variable f, y luego, en la línea 15 (que solo se ejecuta después de cerrar el diálogo de archivo), la ruta se imprime en la consola.

Mostrar imágenes usando Tkinter

Una cosa más interesante que muchas personas pueden encontrar útil para aplicar a sus GUI es mostrar imágenes. Modifiquemos un poco el ejemplo anterior.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import tkinter
import tkinter.filedialog

root = tkinter.Tk()

def display_image():
    f = tkinter.filedialog.askopenfilename(
        parent=root, initialdir='C:/Tutorial',
        title='Choose file',
        filetypes=[('png images', '.png'),
                   ('gif images', '.gif')]
        )

    new_window = tkinter.Toplevel(root)

    image = tkinter.PhotoImage(file=f)
    l1 = tkinter.Label(new_window, image=image)
    l1.image = image
    l1.pack()

b1 = tkinter.Button(root, text='Display image', command=display_image)
b1.pack(fill='x')

root.mainloop()

Producción:

{.img-responsive}

Veamos qué cambió dentro de la función ejecutada por nuestro botón, ahora renombrada como display_image. Mostramos el cuadro de diálogo Archivo, usamos los mismos criterios para la selección de archivos que antes, y nuevamente almacenamos la ruta devuelta en la variable f. Sin embargo, después de obtener la ruta del archivo, no la imprimimos en la consola. Lo que hacemos es crear una ventana de nivel superior en la línea 14. Luego, en la línea 16, instanciamos un objeto de la clase PhotoImage, haciéndolo leer el archivo .png que fue seleccionado por el usuario. Luego, el objeto se almacena en la variable imagen, que podemos pasar como argumento para la construcción del widget Etiqueta en la línea 17. En la línea 18, nos aseguramos de mantener una referencia al objeto imagen en orden. to evitar que el recolector de basura de Python lo elimine. Luego, en la línea 19, empaquetamos nuestra etiqueta (esta vez mostrando una imagen, no un texto) dentro de nueva_ventana.

Selector de color

Otra característica común, especialmente en el software enfocado en gráficos, es que permite al usuario seleccionar un color de una paleta. En este caso, Tkinter también ofrece una solución agradable y lista para usar que debería satisfacer la mayoría de nuestras necesidades con respecto a la función de elección de color.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import tkinter
import tkinter.colorchooser

root = tkinter.Tk()

def color_button():
    color = tkinter.colorchooser.askcolor(parent=root)
    print(color)
    b1.configure(bg=color[1])

b1 = tkinter.Button(root, text='Select Color', command=color_button)
b1.pack(fill='x')

root.mainloop()

Producción:

{.img-responsivo}

En la línea 2 del ejemplo que se muestra arriba, importamos una clase llamada colorchooser. Usamos su método askcolor() en la línea 7. Este método, similar a askopenfilename(), es responsable de abrir un cuadro de diálogo agradable y complejo (un selector de color en este caso) y devuelve los datos que dependen del usuario\ elección de . En este caso, después de que el usuario elige un color de la paleta y acepta su elección, el objeto devuelto a la variable color es una tupla que contiene dos elementos. El primer elemento es una tupla que almacena valores para los canales rojo, verde y azul del color seleccionado. El segundo elemento de la tupla es del mismo color especificado en formato hexadecimal. Podemos ver el contenido de las tuplas en nuestra consola, gracias a print() en la línea 8.

Después de almacenar la tupla devuelta por askcolor en la variable color, usamos esa variable en la línea 9 para configurar el botón b1. Como ya sabes, el argumento bg es responsable de controlar el color de fondo del botón. Le pasamos el primer elemento de la tupla color (la representación del color en formato hexadecimal). Como resultado, después de presionar el botón b1, el usuario puede cambiar su color de fondo usando un agradable selector de color.

Cuadros de mensajes {#cuadros de mensajes}

Antes de pasar de Tkinter a Python Mega Widgets, es bueno mencionar una característica más del módulo Tkinter que hace que la programación de GUI sea un poco más rápida. Tkinter ofrece los llamados cuadros de mensaje, que son un conjunto de cuadros de diálogo estándar simples, pero ampliamente utilizados. Estos cuadros de mensaje se pueden usar para mostrar un mensaje rápido, una advertencia o cuando necesitamos que nuestro usuario tome una decisión simple de sí o no. El siguiente ejemplo muestra todos los cuadros de mensaje que ofrece Tkinter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import tkinter
import tkinter.messagebox

root = tkinter.Tk()

def display_and_print():
    tkinter.messagebox.showinfo("Info","Just so you know")
    tkinter.messagebox.showwarning("Warning","Better be careful")
    tkinter.messagebox.showerror("Error","Something went wrong")

    okcancel = tkinter.messagebox.askokcancel("What do you think?","Should we go ahead?")
    print(okcancel)

    yesno = tkinter.messagebox.askyesno("What do you think?","Please decide")
    print(yesno)

    retrycancel = tkinter.messagebox.askretrycancel("What do you think?","Should we try again?")
    print(retrycancel)

    answer = tkinter.messagebox.askquestion("What do you think?","What's your answer?")
    print(answer)

b1 = tkinter.Button(root, text='Display dialogs', command=display_and_print)
b1.pack(fill='x')

top.mainloop()

Producción:

{.img-responsive}

Esta vez, nuestro botón b1 ejecuta la función display_and_print(). La función permite que aparezcan 7 cuadros de mensaje en secuencia; cada uno se muestra después de que el usuario interactúa con el anterior. Los diálogos definidos en las líneas 11 - 21 son diálogos que requieren que el usuario elija una de las dos opciones disponibles; por lo tanto, devuelven valores basados ​​en las decisiones y los almacenan en sus respectivas variables. En cada caso, podemos pasar dos argumentos al definir los diálogos: el primero es siempre el título del diálogo y el segundo contiene el contenido de su mensaje principal.

Entonces, para empezar desde arriba. En la línea 7, definimos un cuadro de diálogo showinfo simple, que solo debe mostrar un ícono neutral, un mensaje y un botón OK que lo cierra. En las líneas 8 y 9, tenemos tipos de cuadros de mensaje similares y simples, pero sus iconos indican que se requiere precaución por parte del usuario (showwarning) o que se ha producido un error (showerror). Tenga en cuenta que en cada uno de los tres casos, se reproduce un sonido diferente en la apariencia del cuadro de diálogo.

Como dije antes, las líneas 11 - 21 contienen el código responsable de mostrar los diálogos para obtener la decisión del usuario. askokcancel (línea 11) devuelve Verdadero si el usuario hace clic en Aceptar y Falso si hace clic en Cancelar. askyesno (línea 14) devuelve Verdadero si el usuario hace clic en y Falso si el usuario hace clic en No. askretrycancel (línea 17) devuelve Verdadero si el usuario hace clic en Reintentar y Falso si el usuario hace clic en Cancelar. askquestion es muy similar a askyesno, pero devuelve 'yes' si el usuario hace clic en y 'no' si el usuario hace clic en No.

Tenga en cuenta que la apariencia exacta del cuadro de diálogo de archivo, el selector de color y todos los cuadros de mensaje depende del sistema operativo en el que se ejecuta el código, así como del idioma del sistema.

Barra de progreso {#barra de progreso}

Otro elemento útil de las GUI avanzadas es una Barra de progreso. El siguiente ejemplo muestra una implementación simple de esta característica usando Tkinter:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import tkinter
import time
from tkinter import ttk

root = tkinter.Tk()

def start():
    for k in range(1, 11):
        progress_var.set(k)
        print("STEP", k)
        k += 1
        time.sleep(1)
        root.update_idletasks()

b1 = tkinter.Button(root, text="START", command=start)
b1.pack(side="left")

progress_var = tkinter.IntVar()

pb = ttk.Progressbar(root, orient="horizontal",
                     length=200, maximum=10,
                     mode="determinate",
                     var=progress_var)
pb.pack(side="left")

pb["value"] = 0

root.mainloop()

Producción:

{.img-responsive}

El ejemplo anterior muestra la implementación de Progressbar. Es parte del módulo tkinter.ttk, que brinda acceso al conjunto de widgets temáticos de Tk, introducido en **Tk 8.5 **. Es por eso que necesitamos importar adicionalmente el módulo ttk en la línea 3.

El estado de nuestra barra de progreso estará controlado por el tiempo: la barra progresará en diez pasos, ejecutados en intervalos de un segundo. Para ello, importamos el módulo time en la línea 2.

Definimos nuestra Progressbar en la línea 20. Definimos su widget principal (root), le damos una orientación "horizontal" y una longitud de 200 píxeles. Luego, definimos el valor máximo, que es el valor de la variable asignada a la barra de progreso usando el argumento var (en nuestro caso, la variable progress_var), lo que significa que la barra de progreso se llena por completo. Establecemos el modo en "determinado", lo que significa que nuestro código moverá la longitud del indicador a puntos definidos con precisión en función del valor de progress_var.

La variable entera progress_var que controlará el progreso de la barra se define en la línea 18. En la línea 26, usando una asignación similar a un diccionario, establecemos el valor inicial de la barra de progreso en 0.

En la línea 15, creamos un Botón que se supone que inicia el reloj controlando el progreso de nuestra barra ejecutando la función start(), definida entre las líneas 7 y 13. Allí, tenemos un simple bucle for, que iterará a través de valores entre 1 y 10. Con cada iteración, el valor de progress_var se actualiza y aumenta en 1. Para poder observar claramente el progreso, esperamos un segundo durante cada iteración ( línea 12). Luego usamos el método update_idletasks() de la ventana raíz en la línea 13, para permitir que el programa actualice la apariencia de la barra de progreso aunque todavía estemos ejecutando el bucle for (entonces, están técnicamente todavía en una única iteración mainloop()).

megawidgets de Python

Si usa mucho Tkinter en sus proyectos, creo que es una buena idea considerar incorporar [Mega widgets de Python] (http://pmw.sourceforge.net/) en su código. Python Mega Widgets es un conjunto de herramientas basado en Tkinter que ofrece un conjunto de megawidgets: widgets complejos, funcionales y estéticamente agradables hechos de widgets Tkinter más simples. Lo bueno de este paquete, que puedes descargar aquí es que la filosofía general de definir y orientar los widgets es la misma que en el caso de Tkinter , y puede mezclar ambas bibliotecas en su código. Terminemos nuestro tutorial raspando la superficie de este poderoso conjunto de herramientas.

Widget de campo de entrada {#widget de campo de entrada}

Uno de los widgets más útiles del paquete Pmw es EntryField. Analicemos el siguiente ejemplo para ver de qué es capaz:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import tkinter
import Pmw

root = tkinter.Tk()

def color_entry_label():
    color = entry_color.get()
    entry_number.configure(label_bg=color)

entry_color = Pmw.EntryField(root, labelpos="w",
                                 label_text="First name:",
                                 entry_bg="white",
                                 entry_width=15,
                                 validate="alphabetic")

entry_number = Pmw.EntryField(root, labelpos="w",
                                 label_text="Integer:",
                                 entry_bg="white",
                                 entry_width=15,
                                 validate="integer")

ok_button = tkinter.Button(root, text="OK", command=color_entry_label)

entry_color.pack(anchor="e")
entry_number.pack(anchor="e")
ok_button.pack(fill="x")

root.mainloop()

Producción:

{.img-responsive}

Esta vez, no solo tenemos que importar tkinter, sino también nuestro paquete Pmw recién instalado (línea 2). Como siempre, usamos la clase Tk para iniciar nuestra ventana raíz.

En las líneas 10-14 y 16-20 definimos dos widgets Pmw.EntryField. Un EntryField es una combinación funcional de Label y Entry de Tkinter, con algunas funcionalidades útiles adicionales. El primer argumento para la inicialización del widget es, por supuesto, el widget principal. label_text, entry_bg y entry_width controlan algunos aspectos que se explican por sí mismos de la apariencia del widget. El argumento más interesante de nuestro ejemplo es probablemente el argumento validar. Aquí, podemos decidir qué tipo de datos puede poner el usuario dentro del campo.

En el campo entry_color, esperamos una cadena de letras, por lo que establecemos validate en "alfabético". En el widget entry_number, esperamos un número entero, y eso es lo que establecemos en el valor del argumento validate. De esta forma, si intentamos poner un número dentro del primero y una letra dentro del segundo, los símbolos simplemente no aparecerán en los widgets y sonará un sonido del sistema informándonos que estamos tratando de hacer algo. equivocado. Además, si el widget espera cierto tipo de datos y su contenido está en conflicto con esta condición en el momento en que se inicializa, el EntryField se resaltará en rojo.

Como puede ver en nuestro ejemplo, justo después de mostrar nuestra ventana, el primer campo de entrada es blanco y el segundo es rojo. Esto se debe a que una cadena vacía (contenido predeterminado de las entradas) cae en la categoría de entidades "alfabéticas", pero definitivamente no es un número entero.

El botón definido en la línea 26 ejecuta el comando color_entry_label() definido entre las líneas 6 y 8. El objetivo de la función es pintar el fondo de la etiqueta del widget entry_number de acuerdo con el contenido del widget entry_color . En la línea 7, el método get() se utiliza para extraer el contenido de entry_color EntryField. Luego, naturalmente, se usa el método configure() para cambiar la apariencia del widget entry_number. Tenga en cuenta que para cambiar las características de los widgets de Pmw que se componen de varios widgets más simples, debemos especificar qué sub-widget queremos configurar (en nuestro caso, es la etiqueta, por eso configuramos label_bg y no, digamos, entryfield_bg).

El widget EntryField puede no ser visualmente muy impresionante, pero incluso este simple ejemplo ilustra el potencial de los mega-widgets - construir este tipo de pieza de autoverificación de la interfaz de mayor complejidad requeriría mucho más código si tratáramos de lograr el mismo efecto usando Tkinter simple. Te animo a explorar otros potentes mega-widgets descritos en la documentación del kit de herramientas.

Conclusión

Tkinter es una de las muchas bibliotecas GUI disponibles para Python, pero su gran ventaja es que se considera un estándar de Python y todavía se distribuye, de forma predeterminada, con todas las distribuciones de Python. Espero que haya disfrutado de este pequeño tutorial y ahora tenga una buena comprensión de la creación de interfaces para usuarios que podrían asustarse con el software operado por línea de comandos. de comandos.